Tizen 2.0 Release
[framework/multimedia/gst-plugins-good0.10.git] / sys / osxvideo / osxvideosink.m
1 /* GStreamer
2  * OSX video sink
3  * Copyright (C) 2004-6 Zaheer Abbas Merali <zaheerabbas at merali dot org>
4  * Copyright (C) 2007,2008,2009 Pioneers of the Inevitable <songbird@songbirdnest.com>
5  * 
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.
10  *
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.
15  *
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.
20  *
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.
23  * 
24  */
25
26 /**
27  * SECTION:element-osxvideosink
28  *
29  * The OSXVideoSink renders video frames to a MacOSX window. The video output
30  * must be directed to a window embedded in an existing NSApp.
31  *
32  * When the NSView to be embedded is created an element #GstMessage with a 
33  * name of 'have-ns-view' will be created and posted on the bus. 
34  * The pointer to the NSView to embed will be in the 'nsview' field of that 
35  * message. The application MUST handle this message and embed the view
36  * appropriately.
37  */
38
39 #include "config.h"
40 #include <gst/interfaces/xoverlay.h>
41
42 #include "osxvideosink.h"
43 #include <unistd.h>
44 #import "cocoawindow.h"
45
46 GST_DEBUG_CATEGORY (gst_debug_osx_video_sink);
47 #define GST_CAT_DEFAULT gst_debug_osx_video_sink
48
49 static GstStaticPadTemplate gst_osx_video_sink_sink_template_factory =
50 GST_STATIC_PAD_TEMPLATE ("sink",
51     GST_PAD_SINK,
52     GST_PAD_ALWAYS,
53     GST_STATIC_CAPS ("video/x-raw-yuv, "
54         "framerate = (fraction) [ 0, MAX ], "
55         "width = (int) [ 1, MAX ], "
56         "height = (int) [ 1, MAX ], "
57 #if G_BYTE_ORDER == G_BIG_ENDIAN
58        "format = (fourcc) YUY2")
59 #else
60         "format = (fourcc) UYVY")
61 #endif
62     );
63
64 enum
65 {
66   ARG_0,
67   ARG_EMBED,
68 };
69
70 static void gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink);
71
72 static GstVideoSinkClass *parent_class = NULL;
73
74 /* This function handles osx window creation */
75 static gboolean
76 gst_osx_video_sink_osxwindow_create (GstOSXVideoSink * osxvideosink, gint width,
77     gint height)
78 {
79   NSRect rect;
80   GstOSXWindow *osxwindow = NULL;
81   GstStructure *s;
82   GstMessage *msg;
83   gboolean res = TRUE;
84   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
85
86   g_return_val_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink), FALSE);
87
88   GST_DEBUG_OBJECT (osxvideosink, "Creating new OSX window");
89
90   osxvideosink->osxwindow = osxwindow = g_new0 (GstOSXWindow, 1);
91
92   osxwindow->width = width;
93   osxwindow->height = height;
94
95   /* Allocate our GstGLView for the window, and then tell the application
96    * about it (hopefully it's listening...) */
97   rect.origin.x = 0.0;
98   rect.origin.y = 0.0;
99   rect.size.width = (float) osxwindow->width;
100   rect.size.height = (float) osxwindow->height;
101   osxwindow->gstview =[[GstGLView alloc] initWithFrame:rect];
102
103   s = gst_structure_new ("have-ns-view",
104      "nsview", G_TYPE_POINTER, osxwindow->gstview,
105      nil);
106
107   msg = gst_message_new_element (GST_OBJECT (osxvideosink), s);
108   gst_element_post_message (GST_ELEMENT (osxvideosink), msg);
109
110   GST_INFO_OBJECT (osxvideosink, "'have-ns-view' message sent");
111
112   /* check if have-ns-view was handled and osxwindow->gstview was added to a
113    * superview
114    */
115   if ([osxwindow->gstview haveSuperview] == NO) {
116     /* have-ns-view wasn't handled, post prepare-xwindow-id */
117     if (osxvideosink->superview == NULL) {
118       GST_INFO_OBJECT (osxvideosink, "emitting prepare-xwindow-id");
119       gst_x_overlay_prepare_xwindow_id (GST_X_OVERLAY (osxvideosink));
120     }
121
122     if (osxvideosink->superview != NULL) {
123       /* prepare-xwindow-id was handled, we have the superview in
124        * osxvideosink->superview. We now add osxwindow->gstview to the superview
125        * from the main thread
126        */
127       GST_INFO_OBJECT (osxvideosink, "we have a superview, adding our view to it");
128       [osxwindow->gstview performSelectorOnMainThread:@selector(addToSuperview:)
129           withObject:osxvideosink->superview waitUntilDone:YES];
130     } else {
131       /* the view wasn't added to a superview. It's possible that the
132        * application handled have-ns-view, stored our view internally and is
133        * going to add it to a superview later (webkit does that now).
134        */
135       GST_INFO_OBJECT (osxvideosink, "no superview");
136     }
137   }
138
139   [pool release];
140
141   return res;
142 }
143
144 static void
145 gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink)
146 {
147   NSAutoreleasePool *pool;
148
149   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
150   pool = [[NSAutoreleasePool alloc] init];
151
152   if (osxvideosink->osxwindow) {
153     if (osxvideosink->superview) {
154       [osxvideosink->osxwindow->gstview
155           performSelectorOnMainThread:@selector(removeFromSuperview:)
156             withObject:(id)nil waitUntilDone:YES];
157     }
158     [osxvideosink->osxwindow->gstview release];
159
160     g_free (osxvideosink->osxwindow);
161     osxvideosink->osxwindow = NULL;
162   }
163   [pool release];
164 }
165
166 /* This function resizes a GstXWindow */
167 static void
168 gst_osx_video_sink_osxwindow_resize (GstOSXVideoSink * osxvideosink,
169     GstOSXWindow * osxwindow, guint width, guint height)
170 {
171   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
172   g_return_if_fail (osxwindow != NULL);
173   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
174
175   osxwindow->width = width;
176   osxwindow->height = height;
177
178   GST_DEBUG_OBJECT (osxvideosink, "Resizing window to (%d,%d)", width, height);
179
180   /* Directly resize the underlying view */
181   GST_DEBUG_OBJECT (osxvideosink, "Calling setVideoSize on %p", osxwindow->gstview); 
182   [osxwindow->gstview setVideoSize:width :height];
183
184   [pool release];
185 }
186
187 static gboolean
188 gst_osx_video_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
189 {
190   GstOSXVideoSink *osxvideosink;
191   GstStructure *structure;
192   gboolean res, result = FALSE;
193   gint video_width, video_height;
194
195   osxvideosink = GST_OSX_VIDEO_SINK (bsink);
196
197   GST_DEBUG_OBJECT (osxvideosink, "caps: %" GST_PTR_FORMAT, caps);
198
199   structure = gst_caps_get_structure (caps, 0);
200   res = gst_structure_get_int (structure, "width", &video_width);
201   res &= gst_structure_get_int (structure, "height", &video_height);
202
203   if (!res) {
204     goto beach;
205   }
206
207   GST_DEBUG_OBJECT (osxvideosink, "our format is: %dx%d video",
208       video_width, video_height);
209
210   GST_VIDEO_SINK_WIDTH (osxvideosink) = video_width;
211   GST_VIDEO_SINK_HEIGHT (osxvideosink) = video_height;
212
213   gst_osx_video_sink_osxwindow_resize (osxvideosink, osxvideosink->osxwindow,
214       video_width, video_height);
215   result = TRUE;
216
217 beach:
218   return result;
219
220 }
221
222 static GstStateChangeReturn
223 gst_osx_video_sink_change_state (GstElement * element,
224     GstStateChange transition)
225 {
226   GstOSXVideoSink *osxvideosink;
227   GstStateChangeReturn ret;
228
229   osxvideosink = GST_OSX_VIDEO_SINK (element);
230
231   GST_DEBUG_OBJECT (osxvideosink, "%s => %s", 
232         gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT (transition)),
233         gst_element_state_get_name(GST_STATE_TRANSITION_NEXT (transition)));
234
235   switch (transition) {
236     case GST_STATE_CHANGE_NULL_TO_READY:
237       break;
238     case GST_STATE_CHANGE_READY_TO_PAUSED:
239       /* Creating our window and our image */
240       GST_VIDEO_SINK_WIDTH (osxvideosink) = 320;
241       GST_VIDEO_SINK_HEIGHT (osxvideosink) = 240;
242       if (!gst_osx_video_sink_osxwindow_create (osxvideosink,
243           GST_VIDEO_SINK_WIDTH (osxvideosink),
244           GST_VIDEO_SINK_HEIGHT (osxvideosink))) {
245         ret = GST_STATE_CHANGE_FAILURE;
246         goto done;
247       }
248       break;
249     default:
250       break;
251   }
252
253   ret = (GST_ELEMENT_CLASS (parent_class))->change_state (element, transition);
254
255   switch (transition) {
256     case GST_STATE_CHANGE_PAUSED_TO_READY:
257       GST_VIDEO_SINK_WIDTH (osxvideosink) = 0;
258       GST_VIDEO_SINK_HEIGHT (osxvideosink) = 0;
259       gst_osx_video_sink_osxwindow_destroy (osxvideosink);
260       break;
261     case GST_STATE_CHANGE_READY_TO_NULL:
262       break;
263     default:
264       break;
265   }
266
267 done:
268   return ret;
269 }
270
271 static GstFlowReturn
272 gst_osx_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
273 {
274   GstOSXVideoSink *osxvideosink;
275   guint8 *viewdata;
276   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
277
278   osxvideosink = GST_OSX_VIDEO_SINK (bsink);
279   viewdata = (guint8 *) [osxvideosink->osxwindow->gstview getTextureBuffer];
280
281   GST_DEBUG ("show_frame");
282   memcpy (viewdata, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
283   [osxvideosink->osxwindow->gstview displayTexture];
284
285   [pool release];
286
287   return GST_FLOW_OK;
288 }
289
290 /* Buffer management */
291
292
293
294 /* =========================================== */
295 /*                                             */
296 /*              Init & Class init              */
297 /*                                             */
298 /* =========================================== */
299
300 static void
301 gst_osx_video_sink_set_property (GObject * object, guint prop_id,
302     const GValue * value, GParamSpec * pspec)
303 {
304   GstOSXVideoSink *osxvideosink;
305
306   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
307
308   osxvideosink = GST_OSX_VIDEO_SINK (object);
309
310   switch (prop_id) {
311     case ARG_EMBED:
312       /* Ignore, just here for backwards compatibility */
313       break;
314     default:
315       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
316       break;
317   }
318 }
319
320 static void
321 gst_osx_video_sink_get_property (GObject * object, guint prop_id,
322     GValue * value, GParamSpec * pspec)
323 {
324   GstOSXVideoSink *osxvideosink;
325
326   g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
327
328   osxvideosink = GST_OSX_VIDEO_SINK (object);
329
330   switch (prop_id) {
331     case ARG_EMBED:
332       g_value_set_boolean (value, TRUE);
333       break;
334     default:
335       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
336       break;
337   }
338 }
339
340
341 static void
342 gst_osx_video_sink_init (GstOSXVideoSink * osxvideosink)
343 {
344   osxvideosink->osxwindow = NULL;
345   osxvideosink->superview = NULL;
346 }
347
348 static void
349 gst_osx_video_sink_base_init (gpointer g_class)
350 {
351   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
352
353   gst_element_class_set_details_simple (element_class, "OSX Video sink",
354       "Sink/Video", "OSX native videosink",
355       "Zaheer Abbas Merali <zaheerabbas at merali dot org>");
356
357   gst_element_class_add_static_pad_template (element_class,
358       &gst_osx_video_sink_sink_template_factory);
359 }
360
361 static void
362 gst_osx_video_sink_finalize (GObject *object)
363 {
364   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (object);
365
366   if (osxvideosink->superview)
367     [osxvideosink->superview release];
368
369   G_OBJECT_CLASS (parent_class)->finalize (object);
370 }
371
372 static void
373 gst_osx_video_sink_class_init (GstOSXVideoSinkClass * klass)
374 {
375   GObjectClass *gobject_class;
376   GstElementClass *gstelement_class;
377   GstBaseSinkClass *gstbasesink_class;
378
379   gobject_class = (GObjectClass *) klass;
380   gstelement_class = (GstElementClass *) klass;
381   gstbasesink_class = (GstBaseSinkClass *) klass;
382
383
384   parent_class = g_type_class_ref (GST_TYPE_VIDEO_SINK);
385
386   gobject_class->set_property = gst_osx_video_sink_set_property;
387   gobject_class->get_property = gst_osx_video_sink_get_property;
388   gobject_class->finalize = gst_osx_video_sink_finalize;
389
390   gstbasesink_class->set_caps = gst_osx_video_sink_setcaps;
391   gstbasesink_class->preroll = gst_osx_video_sink_show_frame;
392   gstbasesink_class->render = gst_osx_video_sink_show_frame;
393   gstelement_class->change_state = gst_osx_video_sink_change_state;
394
395   /**
396    * GstOSXVideoSink:embed
397    *
398    * Set to #TRUE if you are embedding the video window in an application.
399    *
400    **/
401
402   g_object_class_install_property (gobject_class, ARG_EMBED,
403       g_param_spec_boolean ("embed", "embed", "For ABI compatiblity only, do not use",
404           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
405 }
406
407 static gboolean
408 gst_osx_video_sink_interface_supported (GstImplementsInterface * iface, GType type)
409 {
410   g_assert (type == GST_TYPE_X_OVERLAY);
411   return TRUE;
412 }
413
414 static void
415 gst_osx_video_sink_interface_init (GstImplementsInterfaceClass * klass)
416 {
417   klass->supported = gst_osx_video_sink_interface_supported;
418 }
419
420 static void
421 gst_osx_video_sink_set_window_handle (GstXOverlay * overlay, guintptr handle_id)
422 {
423   GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (overlay);
424   gulong window_id = (gulong) handle_id;
425
426   if (osxvideosink->superview) {
427     GST_INFO_OBJECT (osxvideosink, "old xwindow id %p", osxvideosink->superview);
428     if (osxvideosink->osxwindow) {
429       [osxvideosink->osxwindow->gstview
430           performSelectorOnMainThread:@selector(removeFromSuperview:)
431             withObject:(id)nil waitUntilDone:YES];
432     }
433     [osxvideosink->superview release];
434   }
435
436   GST_INFO_OBJECT (osxvideosink, "set xwindow id 0x%lx", window_id);
437   osxvideosink->superview = [((NSView *) window_id) retain];
438   if (osxvideosink->osxwindow) {
439       [osxvideosink->osxwindow->gstview performSelectorOnMainThread:@selector(addToSuperview:)
440           withObject:osxvideosink->superview waitUntilDone:YES];
441   }
442 }
443
444 static void
445 gst_osx_video_sink_xoverlay_init (GstXOverlayClass * iface)
446 {
447   iface->set_window_handle = gst_osx_video_sink_set_window_handle;
448   iface->expose = NULL;
449   iface->handle_events = NULL;
450 }
451
452 /* ============================================================= */
453 /*                                                               */
454 /*                       Public Methods                          */
455 /*                                                               */
456 /* ============================================================= */
457
458 /* =========================================== */
459 /*                                             */
460 /*          Object typing & Creation           */
461 /*                                             */
462 /* =========================================== */
463
464 GType
465 gst_osx_video_sink_get_type (void)
466 {
467   static GType osxvideosink_type = 0;
468
469   if (!osxvideosink_type) {
470     static const GTypeInfo osxvideosink_info = {
471       sizeof (GstOSXVideoSinkClass),
472       gst_osx_video_sink_base_init,
473       NULL,
474       (GClassInitFunc) gst_osx_video_sink_class_init,
475       NULL,
476       NULL,
477       sizeof (GstOSXVideoSink),
478       0,
479       (GInstanceInitFunc) gst_osx_video_sink_init,
480     };
481
482     static const GInterfaceInfo iface_info = {
483       (GInterfaceInitFunc) gst_osx_video_sink_interface_init,
484       NULL,
485       NULL,
486     };
487
488     static const GInterfaceInfo overlay_info = {
489       (GInterfaceInitFunc) gst_osx_video_sink_xoverlay_init,
490       NULL,
491       NULL,
492     };
493
494     osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
495         "GstOSXVideoSink", &osxvideosink_info, 0);
496
497     g_type_add_interface_static (osxvideosink_type,
498         GST_TYPE_IMPLEMENTS_INTERFACE, &iface_info);
499     g_type_add_interface_static (osxvideosink_type, GST_TYPE_X_OVERLAY,
500         &overlay_info);
501   }
502
503   return osxvideosink_type;
504 }
505
506 static gboolean
507 plugin_init (GstPlugin * plugin)
508 {
509
510   if (!gst_element_register (plugin, "osxvideosink",
511           GST_RANK_PRIMARY, GST_TYPE_OSX_VIDEO_SINK))
512     return FALSE;
513
514   GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0,
515       "osxvideosink element");
516
517   return TRUE;
518 }
519
520 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
521     GST_VERSION_MINOR,
522     "osxvideo",
523     "OSX native video output plugin",
524     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)