servo_media_gstreamer_render_unix/
lib.rs1use gst::prelude::*;
10use gst_gl::prelude::*;
11use sm_gst_render;
12use sm_gst_render::Render;
13use sm_player;
14use sm_player::context::{GlApi, GlContext, NativeDisplay, PlayerGLContext};
15use sm_player::video::{Buffer, VideoFrame, VideoFrameData};
16use sm_player::PlayerError;
17use std::sync::{Arc, Mutex};
18
19struct GStreamerBuffer {
20 is_external_oes: bool,
21 frame: gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>,
22}
23
24impl Buffer for GStreamerBuffer {
25 fn to_vec(&self) -> Result<VideoFrameData, ()> {
26 if self.frame.format() == gst_video::VideoFormat::Rgba {
28 let tex_id = self.frame.texture_id(0).map_err(|_| ())?;
29 Ok(if self.is_external_oes {
30 VideoFrameData::OESTexture(tex_id)
31 } else {
32 VideoFrameData::Texture(tex_id)
33 })
34 } else {
35 Err(())
36 }
37 }
38}
39
40pub struct RenderUnix {
41 display: gst_gl::GLDisplay,
42 app_context: gst_gl::GLContext,
43 gst_context: Arc<Mutex<Option<gst_gl::GLContext>>>,
44 gl_upload: Arc<Mutex<Option<gst::Element>>>,
45}
46
47impl RenderUnix {
48 pub fn new(app_gl_context: Box<dyn PlayerGLContext>) -> Option<RenderUnix> {
55 if gst::ElementFactory::find("glsinkbin").is_none() {
58 return None;
59 }
60
61 let display_native = app_gl_context.get_native_display();
62 let gl_context = app_gl_context.get_gl_context();
63 let gl_api = match app_gl_context.get_gl_api() {
64 GlApi::OpenGL => gst_gl::GLAPI::OPENGL,
65 GlApi::OpenGL3 => gst_gl::GLAPI::OPENGL3,
66 GlApi::Gles1 => gst_gl::GLAPI::GLES1,
67 GlApi::Gles2 => gst_gl::GLAPI::GLES2,
68 GlApi::None => return None,
69 };
70
71 let (wrapped_context, display) = match gl_context {
72 GlContext::Egl(context) => {
73 let display = match display_native {
74 #[cfg(feature = "gl-egl")]
75 NativeDisplay::Egl(display_native) => {
76 unsafe { gstreamer_gl_egl::GLDisplayEGL::with_egl_display(display_native) }
77 .map(|display| display.upcast())
78 .ok()
79 }
80 #[cfg(feature = "gl-wayland")]
81 NativeDisplay::Wayland(display_native) => unsafe {
82 gstreamer_gl_wayland::GLDisplayWayland::with_display(display_native)
83 }
84 .map(|display| display.upcast())
85 .ok(),
86 _ => None,
87 };
88
89 RenderUnix::create_wrapped_context(
90 display,
91 context,
92 gst_gl::GLPlatform::EGL,
93 gl_api,
94 )
95 }
96 GlContext::Glx(context) => {
97 let display = match display_native {
98 #[cfg(feature = "gl-x11")]
99 NativeDisplay::X11(display_native) => {
100 unsafe { gstreamer_gl_x11::GLDisplayX11::with_display(display_native) }
101 .map(|display| display.upcast())
102 .ok()
103 }
104 _ => None,
105 };
106
107 RenderUnix::create_wrapped_context(
108 display,
109 context,
110 gst_gl::GLPlatform::GLX,
111 gl_api,
112 )
113 }
114 GlContext::Unknown => (None, None),
115 };
116
117 if let Some(app_context) = wrapped_context {
118 let cat = gst::DebugCategory::get("servoplayer").unwrap();
119 let _: Result<(), ()> = app_context
120 .activate(true)
121 .and_then(|_| {
122 app_context.fill_info().or_else(|err| {
123 gst::warning!(
124 cat,
125 "Couldn't fill the wrapped app GL context: {}",
126 err.to_string()
127 );
128 Ok(())
129 })
130 })
131 .or_else(|_| {
132 gst::warning!(cat, "Couldn't activate the wrapped app GL context");
133 Ok(())
134 });
135 Some(RenderUnix {
136 display: display.unwrap(),
137 app_context,
138 gst_context: Arc::new(Mutex::new(None)),
139 gl_upload: Arc::new(Mutex::new(None)),
140 })
141 } else {
142 None
143 }
144 }
145
146 fn create_wrapped_context(
147 display: Option<gst_gl::GLDisplay>,
148 handle: usize,
149 platform: gst_gl::GLPlatform,
150 api: gst_gl::GLAPI,
151 ) -> (Option<gst_gl::GLContext>, Option<gst_gl::GLDisplay>) {
152 if let Some(display) = display {
153 let wrapped_context =
154 unsafe { gst_gl::GLContext::new_wrapped(&display, handle, platform, api) };
155 (wrapped_context, Some(display))
156 } else {
157 (None, None)
158 }
159 }
160}
161
162impl Render for RenderUnix {
163 fn is_gl(&self) -> bool {
164 true
165 }
166
167 fn build_frame(&self, sample: gst::Sample) -> Result<VideoFrame, ()> {
168 if self.gst_context.lock().unwrap().is_none() && self.gl_upload.lock().unwrap().is_some() {
169 *self.gst_context.lock().unwrap() =
170 if let Some(glupload) = self.gl_upload.lock().unwrap().as_ref() {
171 Some(glupload.property::<gst_gl::GLContext>("context"))
172 } else {
173 None
174 };
175 }
176
177 let buffer = sample.buffer_owned().ok_or(())?;
178 let caps = sample.caps().ok_or(())?;
179
180 let is_external_oes = caps
181 .structure(0)
182 .and_then(|s| {
183 s.get::<&str>("texture-target").ok().and_then(|target| {
184 if target == "external-oes" {
185 Some(s)
186 } else {
187 None
188 }
189 })
190 })
191 .is_some();
192
193 let info = gst_video::VideoInfo::from_caps(caps).map_err(|_| ())?;
194 let frame = gst_gl::GLVideoFrame::from_buffer_readable(buffer, &info).map_err(|_| ())?;
195
196 VideoFrame::new(
197 info.width() as i32,
198 info.height() as i32,
199 Arc::new(GStreamerBuffer {
200 is_external_oes,
201 frame,
202 }),
203 )
204 }
205
206 fn build_video_sink(
207 &self,
208 appsink: &gst::Element,
209 pipeline: &gst::Element,
210 ) -> Result<(), PlayerError> {
211 if self.gl_upload.lock().unwrap().is_some() {
212 return Err(PlayerError::Backend(
213 "render unix already setup the video sink".to_owned(),
214 ));
215 }
216
217 let vsinkbin = gst::ElementFactory::make("glsinkbin")
218 .name("servo-media-vsink")
219 .build()
220 .map_err(|error| {
221 PlayerError::Backend(format!("glupload creation failed: {error:?}"))
222 })?;
223
224 let caps = gst::Caps::builder("video/x-raw")
225 .features([gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY])
226 .field("format", gst_video::VideoFormat::Rgba.to_str())
227 .field("texture-target", gst::List::new(["2D", "external-oes"]))
228 .build();
229 appsink.set_property("caps", caps);
230
231 vsinkbin.set_property("sink", appsink);
232
233 pipeline.set_property("video-sink", &vsinkbin);
234
235 let bus = pipeline.bus().expect("pipeline with no bus");
236 let display_ = self.display.clone();
237 let context_ = self.app_context.clone();
238 bus.set_sync_handler(move |_, msg| {
239 match msg.view() {
240 gst::MessageView::NeedContext(ctxt) => {
241 if let Some(el) = msg
242 .src()
243 .map(|s| s.clone().downcast::<gst::Element>().unwrap())
244 {
245 let context_type = ctxt.context_type();
246 if context_type == *gst_gl::GL_DISPLAY_CONTEXT_TYPE {
247 let ctxt = gst::Context::new(context_type, true);
248 ctxt.set_gl_display(&display_);
249 el.set_context(&ctxt);
250 } else if context_type == "gst.gl.app_context" {
251 let mut ctxt = gst::Context::new(context_type, true);
252 {
253 let s = ctxt.get_mut().unwrap().structure_mut();
254 s.set_value("context", context_.to_send_value());
255 }
256 el.set_context(&ctxt);
257 }
258 }
259 }
260 _ => (),
261 }
262
263 gst::BusSyncReply::Pass
264 });
265
266 let mut iter = vsinkbin
267 .dynamic_cast::<gst::Bin>()
268 .unwrap()
269 .iterate_elements();
270 *self.gl_upload.lock().unwrap() = loop {
271 match iter.next() {
272 Ok(Some(element)) => {
273 if "glupload" == element.factory().unwrap().name() {
274 break Some(element);
275 }
276 }
277 Err(gst::IteratorError::Resync) => iter.resync(),
278 _ => break None,
279 }
280 };
281
282 Ok(())
283 }
284}