| /* GStreamer |
| * OSX video sink |
| * Copyright (C) 2004-6 Zaheer Abbas Merali <zaheerabbas at merali dot org> |
| * Copyright (C) 2007 Pioneers of the Inevitable <songbird@songbirdnest.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| * |
| * The development of this code was made possible due to the involvement of |
| * Pioneers of the Inevitable, the creators of the Songbird Music player. |
| * |
| */ |
| |
| #include "config.h" |
| |
| /* Object header */ |
| #include "osxvideosink.h" |
| #include <unistd.h> |
| #import "cocoawindow.h" |
| |
| /* Debugging category */ |
| GST_DEBUG_CATEGORY (gst_debug_osx_video_sink); |
| #define GST_CAT_DEFAULT gst_debug_osx_video_sink |
| |
| /* ElementFactory information */ |
| static const GstElementDetails gst_osx_video_sink_details = |
| GST_ELEMENT_DETAILS ("OSX Video sink", |
| "Sink/Video", |
| "OSX native videosink", |
| "Zaheer Abbas Merali <zaheerabbas at merali dot org>"); |
| |
| /* Default template - initiated with class struct to allow gst-register to work |
| without X running */ |
| static GstStaticPadTemplate gst_osx_video_sink_sink_template_factory = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/x-raw-yuv, " |
| "framerate = (fraction) [ 0, MAX ], " |
| "width = (int) [ 1, MAX ], " |
| "height = (int) [ 1, MAX ], " |
| #ifdef G_BYTE_ORDER == G_BIG_ENDIAN |
| "format = (fourcc) YUY2") |
| #else |
| "format = (fourcc) UYVY") |
| #endif |
| ); |
| |
| // much of the following cocoa NSApp code comes from libsdl and libcaca |
| @implementation NSApplication(Gst) |
| - (void)setRunning |
| { |
| _running = 1; |
| } |
| @end |
| |
| @implementation GstAppDelegate : NSObject |
| - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender |
| { |
| // destroy stuff here! |
| GST_DEBUG("Kill me please!"); |
| return NSTerminateNow; |
| } |
| @end |
| |
| |
| enum |
| { |
| ARG_0, |
| ARG_EMBED, |
| ARG_FULLSCREEN |
| /* FILL ME */ |
| }; |
| |
| static GstVideoSinkClass *parent_class = NULL; |
| |
| |
| /* cocoa event loop - needed if not run in own app */ |
| gint |
| cocoa_event_loop (GstOSXVideoSink * vsink) |
| { |
| NSAutoreleasePool *pool; |
| |
| pool = [[NSAutoreleasePool alloc] init]; |
| |
| if ([NSApp isRunning]) { |
| NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask |
| untilDate:[NSDate distantPast] |
| inMode:NSDefaultRunLoopMode dequeue:YES ]; |
| if ( event != nil ) { |
| switch ([event type]) { |
| default: //XXX Feed me please |
| [NSApp sendEvent:event]; |
| break; |
| } |
| } |
| } |
| |
| [pool release]; |
| |
| return TRUE; |
| } |
| |
| static NSString * |
| GetApplicationName(void) |
| { |
| NSDictionary *dict; |
| NSString *appName = 0; |
| |
| /* Determine the application name */ |
| dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); |
| if (dict) |
| appName = [dict objectForKey: @"CFBundleName"]; |
| |
| if (![appName length]) |
| appName = [[NSProcessInfo processInfo] processName]; |
| |
| return appName; |
| } |
| |
| static void |
| CreateApplicationMenus(void) |
| { |
| NSString *appName; |
| NSString *title; |
| NSMenu *appleMenu; |
| NSMenu *windowMenu; |
| NSMenuItem *menuItem; |
| |
| /* Create the main menu bar */ |
| [NSApp setMainMenu:[[NSMenu alloc] init]]; |
| |
| /* Create the application menu */ |
| appName = GetApplicationName(); |
| appleMenu = [[NSMenu alloc] initWithTitle:@""]; |
| |
| /* Add menu items */ |
| title = [@"About " stringByAppendingString:appName]; |
| [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; |
| |
| [appleMenu addItem:[NSMenuItem separatorItem]]; |
| |
| title = [@"Hide " stringByAppendingString:appName]; |
| [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@/*"h"*/""]; |
| |
| menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@/*"h"*/""]; |
| [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; |
| |
| [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; |
| |
| [appleMenu addItem:[NSMenuItem separatorItem]]; |
| |
| title = [@"Quit " stringByAppendingString:appName]; |
| [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@/*"q"*/""]; |
| |
| /* Put menu into the menubar */ |
| menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; |
| [menuItem setSubmenu:appleMenu]; |
| [[NSApp mainMenu] addItem:menuItem]; |
| [menuItem release]; |
| |
| /* Tell the application object that this is now the application menu */ |
| [NSApp setAppleMenu:appleMenu]; |
| [appleMenu release]; |
| |
| |
| /* Create the window menu */ |
| windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; |
| |
| /* "Minimize" item */ |
| menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@/*"m"*/""]; |
| [windowMenu addItem:menuItem]; |
| [menuItem release]; |
| |
| /* Put menu into the menubar */ |
| menuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; |
| [menuItem setSubmenu:windowMenu]; |
| [[NSApp mainMenu] addItem:menuItem]; |
| [menuItem release]; |
| |
| /* Tell the application object that this is now the window menu */ |
| [NSApp setWindowsMenu:windowMenu]; |
| [windowMenu release]; |
| } |
| |
| /* This function handles osx window creation */ |
| static GstOSXWindow * |
| gst_osx_video_sink_osxwindow_new (GstOSXVideoSink * osxvideosink, gint width, |
| gint height) |
| { |
| NSRect rect; |
| |
| GstOSXWindow *osxwindow = NULL; |
| |
| g_return_val_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink), NULL); |
| |
| osxwindow = g_new0 (GstOSXWindow, 1); |
| |
| osxwindow->width = width; |
| osxwindow->height = height; |
| osxwindow->internal = TRUE; |
| osxwindow->pool = [[NSAutoreleasePool alloc] init]; |
| |
| if (osxvideosink->embed == FALSE) { |
| ProcessSerialNumber psn; |
| unsigned int mask = NSTitledWindowMask | |
| NSClosableWindowMask | |
| NSResizableWindowMask | |
| NSTexturedBackgroundWindowMask | |
| NSMiniaturizableWindowMask; |
| |
| rect.origin.x = 100.0; |
| rect.origin.y = 100.0; |
| rect.size.width = (float) osxwindow->width; |
| rect.size.height = (float) osxwindow->height; |
| |
| if (!GetCurrentProcess(&psn)) { |
| TransformProcessType(&psn, kProcessTransformToForegroundApplication); |
| SetFrontProcess(&psn); |
| } |
| |
| [NSApplication sharedApplication]; |
| |
| osxwindow->win =[[GstOSXVideoSinkWindow alloc] |
| initWithContentRect: rect |
| styleMask: mask |
| backing: NSBackingStoreBuffered |
| defer: NO |
| screen: nil]; |
| [osxwindow->win autorelease]; |
| [NSApplication sharedApplication]; |
| [osxwindow->win makeKeyAndOrderFront:NSApp]; |
| osxwindow->gstview =[osxwindow->win gstView]; |
| [osxwindow->gstview autorelease]; |
| if (osxvideosink->fullscreen) |
| [osxwindow->gstview setFullScreen:YES]; |
| |
| CreateApplicationMenus(); |
| |
| [NSApp finishLaunching]; |
| [NSApp setDelegate:[[GstAppDelegate alloc] init]]; |
| |
| [NSApp setRunning]; |
| // insert event dispatch in the glib main loop |
| g_idle_add ((GSourceFunc) cocoa_event_loop, osxvideosink); |
| } else { |
| GstStructure *s; |
| GstMessage *msg; |
| gchar * tmp; |
| /* Needs to be embedded */ |
| |
| rect.origin.x = 0.0; |
| rect.origin.y = 0.0; |
| rect.size.width = (float) osxwindow->width; |
| rect.size.height = (float) osxwindow->height; |
| osxwindow->gstview =[[GstGLView alloc] initWithFrame:rect]; |
| [osxwindow->gstview autorelease]; |
| |
| s = gst_structure_new ("have-ns-view", |
| "nsview", G_TYPE_POINTER, osxwindow->gstview, |
| nil); |
| |
| tmp = gst_structure_to_string (s); |
| GST_DEBUG_OBJECT (osxvideosink, "Sending message %s", |
| tmp); |
| g_free (tmp); |
| |
| msg = gst_message_new_element (GST_OBJECT (osxvideosink), s); |
| gst_element_post_message (GST_ELEMENT (osxvideosink), msg); |
| |
| GST_LOG_OBJECT (osxvideosink, "'have-ns-view' message sent"); |
| } |
| return osxwindow; |
| } |
| |
| /* This function destroys a GstXWindow */ |
| static void |
| gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink, |
| GstOSXWindow * osxwindow) |
| { |
| g_return_if_fail (osxwindow != NULL); |
| g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink)); |
| |
| [osxwindow->pool release]; |
| |
| g_free (osxwindow); |
| } |
| |
| /* This function resizes a GstXWindow */ |
| static void |
| gst_osx_video_sink_osxwindow_resize (GstOSXVideoSink * osxvideosink, |
| GstOSXWindow * osxwindow, guint width, guint height) |
| { |
| NSSize size; |
| NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init]; |
| g_return_if_fail (osxwindow != NULL); |
| g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink)); |
| |
| osxwindow->width = width; |
| osxwindow->height = height; |
| |
| size.width = width; |
| size.height = height; |
| /* Call relevant cocoa function to resize window */ |
| [osxwindow->win setContentSize:size]; |
| [subPool release]; |
| } |
| |
| static void |
| gst_osx_video_sink_osxwindow_clear (GstOSXVideoSink * osxvideosink, |
| GstOSXWindow * osxwindow) |
| { |
| |
| g_return_if_fail (osxwindow != NULL); |
| g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink)); |
| |
| } |
| |
| |
| /* Element stuff */ |
| static gboolean |
| gst_osx_video_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) |
| { |
| GstOSXVideoSink *osxvideosink; |
| GstStructure *structure; |
| gboolean res, result = FALSE; |
| gint video_width, video_height; |
| const GValue *framerate; |
| |
| osxvideosink = GST_OSX_VIDEO_SINK (bsink); |
| |
| GST_DEBUG_OBJECT (osxvideosink, "caps: %" GST_PTR_FORMAT, caps); |
| |
| structure = gst_caps_get_structure (caps, 0); |
| res = gst_structure_get_int (structure, "width", &video_width); |
| res &= gst_structure_get_int (structure, "height", &video_height); |
| framerate = gst_structure_get_value (structure, "framerate"); |
| res &= (framerate != NULL); |
| |
| if (!res) { |
| goto beach; |
| } |
| |
| osxvideosink->fps_n = gst_value_get_fraction_numerator (framerate); |
| osxvideosink->fps_d = gst_value_get_fraction_denominator (framerate); |
| |
| GST_DEBUG_OBJECT (osxvideosink, "our format is: %dx%d video at %d/%d fps", |
| video_width, video_height, osxvideosink->fps_n, osxvideosink->fps_d); |
| |
| GST_VIDEO_SINK_WIDTH (osxvideosink) = video_width; |
| GST_VIDEO_SINK_HEIGHT (osxvideosink) = video_height; |
| |
| gst_osx_video_sink_osxwindow_resize (osxvideosink, osxvideosink->osxwindow, |
| video_width, video_height); |
| result = TRUE; |
| |
| beach: |
| return result; |
| |
| } |
| |
| static GstStateChangeReturn |
| gst_osx_video_sink_change_state (GstElement * element, |
| GstStateChange transition) |
| { |
| GstOSXVideoSink *osxvideosink; |
| |
| osxvideosink = GST_OSX_VIDEO_SINK (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| /* Creating our window and our image */ |
| if (!osxvideosink->osxwindow) { |
| GST_VIDEO_SINK_WIDTH (osxvideosink) = 320; |
| GST_VIDEO_SINK_HEIGHT (osxvideosink) = 240; |
| osxvideosink->osxwindow = |
| gst_osx_video_sink_osxwindow_new (osxvideosink, |
| GST_VIDEO_SINK_WIDTH (osxvideosink), |
| GST_VIDEO_SINK_HEIGHT (osxvideosink)); |
| gst_osx_video_sink_osxwindow_clear (osxvideosink, |
| osxvideosink->osxwindow); |
| } else { |
| if (osxvideosink->osxwindow->internal) |
| gst_osx_video_sink_osxwindow_resize (osxvideosink, |
| osxvideosink->osxwindow, GST_VIDEO_SINK_WIDTH (osxvideosink), |
| GST_VIDEO_SINK_HEIGHT (osxvideosink)); |
| } |
| break; |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| GST_DEBUG ("ready to paused"); |
| if (osxvideosink->osxwindow) |
| gst_osx_video_sink_osxwindow_clear (osxvideosink, |
| osxvideosink->osxwindow); |
| osxvideosink->time = 0; |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| break; |
| case GST_STATE_CHANGE_PLAYING_TO_PAUSED: |
| break; |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| osxvideosink->fps_n = 0; |
| osxvideosink->fps_d = 0; |
| osxvideosink->sw_scaling_failed = FALSE; |
| GST_VIDEO_SINK_WIDTH (osxvideosink) = 0; |
| GST_VIDEO_SINK_HEIGHT (osxvideosink) = 0; |
| break; |
| case GST_STATE_CHANGE_READY_TO_NULL: |
| |
| if (osxvideosink->osxwindow) { |
| gst_osx_video_sink_osxwindow_destroy (osxvideosink, |
| osxvideosink->osxwindow); |
| osxvideosink->osxwindow = NULL; |
| } |
| break; |
| } |
| |
| return (GST_ELEMENT_CLASS (parent_class))->change_state (element, transition); |
| |
| } |
| |
| static GstFlowReturn |
| gst_osx_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf) |
| { |
| GstOSXVideoSink *osxvideosink; |
| |
| osxvideosink = GST_OSX_VIDEO_SINK (bsink); |
| |
| char *viewdata =[osxvideosink->osxwindow->gstview getTextureBuffer]; |
| |
| GST_DEBUG ("show_frame"); |
| memcpy (viewdata, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); |
| [osxvideosink->osxwindow->gstview displayTexture]; |
| |
| return GST_FLOW_OK; |
| } |
| |
| /* Buffer management */ |
| |
| |
| |
| /* =========================================== */ |
| /* */ |
| /* Init & Class init */ |
| /* */ |
| /* =========================================== */ |
| |
| static void |
| gst_osx_video_sink_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstOSXVideoSink *osxvideosink; |
| |
| g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object)); |
| |
| osxvideosink = GST_OSX_VIDEO_SINK (object); |
| |
| switch (prop_id) { |
| case ARG_EMBED: |
| osxvideosink->embed = g_value_get_boolean (value); |
| break; |
| case ARG_FULLSCREEN: |
| osxvideosink->fullscreen = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_osx_video_sink_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstOSXVideoSink *osxvideosink; |
| |
| g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object)); |
| |
| osxvideosink = GST_OSX_VIDEO_SINK (object); |
| |
| switch (prop_id) { |
| case ARG_EMBED: |
| g_value_set_boolean (value, osxvideosink->embed); |
| break; |
| case ARG_FULLSCREEN: |
| g_value_set_boolean (value, osxvideosink->fullscreen); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_osx_video_sink_init (GstOSXVideoSink * osxvideosink) |
| { |
| |
| osxvideosink->osxwindow = NULL; |
| |
| osxvideosink->fps_n = 0; |
| osxvideosink->fps_d = 0; |
| |
| osxvideosink->pixel_width = osxvideosink->pixel_height = 1; |
| osxvideosink->sw_scaling_failed = FALSE; |
| osxvideosink->embed = FALSE; |
| osxvideosink->fullscreen = FALSE; |
| |
| } |
| |
| static void |
| gst_osx_video_sink_base_init (gpointer g_class) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
| |
| gst_element_class_set_details (element_class, &gst_osx_video_sink_details); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&gst_osx_video_sink_sink_template_factory)); |
| } |
| |
| static void |
| gst_osx_video_sink_class_init (GstOSXVideoSinkClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| GstBaseSinkClass *gstbasesink_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstelement_class = (GstElementClass *) klass; |
| gstbasesink_class = (GstBaseSinkClass *) klass; |
| |
| |
| parent_class = g_type_class_ref (GST_TYPE_VIDEO_SINK); |
| |
| gobject_class->set_property = gst_osx_video_sink_set_property; |
| gobject_class->get_property = gst_osx_video_sink_get_property; |
| |
| gstbasesink_class->set_caps = gst_osx_video_sink_setcaps; |
| gstbasesink_class->preroll = gst_osx_video_sink_show_frame; |
| gstbasesink_class->render = gst_osx_video_sink_show_frame; |
| gstelement_class->change_state = gst_osx_video_sink_change_state; |
| |
| g_object_class_install_property (gobject_class, ARG_EMBED, |
| g_param_spec_boolean ("embed", "embed", "When enabled, it " |
| "can be embedded", FALSE, G_PARAM_READWRITE)); |
| g_object_class_install_property (gobject_class, ARG_FULLSCREEN, |
| g_param_spec_boolean ("fullscreen", "fullscreen", |
| "When enabled, the view " "is fullscreen", FALSE, |
| G_PARAM_READWRITE)); |
| } |
| |
| /* ============================================================= */ |
| /* */ |
| /* Public Methods */ |
| /* */ |
| /* ============================================================= */ |
| |
| /* =========================================== */ |
| /* */ |
| /* Object typing & Creation */ |
| /* */ |
| /* =========================================== */ |
| |
| GType |
| gst_osx_video_sink_get_type (void) |
| { |
| static GType osxvideosink_type = 0; |
| |
| if (!osxvideosink_type) { |
| static const GTypeInfo osxvideosink_info = { |
| sizeof (GstOSXVideoSinkClass), |
| gst_osx_video_sink_base_init, |
| NULL, |
| (GClassInitFunc) gst_osx_video_sink_class_init, |
| NULL, |
| NULL, |
| sizeof (GstOSXVideoSink), |
| 0, |
| (GInstanceInitFunc) gst_osx_video_sink_init, |
| }; |
| |
| osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK, |
| "GstOSXVideoSink", &osxvideosink_info, 0); |
| |
| } |
| |
| return osxvideosink_type; |
| } |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| |
| if (!gst_element_register (plugin, "osxvideosink", |
| GST_RANK_PRIMARY, GST_TYPE_OSX_VIDEO_SINK)) |
| return FALSE; |
| |
| GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0, |
| "osxvideosink element"); |
| |
| return TRUE; |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| "osxvideo", |
| "OSX native video output plugin", |
| plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |