3 * Copyright (C) 2004-6 Zaheer Abbas Merali <zaheerabbas at merali dot org>
4 * Copyright (C) 2007 Pioneers of the Inevitable <songbird@songbirdnest.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
21 * The development of this code was made possible due to the involvement of
22 * Pioneers of the Inevitable, the creators of the Songbird Music player.
27 * SECTION:element-osxvideosink
31 * The OSXVideoSink renders video frames to a MacOSX window. The video output
32 * can be directed to a window embedded in an existing NSApp. This can be done
33 * by setting the "embed" property to #TRUE. When the NSView to be embedded is
34 * created an element #GstMessage with a name of 'have-ns-view' will be created
35 * and posted on the bus. The pointer to the NSView to embed will be in the
36 * 'nsview' field of that message. If no embedding is requested, the plugin will
37 * create a standalone window.
39 * <title>Examples</title>
41 * Simple timeline to test the sink :
43 * gst-launch-0.10 -v videotestsrc ! osxvideosink
52 #include "osxvideosink.h"
54 #import "cocoawindow.h"
56 /* Debugging category */
57 GST_DEBUG_CATEGORY (gst_debug_osx_video_sink);
58 #define GST_CAT_DEFAULT gst_debug_osx_video_sink
60 /* ElementFactory information */
61 static const GstElementDetails gst_osx_video_sink_details =
62 GST_ELEMENT_DETAILS ("OSX Video sink",
64 "OSX native videosink",
65 "Zaheer Abbas Merali <zaheerabbas at merali dot org>");
67 /* Default template - initiated with class struct to allow gst-register to work
69 static GstStaticPadTemplate gst_osx_video_sink_sink_template_factory =
70 GST_STATIC_PAD_TEMPLATE ("sink",
73 GST_STATIC_CAPS ("video/x-raw-yuv, "
74 "framerate = (fraction) [ 0, MAX ], "
75 "width = (int) [ 1, MAX ], "
76 "height = (int) [ 1, MAX ], "
77 #if G_BYTE_ORDER == G_BIG_ENDIAN
78 "format = (fourcc) YUY2")
80 "format = (fourcc) UYVY")
84 // much of the following cocoa NSApp code comes from libsdl and libcaca
85 @implementation NSApplication(Gst)
92 @implementation GstAppDelegate : NSObject
93 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
95 // destroy stuff here!
96 GST_DEBUG("Kill me please!");
97 return NSTerminateNow;
110 static GstVideoSinkClass *parent_class = NULL;
113 /* cocoa event loop - needed if not run in own app */
115 cocoa_event_loop (GstOSXVideoSink * vsink)
117 NSAutoreleasePool *pool;
119 GST_DEBUG_OBJECT (vsink, "Entering event loop");
121 pool = [[NSAutoreleasePool alloc] init];
123 while ([NSApp isRunning]) {
124 NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
125 untilDate:[NSDate distantPast]
126 inMode:NSDefaultRunLoopMode dequeue:YES ];
127 if ( event == nil ) {
131 switch ([event type]) {
132 default: //XXX Feed me please
133 [NSApp sendEvent:event];
144 GetApplicationName(void)
147 NSString *appName = 0;
149 /* Determine the application name */
150 dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle());
152 appName = [dict objectForKey: @"CFBundleName"];
154 if (![appName length])
155 appName = [[NSProcessInfo processInfo] processName];
161 CreateApplicationMenus(void)
167 NSMenuItem *menuItem;
169 /* Create the main menu bar */
170 [NSApp setMainMenu:[[NSMenu alloc] init]];
172 /* Create the application menu */
173 appName = GetApplicationName();
174 appleMenu = [[NSMenu alloc] initWithTitle:@""];
177 title = [@"About " stringByAppendingString:appName];
178 [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
180 [appleMenu addItem:[NSMenuItem separatorItem]];
182 title = [@"Hide " stringByAppendingString:appName];
183 [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@/*"h"*/""];
185 menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@/*"h"*/""];
186 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
188 [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
190 [appleMenu addItem:[NSMenuItem separatorItem]];
192 title = [@"Quit " stringByAppendingString:appName];
193 [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@/*"q"*/""];
195 /* Put menu into the menubar */
196 menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
197 [menuItem setSubmenu:appleMenu];
198 [[NSApp mainMenu] addItem:menuItem];
201 /* Tell the application object that this is now the application menu */
202 [NSApp setAppleMenu:appleMenu];
206 /* Create the window menu */
207 windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
209 /* "Minimize" item */
210 menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@/*"m"*/""];
211 [windowMenu addItem:menuItem];
214 /* Put menu into the menubar */
215 menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
216 [menuItem setSubmenu:windowMenu];
217 [[NSApp mainMenu] addItem:menuItem];
220 /* Tell the application object that this is now the window menu */
221 [NSApp setWindowsMenu:windowMenu];
222 [windowMenu release];
225 /* This function handles osx window creation */
226 static GstOSXWindow *
227 gst_osx_video_sink_osxwindow_new (GstOSXVideoSink * osxvideosink, gint width,
231 GstOSXWindow *osxwindow = NULL;
233 g_return_val_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink), NULL);
235 GST_DEBUG_OBJECT (osxvideosink, "Creating new OSX window");
237 osxwindow = g_new0 (GstOSXWindow, 1);
239 osxwindow->width = width;
240 osxwindow->height = height;
241 osxwindow->internal = TRUE;
242 osxwindow->pool = [[NSAutoreleasePool alloc] init];
244 if (osxvideosink->embed == FALSE) {
245 ProcessSerialNumber psn;
246 unsigned int mask = NSTitledWindowMask |
247 NSClosableWindowMask |
248 NSResizableWindowMask |
249 NSTexturedBackgroundWindowMask |
250 NSMiniaturizableWindowMask;
252 rect.origin.x = 100.0;
253 rect.origin.y = 100.0;
254 rect.size.width = (float) osxwindow->width;
255 rect.size.height = (float) osxwindow->height;
257 if (!GetCurrentProcess(&psn)) {
258 TransformProcessType(&psn, kProcessTransformToForegroundApplication);
259 SetFrontProcess(&psn);
262 [NSApplication sharedApplication];
264 osxwindow->win =[[GstOSXVideoSinkWindow alloc]
265 initWithContentRect: rect
267 backing: NSBackingStoreBuffered
270 GST_DEBUG("VideoSinkWindow created, %p", osxwindow->win);
271 [osxwindow->win autorelease];
272 [NSApplication sharedApplication];
273 [osxwindow->win makeKeyAndOrderFront:NSApp];
274 osxwindow->gstview =[osxwindow->win gstView];
275 [osxwindow->gstview autorelease];
276 if (osxvideosink->fullscreen)
277 [osxwindow->gstview setFullScreen:YES];
279 CreateApplicationMenus();
281 [NSApp finishLaunching];
282 [NSApp setDelegate:[[GstAppDelegate alloc] init]];
285 g_static_rec_mutex_init (&osxvideosink->event_task_lock);
286 osxvideosink->event_task = gst_task_create ((GstTaskFunction)cocoa_event_loop,
288 gst_task_set_lock (osxvideosink->event_task, &osxvideosink->event_task_lock);
289 gst_task_start (osxvideosink->event_task);
294 /* Needs to be embedded */
298 rect.size.width = (float) osxwindow->width;
299 rect.size.height = (float) osxwindow->height;
300 osxwindow->gstview =[[GstGLView alloc] initWithFrame:rect];
301 [osxwindow->gstview autorelease];
303 s = gst_structure_new ("have-ns-view",
304 "nsview", G_TYPE_POINTER, osxwindow->gstview,
307 tmp = gst_structure_to_string (s);
308 GST_DEBUG_OBJECT (osxvideosink, "Sending message %s (with view %p)",
309 tmp, osxwindow->gstview);
312 msg = gst_message_new_element (GST_OBJECT (osxvideosink), s);
313 gst_element_post_message (GST_ELEMENT (osxvideosink), msg);
315 GST_LOG_OBJECT (osxvideosink, "'have-ns-view' message sent");
320 /* This function destroys a GstXWindow */
322 gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink,
323 GstOSXWindow * osxwindow)
325 g_return_if_fail (osxwindow != NULL);
326 g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
328 [osxwindow->pool release];
330 if (osxvideosink->event_task) {
331 gst_task_join (osxvideosink->event_task);
332 gst_object_unref (osxvideosink->event_task);
333 osxvideosink->event_task = NULL;
334 g_static_rec_mutex_free (&osxvideosink->event_task_lock);
340 /* This function resizes a GstXWindow */
342 gst_osx_video_sink_osxwindow_resize (GstOSXVideoSink * osxvideosink,
343 GstOSXWindow * osxwindow, guint width, guint height)
345 NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];
346 g_return_if_fail (osxwindow != NULL);
347 g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
349 osxwindow->width = width;
350 osxwindow->height = height;
352 GST_DEBUG_OBJECT (osxvideosink, "Resizing window to (%d,%d)", width, height);
353 if (osxwindow->win) {
354 /* Call relevant cocoa function to resize window */
357 size.height = height;
359 NSLog(@"osxwindow->win = %@", osxwindow->win);
360 GST_DEBUG_OBJECT (osxvideosink, "Calling setContentSize on %p", osxwindow->win);
361 [osxwindow->win setContentSize:size];
364 /* Directly resize the underlying view */
365 GST_DEBUG_OBJECT (osxvideosink, "Calling setVideoSize on %p", osxwindow->gstview);
366 [osxwindow->gstview setVideoSize:width :height];
372 gst_osx_video_sink_osxwindow_clear (GstOSXVideoSink * osxvideosink,
373 GstOSXWindow * osxwindow)
376 g_return_if_fail (osxwindow != NULL);
377 g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
384 gst_osx_video_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
386 GstOSXVideoSink *osxvideosink;
387 GstStructure *structure;
388 gboolean res, result = FALSE;
389 gint video_width, video_height;
391 osxvideosink = GST_OSX_VIDEO_SINK (bsink);
393 GST_DEBUG_OBJECT (osxvideosink, "caps: %" GST_PTR_FORMAT, caps);
395 structure = gst_caps_get_structure (caps, 0);
396 res = gst_structure_get_int (structure, "width", &video_width);
397 res &= gst_structure_get_int (structure, "height", &video_height);
403 GST_DEBUG_OBJECT (osxvideosink, "our format is: %dx%d video",
404 video_width, video_height);
406 GST_VIDEO_SINK_WIDTH (osxvideosink) = video_width;
407 GST_VIDEO_SINK_HEIGHT (osxvideosink) = video_height;
409 gst_osx_video_sink_osxwindow_resize (osxvideosink, osxvideosink->osxwindow,
410 video_width, video_height);
418 static GstStateChangeReturn
419 gst_osx_video_sink_change_state (GstElement * element,
420 GstStateChange transition)
422 GstOSXVideoSink *osxvideosink;
424 osxvideosink = GST_OSX_VIDEO_SINK (element);
426 GST_DEBUG_OBJECT (osxvideosink, "%s => %s",
427 gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT (transition)),
428 gst_element_state_get_name(GST_STATE_TRANSITION_NEXT (transition)));
430 switch (transition) {
431 case GST_STATE_CHANGE_NULL_TO_READY:
432 /* Creating our window and our image */
433 if (!osxvideosink->osxwindow) {
434 GST_VIDEO_SINK_WIDTH (osxvideosink) = 320;
435 GST_VIDEO_SINK_HEIGHT (osxvideosink) = 240;
436 osxvideosink->osxwindow =
437 gst_osx_video_sink_osxwindow_new (osxvideosink,
438 GST_VIDEO_SINK_WIDTH (osxvideosink),
439 GST_VIDEO_SINK_HEIGHT (osxvideosink));
440 gst_osx_video_sink_osxwindow_clear (osxvideosink,
441 osxvideosink->osxwindow);
443 if (osxvideosink->osxwindow->internal)
444 gst_osx_video_sink_osxwindow_resize (osxvideosink,
445 osxvideosink->osxwindow, GST_VIDEO_SINK_WIDTH (osxvideosink),
446 GST_VIDEO_SINK_HEIGHT (osxvideosink));
449 case GST_STATE_CHANGE_READY_TO_PAUSED:
450 GST_DEBUG ("ready to paused");
451 if (osxvideosink->osxwindow)
452 gst_osx_video_sink_osxwindow_clear (osxvideosink,
453 osxvideosink->osxwindow);
454 osxvideosink->time = 0;
456 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
458 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
460 case GST_STATE_CHANGE_PAUSED_TO_READY:
461 osxvideosink->sw_scaling_failed = FALSE;
462 GST_VIDEO_SINK_WIDTH (osxvideosink) = 0;
463 GST_VIDEO_SINK_HEIGHT (osxvideosink) = 0;
465 case GST_STATE_CHANGE_READY_TO_NULL:
467 if (osxvideosink->osxwindow) {
468 gst_osx_video_sink_osxwindow_destroy (osxvideosink,
469 osxvideosink->osxwindow);
470 osxvideosink->osxwindow = NULL;
475 return (GST_ELEMENT_CLASS (parent_class))->change_state (element, transition);
480 gst_osx_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
482 GstOSXVideoSink *osxvideosink;
485 osxvideosink = GST_OSX_VIDEO_SINK (bsink);
486 viewdata = [osxvideosink->osxwindow->gstview getTextureBuffer];
488 GST_DEBUG ("show_frame");
489 memcpy (viewdata, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
490 [osxvideosink->osxwindow->gstview displayTexture];
495 /* Buffer management */
499 /* =========================================== */
501 /* Init & Class init */
503 /* =========================================== */
506 gst_osx_video_sink_set_property (GObject * object, guint prop_id,
507 const GValue * value, GParamSpec * pspec)
509 GstOSXVideoSink *osxvideosink;
511 g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
513 osxvideosink = GST_OSX_VIDEO_SINK (object);
517 osxvideosink->embed = g_value_get_boolean (value);
520 osxvideosink->fullscreen = g_value_get_boolean (value);
523 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
529 gst_osx_video_sink_get_property (GObject * object, guint prop_id,
530 GValue * value, GParamSpec * pspec)
532 GstOSXVideoSink *osxvideosink;
534 g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
536 osxvideosink = GST_OSX_VIDEO_SINK (object);
540 g_value_set_boolean (value, osxvideosink->embed);
543 g_value_set_boolean (value, osxvideosink->fullscreen);
546 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
552 gst_osx_video_sink_init (GstOSXVideoSink * osxvideosink)
555 osxvideosink->osxwindow = NULL;
557 osxvideosink->pixel_width = osxvideosink->pixel_height = 1;
558 osxvideosink->sw_scaling_failed = FALSE;
559 osxvideosink->embed = FALSE;
560 osxvideosink->fullscreen = FALSE;
565 gst_osx_video_sink_base_init (gpointer g_class)
567 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
569 gst_element_class_set_details (element_class, &gst_osx_video_sink_details);
571 gst_element_class_add_pad_template (element_class,
572 gst_static_pad_template_get (&gst_osx_video_sink_sink_template_factory));
576 gst_osx_video_sink_class_init (GstOSXVideoSinkClass * klass)
578 GObjectClass *gobject_class;
579 GstElementClass *gstelement_class;
580 GstBaseSinkClass *gstbasesink_class;
582 gobject_class = (GObjectClass *) klass;
583 gstelement_class = (GstElementClass *) klass;
584 gstbasesink_class = (GstBaseSinkClass *) klass;
587 parent_class = g_type_class_ref (GST_TYPE_VIDEO_SINK);
589 gobject_class->set_property = gst_osx_video_sink_set_property;
590 gobject_class->get_property = gst_osx_video_sink_get_property;
592 gstbasesink_class->set_caps = gst_osx_video_sink_setcaps;
593 gstbasesink_class->preroll = gst_osx_video_sink_show_frame;
594 gstbasesink_class->render = gst_osx_video_sink_show_frame;
595 gstelement_class->change_state = gst_osx_video_sink_change_state;
598 * GstOSXVideoSink:embed
600 * Set to #TRUE if you are embedding the video window in an application.
604 g_object_class_install_property (gobject_class, ARG_EMBED,
605 g_param_spec_boolean ("embed", "embed", "When enabled, it "
606 "can be embedded", FALSE, G_PARAM_READWRITE));
609 * GstOSXVideoSink:fullscreen
611 * Set to #TRUE to have the video displayed in fullscreen.
614 g_object_class_install_property (gobject_class, ARG_FULLSCREEN,
615 g_param_spec_boolean ("fullscreen", "fullscreen",
616 "When enabled, the view " "is fullscreen", FALSE,
620 /* ============================================================= */
624 /* ============================================================= */
626 /* =========================================== */
628 /* Object typing & Creation */
630 /* =========================================== */
633 gst_osx_video_sink_get_type (void)
635 static GType osxvideosink_type = 0;
637 if (!osxvideosink_type) {
638 static const GTypeInfo osxvideosink_info = {
639 sizeof (GstOSXVideoSinkClass),
640 gst_osx_video_sink_base_init,
642 (GClassInitFunc) gst_osx_video_sink_class_init,
645 sizeof (GstOSXVideoSink),
647 (GInstanceInitFunc) gst_osx_video_sink_init,
650 osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
651 "GstOSXVideoSink", &osxvideosink_info, 0);
655 return osxvideosink_type;
659 plugin_init (GstPlugin * plugin)
662 if (!gst_element_register (plugin, "osxvideosink",
663 GST_RANK_PRIMARY, GST_TYPE_OSX_VIDEO_SINK))
666 GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0,
667 "osxvideosink element");
672 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
675 "OSX native video output plugin",
676 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)