2 * Copyright (C) 2012 Roland Krikava <info@bluedigits.com>
3 * Copyright (C) 2010-2011 David Hoyt <dhoyt@hoytsoft.org>
4 * Copyright (C) 2010 Andoni Morales <ylatuya@gmail.com>
5 * Copyright (C) 2012 Sebastian Dröge <sebastian.droege@collabora.co.uk>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
26 #include "d3dvideosink.h"
28 #define ELEMENT_NAME "d3dvideosink"
33 PROP_FORCE_ASPECT_RATIO,
34 PROP_CREATE_RENDER_WINDOW,
35 PROP_STREAM_STOP_ON_CLOSE,
36 PROP_ENABLE_NAVIGATION_EVENTS,
40 #define DEFAULT_FORCE_ASPECT_RATIO TRUE
41 #define DEFAULT_CREATE_RENDER_WINDOW TRUE
42 #define DEFAULT_STREAM_STOP_ON_CLOSE TRUE
43 #define DEFAULT_ENABLE_NAVIGATION_EVENTS TRUE
45 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
48 GST_STATIC_CAPS ("video/x-raw, "
49 "format = (string) { I420, YV12, UYVY, YUY2, NV12, BGRx, RGBx, RGBA, BGRA, BGR, RGB16, RGB15 }, "
50 "framerate = (fraction) [ 0, MAX ], "
51 "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ]")
54 GST_DEBUG_CATEGORY (gst_d3dvideosink_debug);
55 #define GST_CAT_DEFAULT gst_d3dvideosink_debug
58 /* GstXOverlay Interface */
60 gst_d3dvideosink_video_overlay_interface_init (GstVideoOverlayInterface *
62 static void gst_d3dvideosink_set_window_handle (GstVideoOverlay * overlay,
64 static void gst_d3dvideosink_set_render_rectangle (GstVideoOverlay * overlay,
65 gint x, gint y, gint width, gint height);
66 static void gst_d3dvideosink_expose (GstVideoOverlay * overlay);
67 /* GstNavigation Interface */
68 static void gst_d3dvideosink_navigation_interface_init (GstNavigationInterface *
70 static void gst_d3dvideosink_navigation_send_event (GstNavigation * navigation,
71 GstStructure * structure);
73 static void gst_d3dvideosink_set_property (GObject * object, guint prop_id,
74 const GValue * value, GParamSpec * pspec);
75 static void gst_d3dvideosink_get_property (GObject * object, guint prop_id,
76 GValue * value, GParamSpec * pspec);
77 static void gst_d3dvideosink_finalize (GObject * gobject);
79 static GstCaps *gst_d3dvideosink_get_caps (GstBaseSink * basesink,
81 static gboolean gst_d3dvideosink_set_caps (GstBaseSink * bsink, GstCaps * caps);
82 static gboolean gst_d3dvideosink_start (GstBaseSink * sink);
83 static gboolean gst_d3dvideosink_stop (GstBaseSink * sink);
84 static gboolean gst_d3dvideosink_propose_allocation (GstBaseSink * bsink,
87 static GstFlowReturn gst_d3dvideosink_show_frame (GstVideoSink * vsink,
91 G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, gst_d3dvideosink_navigation_interface_init); \
92 G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY, gst_d3dvideosink_video_overlay_interface_init); \
93 GST_DEBUG_CATEGORY_INIT (gst_d3dvideosink_debug, ELEMENT_NAME, 0, "Direct3D Video");
95 G_DEFINE_TYPE_WITH_CODE (GstD3DVideoSink, gst_d3dvideosink, GST_TYPE_VIDEO_SINK,
99 gst_d3dvideosink_class_init (GstD3DVideoSinkClass * klass)
101 GObjectClass *gobject_class;
102 GstElementClass *gstelement_class;
103 GstVideoSinkClass *gstvideosink_class;
104 GstBaseSinkClass *gstbasesink_class;
106 gobject_class = (GObjectClass *) klass;
107 gstelement_class = (GstElementClass *) klass;
108 gstvideosink_class = (GstVideoSinkClass *) klass;
109 gstbasesink_class = (GstBaseSinkClass *) klass;
111 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_d3dvideosink_finalize);
112 gobject_class->set_property =
113 GST_DEBUG_FUNCPTR (gst_d3dvideosink_set_property);
114 gobject_class->get_property =
115 GST_DEBUG_FUNCPTR (gst_d3dvideosink_get_property);
117 gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_d3dvideosink_get_caps);
118 gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_d3dvideosink_set_caps);
119 gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_d3dvideosink_start);
120 gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_d3dvideosink_stop);
121 gstbasesink_class->propose_allocation =
122 GST_DEBUG_FUNCPTR (gst_d3dvideosink_propose_allocation);
124 gstvideosink_class->show_frame =
125 GST_DEBUG_FUNCPTR (gst_d3dvideosink_show_frame);
128 g_object_class_install_property (G_OBJECT_CLASS (klass),
129 PROP_FORCE_ASPECT_RATIO, g_param_spec_boolean ("force-aspect-ratio",
130 "Force aspect ratio",
131 "When enabled, scaling will respect original aspect ratio",
132 DEFAULT_FORCE_ASPECT_RATIO,
133 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
134 g_object_class_install_property (G_OBJECT_CLASS (klass),
135 PROP_CREATE_RENDER_WINDOW, g_param_spec_boolean ("create-render-window",
136 "Create render window",
137 "If no window ID is given, a new render window is created",
138 DEFAULT_CREATE_RENDER_WINDOW,
139 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
140 g_object_class_install_property (G_OBJECT_CLASS (klass),
141 PROP_STREAM_STOP_ON_CLOSE, g_param_spec_boolean ("stream-stop-on-close",
142 "Stop streaming on window close",
143 "If the render window is closed stop stream",
144 DEFAULT_STREAM_STOP_ON_CLOSE,
145 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
146 g_object_class_install_property (G_OBJECT_CLASS (klass),
147 PROP_ENABLE_NAVIGATION_EVENTS,
148 g_param_spec_boolean ("enable-navigation-events",
149 "Enable navigation events",
150 "When enabled, navigation events are sent upstream",
151 DEFAULT_ENABLE_NAVIGATION_EVENTS,
152 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154 gst_element_class_set_static_metadata (gstelement_class,
155 "Direct3D video sink", "Sink/Video",
156 "Display data using a Direct3D video renderer",
157 "David Hoyt <dhoyt@hoytsoft.org>, Roland Krikava <info@bluedigits.com>");
159 gst_element_class_add_pad_template (gstelement_class,
160 gst_static_pad_template_get (&sink_template));
162 g_rec_mutex_init (&klass->lock);
166 gst_d3dvideosink_init (GstD3DVideoSink * sink)
168 GST_DEBUG_OBJECT (sink, " ");
170 /* Init Properties */
171 sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
172 sink->create_internal_window = DEFAULT_CREATE_RENDER_WINDOW;
173 sink->stream_stop_on_close = DEFAULT_STREAM_STOP_ON_CLOSE;
174 sink->enable_navigation_events = DEFAULT_ENABLE_NAVIGATION_EVENTS;
176 g_rec_mutex_init (&sink->lock);
179 /** GObject Functions **/
182 gst_d3dvideosink_finalize (GObject * gobject)
184 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (gobject);
186 GST_DEBUG_OBJECT (sink, " ");
188 gst_object_replace ((GstObject **) & sink->pool, NULL);
189 gst_object_replace ((GstObject **) & sink->fallback_pool, NULL);
191 gst_caps_replace (&sink->supported_caps, NULL);
193 g_rec_mutex_clear (&sink->lock);
195 G_OBJECT_CLASS (gst_d3dvideosink_parent_class)->finalize (gobject);
199 gst_d3dvideosink_set_property (GObject * object, guint prop_id,
200 const GValue * value, GParamSpec * pspec)
202 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (object);
205 case PROP_FORCE_ASPECT_RATIO:
206 sink->force_aspect_ratio = g_value_get_boolean (value);
208 case PROP_CREATE_RENDER_WINDOW:
209 sink->create_internal_window = g_value_get_boolean (value);
211 case PROP_STREAM_STOP_ON_CLOSE:
212 sink->stream_stop_on_close = g_value_get_boolean (value);
214 case PROP_ENABLE_NAVIGATION_EVENTS:
215 sink->enable_navigation_events = g_value_get_boolean (value);
218 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
224 gst_d3dvideosink_get_property (GObject * object, guint prop_id, GValue * value,
227 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (object);
230 case PROP_FORCE_ASPECT_RATIO:
231 g_value_set_boolean (value, sink->force_aspect_ratio);
233 case PROP_CREATE_RENDER_WINDOW:
234 g_value_set_boolean (value, sink->create_internal_window);
236 case PROP_STREAM_STOP_ON_CLOSE:
237 g_value_set_boolean (value, sink->stream_stop_on_close);
239 case PROP_ENABLE_NAVIGATION_EVENTS:
240 g_value_set_boolean (value, sink->enable_navigation_events);
243 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
248 /** GstBaseSinkClass Functions **/
251 gst_d3dvideosink_get_caps (GstBaseSink * basesink, GstCaps * filter)
253 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (basesink);
256 caps = d3d_supported_caps (sink);
258 caps = gst_pad_get_pad_template_caps (GST_VIDEO_SINK_PAD (sink));
260 if (caps && filter) {
262 isect = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
263 gst_caps_unref (caps);
271 gst_d3dvideosink_set_caps (GstBaseSink * bsink, GstCaps * caps)
273 GstD3DVideoSink *sink;
275 gint video_width, video_height;
276 gint video_par_n, video_par_d; /* video's PAR */
277 gint display_par_n = 1, display_par_d = 1; /* display's PAR */
280 GstBufferPool *newpool, *oldpool;
281 GstBufferPool *newfbpool, *oldfbpool;
282 GstStructure *config;
284 GST_DEBUG_OBJECT (bsink, " ");
286 GST_DEBUG_OBJECT (bsink, "Caps: %s", (tmp = gst_caps_to_string (caps)));
287 sink = GST_D3DVIDEOSINK (bsink);
289 sink_caps = d3d_supported_caps (sink);
291 if (!gst_caps_can_intersect (sink_caps, caps))
292 goto incompatible_caps;
293 gst_caps_replace (&sink_caps, NULL);
295 memset (&sink->info, 0, sizeof (GstVideoInfo));
296 if (!gst_video_info_from_caps (&sink->info, caps))
299 sink->format = sink->info.finfo->format;
300 video_width = sink->info.width;
301 video_height = sink->info.height;
302 video_par_n = sink->info.par_n;
303 video_par_d = sink->info.par_d;
305 GST_DEBUG_OBJECT (bsink, "Set Caps Format: %s",
306 gst_video_format_to_string (sink->format));
308 /* get aspect ratio from caps if it's present, and
309 * convert video width and height to a display width and height
310 * using wd / hd = wv / hv * PARv / PARd */
312 /* TODO: Get display PAR */
314 if (!gst_video_calculate_display_ratio (&num, &den, video_width,
315 video_height, video_par_n, video_par_d, display_par_n, display_par_d))
318 GST_DEBUG_OBJECT (sink,
319 "video width/height: %dx%d, calculated display ratio: %d/%d format: %u",
320 video_width, video_height, num, den, sink->format);
322 /* now find a width x height that respects this display ratio.
323 * prefer those that have one of w/h the same as the incoming video
324 * using wd / hd = num / den
327 /* start with same height, because of interlaced video
328 * check hd / den is an integer scale factor, and scale wd with the PAR
330 if (video_height % den == 0) {
331 GST_DEBUG_OBJECT (sink, "keeping video height");
332 GST_VIDEO_SINK_WIDTH (sink) = (guint)
333 gst_util_uint64_scale_int (video_height, num, den);
334 GST_VIDEO_SINK_HEIGHT (sink) = video_height;
335 } else if (video_width % num == 0) {
336 GST_DEBUG_OBJECT (sink, "keeping video width");
337 GST_VIDEO_SINK_WIDTH (sink) = video_width;
338 GST_VIDEO_SINK_HEIGHT (sink) = (guint)
339 gst_util_uint64_scale_int (video_width, den, num);
341 GST_DEBUG_OBJECT (sink, "approximating while keeping video height");
342 GST_VIDEO_SINK_WIDTH (sink) = (guint)
343 gst_util_uint64_scale_int (video_height, num, den);
344 GST_VIDEO_SINK_HEIGHT (sink) = video_height;
346 GST_DEBUG_OBJECT (sink, "scaling to %dx%d",
347 GST_VIDEO_SINK_WIDTH (sink), GST_VIDEO_SINK_HEIGHT (sink));
349 if (GST_VIDEO_SINK_WIDTH (sink) <= 0 || GST_VIDEO_SINK_HEIGHT (sink) <= 0)
350 goto no_display_size;
352 sink->width = video_width;
353 sink->height = video_height;
355 GST_DEBUG_OBJECT (bsink, "Selected caps: %s", (tmp =
356 gst_caps_to_string (caps)));
359 if (!d3d_set_render_format (sink))
360 goto incompatible_caps;
362 /* Create a window (or start using an application-supplied one, then connect the graph */
363 d3d_prepare_window (sink);
365 newpool = gst_d3dsurface_buffer_pool_new (sink);
366 config = gst_buffer_pool_get_config (newpool);
367 /* we need at least 2 buffer because we hold on to the last one */
368 gst_buffer_pool_config_set_params (config, caps, sink->info.size, 2, 0);
369 if (!gst_buffer_pool_set_config (newpool, config)) {
370 gst_object_unref (newpool);
371 GST_ERROR_OBJECT (sink, "Failed to set buffer pool configuration");
375 newfbpool = gst_d3dsurface_buffer_pool_new (sink);
376 config = gst_buffer_pool_get_config (newfbpool);
377 /* we need at least 2 buffer because we hold on to the last one */
378 gst_buffer_pool_config_set_params (config, caps, sink->info.size, 2, 0);
379 /* Fallback pool must use videometa */
380 gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
381 if (!gst_buffer_pool_set_config (newfbpool, config)) {
382 gst_object_unref (newfbpool);
383 GST_ERROR_OBJECT (sink, "Failed to set buffer pool configuration");
387 GST_OBJECT_LOCK (sink);
388 oldpool = sink->pool;
389 sink->pool = newpool;
390 oldfbpool = sink->fallback_pool;
391 sink->fallback_pool = newfbpool;
392 GST_OBJECT_UNLOCK (sink);
395 gst_object_unref (oldpool);
397 gst_object_unref (oldfbpool);
403 GST_ERROR_OBJECT (sink, "caps incompatible");
404 gst_caps_unref (sink_caps);
409 gchar *caps_txt = gst_caps_to_string (caps);
410 GST_DEBUG_OBJECT (sink,
411 "Could not locate image format from caps %s", caps_txt);
417 GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL),
418 ("Error calculating the output display ratio of the video."));
423 GST_ELEMENT_ERROR (sink, CORE, NEGOTIATION, (NULL),
424 ("Error calculating the output display ratio of the video."));
430 gst_d3dvideosink_start (GstBaseSink * bsink)
432 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (bsink);
434 GST_DEBUG_OBJECT (bsink, "Start() called");
436 return d3d_class_init (sink);
440 gst_d3dvideosink_stop (GstBaseSink * bsink)
442 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (bsink);
444 GST_DEBUG_OBJECT (bsink, "Stop() called");
446 d3d_class_destroy (sink);
452 gst_d3dvideosink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
454 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (bsink);
456 GstStructure *config;
461 gst_query_parse_allocation (query, &caps, &need_pool);
463 GST_DEBUG_OBJECT (sink, "no caps specified");
467 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
469 GST_OBJECT_LOCK (sink);
470 pool = sink->pool ? gst_object_ref (sink->pool) : NULL;
471 GST_OBJECT_UNLOCK (sink);
476 /* we had a pool, check caps */
477 GST_DEBUG_OBJECT (sink, "check existing pool caps");
478 config = gst_buffer_pool_get_config (pool);
479 gst_buffer_pool_config_get_params (config, &pcaps, &size, NULL, NULL);
481 if (!gst_caps_is_equal (caps, pcaps)) {
482 GST_DEBUG_OBJECT (sink, "pool has different caps");
483 /* different caps, we can't use this pool */
484 gst_object_unref (pool);
487 gst_structure_free (config);
490 if (pool == NULL && need_pool) {
493 if (!gst_video_info_from_caps (&info, caps)) {
494 GST_ERROR_OBJECT (sink, "allocation query has invalid caps %"
495 GST_PTR_FORMAT, caps);
499 GST_DEBUG_OBJECT (sink, "create new pool");
500 pool = gst_d3dsurface_buffer_pool_new (sink);
502 /* the normal size of a frame */
505 config = gst_buffer_pool_get_config (pool);
506 /* we need at least 2 buffer because we hold on to the last one */
507 gst_buffer_pool_config_set_params (config, caps, size, 2, 0);
508 if (!gst_buffer_pool_set_config (pool, config)) {
509 gst_object_unref (pool);
510 GST_ERROR_OBJECT (sink, "failed to set pool configuration");
516 /* we need at least 2 buffer because we hold on to the last one */
517 gst_query_add_allocation_pool (query, pool, size, 2, 0);
518 gst_object_unref (pool);
524 /** PUBLIC FUNCTIONS **/
526 /* Iterface Registrations */
529 gst_d3dvideosink_video_overlay_interface_init (GstVideoOverlayInterface * iface)
531 iface->set_window_handle = gst_d3dvideosink_set_window_handle;
532 iface->set_render_rectangle = gst_d3dvideosink_set_render_rectangle;
533 iface->expose = gst_d3dvideosink_expose;
537 gst_d3dvideosink_navigation_interface_init (GstNavigationInterface * iface)
539 iface->send_event = gst_d3dvideosink_navigation_send_event;
542 /* Video Render Code */
545 gst_d3dvideosink_set_window_handle (GstVideoOverlay * overlay,
548 d3d_set_window_handle (GST_D3DVIDEOSINK (overlay), window_id, FALSE);
552 gst_d3dvideosink_set_render_rectangle (GstVideoOverlay * overlay, gint x,
553 gint y, gint width, gint height)
555 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (overlay);
556 sink->render_rect.x = x;
557 sink->render_rect.y = y;
558 sink->render_rect.w = width;
559 sink->render_rect.h = height;
560 d3d_set_render_rectangle (sink);
564 gst_d3dvideosink_expose (GstVideoOverlay * overlay)
566 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (overlay);
567 d3d_expose_window (sink);
571 gst_d3dvideosink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
573 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (vsink);
574 return d3d_render_buffer (sink, buffer);
577 /* Video Navigation Events */
580 gst_d3dvideosink_navigation_send_event (GstNavigation * navigation,
581 GstStructure * structure)
583 GstD3DVideoSink *sink = GST_D3DVIDEOSINK (navigation);
586 if ((e = gst_event_new_navigation (structure))) {
588 if ((pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink)))) {
589 gst_pad_send_event (pad, e);
590 gst_object_unref (pad);
595 /** PRIVATE FUNCTIONS **/
598 /* Plugin entry point */
600 plugin_init (GstPlugin * plugin)
602 /* PRIMARY: this is the best videosink to use on windows */
603 if (!gst_element_register (plugin, ELEMENT_NAME,
604 GST_RANK_PRIMARY, GST_TYPE_D3DVIDEOSINK))
610 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
613 "Direct3D sink wrapper plugin",
614 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)