• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, 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 */
33
34#include "config.h"
35#include <gst/video/videooverlay.h>
36#include <gst/video/navigation.h>
37#include <gst/video/video.h>
38
39#include "osxvideosink.h"
40#include <unistd.h>
41#import "cocoawindow.h"
42
43GST_DEBUG_CATEGORY (gst_debug_osx_video_sink);
44#define GST_CAT_DEFAULT gst_debug_osx_video_sink
45
46#ifndef GSTREAMER_GLIB_COCOA_NSAPPLICATION
47#include <pthread.h>
48extern void _CFRunLoopSetCurrent (CFRunLoopRef rl);
49extern pthread_t _CFMainPThread;
50#endif
51
52static GstStaticPadTemplate gst_osx_video_sink_sink_template_factory =
53GST_STATIC_PAD_TEMPLATE ("sink",
54    GST_PAD_SINK,
55    GST_PAD_ALWAYS,
56    GST_STATIC_CAPS ("video/x-raw, "
57        "framerate = (fraction) [ 0, MAX ], "
58        "width = (int) [ 1, MAX ], "
59        "height = (int) [ 1, MAX ], "
60#if G_BYTE_ORDER == G_BIG_ENDIAN
61       "format = (string) YUY2")
62#else
63        "format = (string) UYVY")
64#endif
65    );
66
67enum
68{
69  ARG_0,
70  ARG_EMBED,
71  ARG_FORCE_PAR,
72};
73
74static void gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink);
75
76#ifndef GSTREAMER_GLIB_COCOA_NSAPPLICATION
77static GMutex _run_loop_check_mutex;
78static GMutex _run_loop_mutex;
79static GCond _run_loop_cond;
80#endif
81
82static GstOSXVideoSinkClass *sink_class = NULL;
83static GstVideoSinkClass *parent_class = NULL;
84
85#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
86#define NSEventMaskAny                       NSAnyEventMask
87#define NSWindowStyleMaskTitled              NSTitledWindowMask
88#define NSWindowStyleMaskClosable            NSClosableWindowMask
89#define NSWindowStyleMaskResizable           NSResizableWindowMask
90#define NSWindowStyleMaskTexturedBackground  NSTexturedBackgroundWindowMask
91#define NSWindowStyleMaskMiniaturizable      NSMiniaturizableWindowMask
92#endif
93
94/* Helper to trigger calls from the main thread */
95static void
96gst_osx_video_sink_call_from_main_thread(GstOSXVideoSink *osxvideosink,
97    NSObject * object, SEL function, NSObject *data, BOOL waitUntilDone)
98{
99
100  NSThread *thread;
101  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
102
103  if (sink_class->ns_app_thread == NULL){
104    thread = [NSThread mainThread];
105  } else {
106    thread = sink_class->ns_app_thread;
107  }
108
109  [object performSelector:function onThread:thread
110          withObject:data waitUntilDone:waitUntilDone];
111  [pool release];
112}
113
114#ifndef GSTREAMER_GLIB_COCOA_NSAPPLICATION
115/* Poll for cocoa events */
116static void
117run_ns_app_loop (void) {
118  NSEvent *event;
119  NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
120  NSDate *pollTime = nil;
121
122  /* when running the loop in a thread we want to sleep as long as possible */
123  pollTime = [NSDate distantFuture];
124
125  do {
126      event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:pollTime
127          inMode:NSDefaultRunLoopMode dequeue:YES];
128      [NSApp sendEvent:event];
129    }
130  while (event != nil);
131  [pool release];
132}
133
134static void
135gst_osx_videosink_check_main_run_loop (GstOSXVideoSink *sink)
136{
137  /* check if the main run loop is running */
138  gboolean is_running;
139
140  /* the easy way */
141  is_running = [[NSRunLoop mainRunLoop] currentMode] != nil;
142  if (is_running) {
143    goto exit;
144  } else {
145    /* the previous check doesn't always work with main loops that run
146     * cocoa's main run loop manually, like the gdk one, giving false
147     * negatives. This check defers a call to the main thread and waits to
148     * be awaken by this function. */
149    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
150    GstOSXVideoSinkObject * object = (GstOSXVideoSinkObject *) sink->osxvideosinkobject;
151    gint64 abstime;
152
153    g_mutex_lock (&_run_loop_mutex);
154    [object performSelectorOnMainThread:
155          @selector(checkMainRunLoop)
156          withObject:nil waitUntilDone:NO];
157    /* Wait 100 ms */
158    abstime = g_get_monotonic_time () + 100 * 1000;
159    is_running = g_cond_wait_until (&_run_loop_cond,
160        &_run_loop_mutex, abstime);
161    g_mutex_unlock (&_run_loop_mutex);
162
163    [pool release];
164  }
165
166exit:
167  {
168  GST_DEBUG_OBJECT(sink, "The main runloop %s is running",
169      is_running ? "" : " not ");
170  if (is_running) {
171    sink_class->run_loop_state = GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_RUNNING;
172    sink_class->ns_app_thread = [NSThread mainThread];
173  } else {
174    sink_class->run_loop_state = GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_NOT_RUNNING;
175  }
176  }
177}
178
179static void
180gst_osx_video_sink_run_cocoa_loop (GstOSXVideoSink * sink )
181{
182  /* Cocoa applications require a main runloop running to dispatch UI
183   * events and process deferred calls to the main thread through
184   * perfermSelectorOnMainThread.
185   * Since the sink needs to create it's own Cocoa window when no
186   * external NSView is passed to the sink through the GstVideoOverlay API,
187   * we need to run the cocoa mainloop somehow.
188   * This run loop can only be started once, by the first sink needing it
189   */
190
191  g_mutex_lock (&_run_loop_check_mutex);
192
193  if (sink_class->run_loop_state == GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_UNKNOWN) {
194    gst_osx_videosink_check_main_run_loop (sink);
195  }
196
197  if (sink_class->run_loop_state == GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_RUNNING) {
198    g_mutex_unlock (&_run_loop_check_mutex);
199    return;
200  }
201
202  if (sink_class->ns_app_thread == NULL) {
203    /* run the main runloop in a separate thread */
204
205    /* override [NSThread isMainThread] with our own implementation so that we can
206     * make it believe our dedicated thread is the main thread
207     */
208    Method origIsMainThread = class_getClassMethod([NSThread class],
209        NSSelectorFromString(@"isMainThread"));
210    Method ourIsMainThread = class_getClassMethod([GstOSXVideoSinkObject class],
211        NSSelectorFromString(@"isMainThread"));
212
213    method_exchangeImplementations(origIsMainThread, ourIsMainThread);
214
215    sink_class->ns_app_thread = [[NSThread alloc]
216        initWithTarget:sink->osxvideosinkobject
217        selector:@selector(nsAppThread) object:nil];
218    [sink_class->ns_app_thread start];
219
220    g_mutex_lock (&_run_loop_mutex);
221    g_cond_wait (&_run_loop_cond, &_run_loop_mutex);
222    g_mutex_unlock (&_run_loop_mutex);
223  }
224
225  g_mutex_unlock (&_run_loop_check_mutex);
226}
227
228static void
229gst_osx_video_sink_stop_cocoa_loop (GstOSXVideoSink * osxvideosink)
230{
231}
232#endif
233
234/* This function handles osx window creation */
235static gboolean
236gst_osx_video_sink_osxwindow_create (GstOSXVideoSink * osxvideosink, gint width,
237    gint height)
238{
239  NSRect rect;
240  GstOSXWindow *osxwindow = NULL;
241  gboolean res = TRUE;
242  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
243
244  g_return_val_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink), FALSE);
245
246  GST_DEBUG_OBJECT (osxvideosink, "Creating new OSX window");
247
248  osxvideosink->osxwindow = osxwindow = g_new0 (GstOSXWindow, 1);
249
250  osxwindow->width = width;
251  osxwindow->height = height;
252  osxwindow->closed = FALSE;
253  osxwindow->internal = FALSE;
254
255  /* Allocate our GstGLView for the window, and then tell the application
256   * about it (hopefully it's listening...) */
257  rect.origin.x = 0.0;
258  rect.origin.y = 0.0;
259  rect.size.width = (float) osxwindow->width;
260  rect.size.height = (float) osxwindow->height;
261  osxwindow->gstview =[[GstGLView alloc] initWithFrame:rect];
262
263#ifndef GSTREAMER_GLIB_COCOA_NSAPPLICATION
264  gst_osx_video_sink_run_cocoa_loop (osxvideosink);
265  [osxwindow->gstview setMainThread:sink_class->ns_app_thread];
266#endif
267
268  if (osxvideosink->superview == NULL) {
269    GST_INFO_OBJECT (osxvideosink, "emitting prepare-xwindow-id");
270    gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (osxvideosink));
271  }
272
273  if (osxvideosink->superview != NULL) {
274    /* prepare-xwindow-id was handled, we have the superview in
275     * osxvideosink->superview. We now add osxwindow->gstview to the superview
276     * from the main thread
277     */
278    GST_INFO_OBJECT (osxvideosink, "we have a superview, adding our view to it");
279    gst_osx_video_sink_call_from_main_thread(osxvideosink, osxwindow->gstview,
280        @selector(addToSuperview:), osxvideosink->superview, NO);
281
282  } else {
283    gst_osx_video_sink_call_from_main_thread(osxvideosink,
284      osxvideosink->osxvideosinkobject,
285      @selector(createInternalWindow), nil, YES);
286    GST_INFO_OBJECT (osxvideosink, "No superview, creating an internal window.");
287  }
288  [osxwindow->gstview setNavigation: GST_NAVIGATION(osxvideosink)];
289  [osxvideosink->osxwindow->gstview setKeepAspectRatio: osxvideosink->keep_par];
290
291  [pool release];
292
293  return res;
294}
295
296static void
297gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink)
298{
299  NSAutoreleasePool *pool;
300
301  g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
302  pool = [[NSAutoreleasePool alloc] init];
303
304  GST_OBJECT_LOCK (osxvideosink);
305  gst_osx_video_sink_call_from_main_thread(osxvideosink,
306      osxvideosink->osxvideosinkobject,
307      @selector(destroy), (id) nil, YES);
308  GST_OBJECT_UNLOCK (osxvideosink);
309#ifndef GSTREAMER_GLIB_COCOA_NSAPPLICATION
310  gst_osx_video_sink_stop_cocoa_loop (osxvideosink);
311#endif
312  [pool release];
313}
314
315/* This function resizes a GstXWindow */
316static void
317gst_osx_video_sink_osxwindow_resize (GstOSXVideoSink * osxvideosink,
318    GstOSXWindow * osxwindow, guint width, guint height)
319{
320  GstOSXVideoSinkObject *object = osxvideosink->osxvideosinkobject;
321
322  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
323  g_return_if_fail (osxwindow != NULL);
324  g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
325
326  osxwindow->width = width;
327  osxwindow->height = height;
328
329  GST_DEBUG_OBJECT (osxvideosink, "Resizing window to (%d,%d)", width, height);
330
331  /* Directly resize the underlying view */
332  GST_DEBUG_OBJECT (osxvideosink, "Calling setVideoSize on %p", osxwindow->gstview);
333  gst_osx_video_sink_call_from_main_thread (osxvideosink, object,
334      @selector(resize), (id)nil, YES);
335
336  [pool release];
337}
338
339static gboolean
340gst_osx_video_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
341{
342  GstOSXVideoSink *osxvideosink;
343  GstStructure *structure;
344  gboolean res, result = FALSE;
345  gint video_width, video_height;
346
347  osxvideosink = GST_OSX_VIDEO_SINK (bsink);
348
349  GST_DEBUG_OBJECT (osxvideosink, "caps: %" GST_PTR_FORMAT, caps);
350
351  structure = gst_caps_get_structure (caps, 0);
352  res = gst_structure_get_int (structure, "width", &video_width);
353  res &= gst_structure_get_int (structure, "height", &video_height);
354
355  if (!res) {
356    goto beach;
357  }
358
359  GST_DEBUG_OBJECT (osxvideosink, "our format is: %dx%d video",
360      video_width, video_height);
361
362  GST_VIDEO_SINK_WIDTH (osxvideosink) = video_width;
363  GST_VIDEO_SINK_HEIGHT (osxvideosink) = video_height;
364
365  gst_osx_video_sink_osxwindow_resize (osxvideosink, osxvideosink->osxwindow,
366      video_width, video_height);
367
368  gst_video_info_from_caps (&osxvideosink->info, caps);
369
370  result = TRUE;
371
372beach:
373  return result;
374
375}
376
377static GstStateChangeReturn
378gst_osx_video_sink_change_state (GstElement * element,
379    GstStateChange transition)
380{
381  GstOSXVideoSink *osxvideosink;
382  GstStateChangeReturn ret;
383
384  osxvideosink = GST_OSX_VIDEO_SINK (element);
385
386  GST_DEBUG_OBJECT (osxvideosink, "%s => %s",
387        gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT (transition)),
388        gst_element_state_get_name(GST_STATE_TRANSITION_NEXT (transition)));
389
390  switch (transition) {
391    case GST_STATE_CHANGE_NULL_TO_READY:
392      break;
393    case GST_STATE_CHANGE_READY_TO_PAUSED:
394      /* Creating our window and our image */
395      GST_VIDEO_SINK_WIDTH (osxvideosink) = 320;
396      GST_VIDEO_SINK_HEIGHT (osxvideosink) = 240;
397      if (!gst_osx_video_sink_osxwindow_create (osxvideosink,
398          GST_VIDEO_SINK_WIDTH (osxvideosink),
399          GST_VIDEO_SINK_HEIGHT (osxvideosink))) {
400        ret = GST_STATE_CHANGE_FAILURE;
401        goto done;
402      }
403      break;
404    default:
405      break;
406  }
407
408  ret = (GST_ELEMENT_CLASS (parent_class))->change_state (element, transition);
409
410  switch (transition) {
411    case GST_STATE_CHANGE_PAUSED_TO_READY:
412      GST_VIDEO_SINK_WIDTH (osxvideosink) = 0;
413      GST_VIDEO_SINK_HEIGHT (osxvideosink) = 0;
414      gst_osx_video_sink_osxwindow_destroy (osxvideosink);
415      break;
416    case GST_STATE_CHANGE_READY_TO_NULL:
417      break;
418    default:
419      break;
420  }
421
422done:
423  return ret;
424}
425
426static GstFlowReturn
427gst_osx_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
428{
429  GstOSXVideoSink *osxvideosink;
430  GstBufferObject* bufferobject;
431  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
432
433  osxvideosink = GST_OSX_VIDEO_SINK (bsink);
434
435  GST_DEBUG ("show_frame");
436  bufferobject = [[GstBufferObject alloc] initWithBuffer:buf];
437  gst_osx_video_sink_call_from_main_thread(osxvideosink,
438      osxvideosink->osxvideosinkobject,
439      @selector(showFrame:), bufferobject, NO);
440  [pool release];
441  return GST_FLOW_OK;
442}
443
444/* Buffer management */
445
446
447
448/* =========================================== */
449/*                                             */
450/*              Init & Class init              */
451/*                                             */
452/* =========================================== */
453
454static void
455gst_osx_video_sink_set_property (GObject * object, guint prop_id,
456    const GValue * value, GParamSpec * pspec)
457{
458  GstOSXVideoSink *osxvideosink;
459
460  g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
461
462  osxvideosink = GST_OSX_VIDEO_SINK (object);
463
464  switch (prop_id) {
465    case ARG_EMBED:
466      g_warning ("The \"embed\" property of osxvideosink is deprecated and "
467          "has no effect anymore. Use the GstVideoOverlay "
468          "instead.");
469      break;
470    case ARG_FORCE_PAR:
471      osxvideosink->keep_par = g_value_get_boolean(value);
472      if (osxvideosink->osxwindow)
473        [osxvideosink->osxwindow->gstview
474            setKeepAspectRatio: osxvideosink->keep_par];
475      break;
476    default:
477      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
478      break;
479  }
480}
481
482static void
483gst_osx_video_sink_get_property (GObject * object, guint prop_id,
484    GValue * value, GParamSpec * pspec)
485{
486  GstOSXVideoSink *osxvideosink;
487
488  g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
489
490  osxvideosink = GST_OSX_VIDEO_SINK (object);
491
492  switch (prop_id) {
493    case ARG_EMBED:
494      g_value_set_boolean (value, FALSE);
495      break;
496    case ARG_FORCE_PAR:
497      g_value_set_boolean (value, osxvideosink->keep_par);
498      break;
499    default:
500      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
501      break;
502  }
503}
504
505static gboolean
506gst_osx_video_sink_propose_allocation (GstBaseSink * base_sink, GstQuery * query)
507{
508    gst_query_add_allocation_meta (query,
509        GST_VIDEO_META_API_TYPE, NULL);
510
511    return TRUE;
512}
513
514static void
515gst_osx_video_sink_init (GstOSXVideoSink * sink)
516{
517  sink->osxwindow = NULL;
518  sink->superview = NULL;
519  sink->osxvideosinkobject = [[GstOSXVideoSinkObject alloc] initWithSink:sink];
520  sink->keep_par = FALSE;
521}
522
523static void
524gst_osx_video_sink_base_init (gpointer g_class)
525{
526  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
527
528  gst_element_class_set_static_metadata (element_class, "OSX Video sink",
529      "Sink/Video", "OSX native videosink",
530      "Zaheer Abbas Merali <zaheerabbas at merali dot org>");
531
532  gst_element_class_add_static_pad_template (element_class, &gst_osx_video_sink_sink_template_factory);
533}
534
535static void
536gst_osx_video_sink_finalize (GObject *object)
537{
538  GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (object);
539
540  if (osxvideosink->superview)
541    [osxvideosink->superview release];
542
543  if (osxvideosink->osxvideosinkobject)
544    [(GstOSXVideoSinkObject*)(osxvideosink->osxvideosinkobject) release];
545
546  G_OBJECT_CLASS (parent_class)->finalize (object);
547}
548
549static void
550gst_osx_video_sink_class_init (GstOSXVideoSinkClass * klass)
551{
552  GObjectClass *gobject_class;
553  GstElementClass *gstelement_class;
554  GstBaseSinkClass *gstbasesink_class;
555
556  gobject_class = (GObjectClass *) klass;
557  gstelement_class = (GstElementClass *) klass;
558  gstbasesink_class = (GstBaseSinkClass *) klass;
559
560  parent_class = g_type_class_ref (GST_TYPE_VIDEO_SINK);
561  sink_class = klass;
562
563  klass->run_loop_state = GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_UNKNOWN;
564  klass->ns_app_thread = NULL;
565
566  gobject_class->set_property = gst_osx_video_sink_set_property;
567  gobject_class->get_property = gst_osx_video_sink_get_property;
568  gobject_class->finalize = gst_osx_video_sink_finalize;
569
570  gstbasesink_class->set_caps = gst_osx_video_sink_setcaps;
571  gstbasesink_class->preroll = gst_osx_video_sink_show_frame;
572  gstbasesink_class->render = gst_osx_video_sink_show_frame;
573  gstbasesink_class->propose_allocation = gst_osx_video_sink_propose_allocation;
574  gstelement_class->change_state = gst_osx_video_sink_change_state;
575
576  /**
577   * GstOSXVideoSink:embed
578   *
579   * For ABI comatibility onyl, do not use
580   *
581   **/
582
583  g_object_class_install_property (gobject_class, ARG_EMBED,
584      g_param_spec_boolean ("embed", "embed", "For ABI compatiblity only, do not use",
585          FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
586
587  /**
588   * GstOSXVideoSink:force-aspect-ratio
589   *
590   * When enabled, scaling will respect original aspect ratio.
591   *
592   **/
593
594  g_object_class_install_property (gobject_class, ARG_FORCE_PAR,
595      g_param_spec_boolean ("force-aspect-ratio", "force aspect ration",
596          "When enabled, scaling will respect original aspect ration",
597          TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
598}
599
600static void
601gst_osx_video_sink_navigation_send_event (GstNavigation * navigation,
602    GstStructure * structure)
603{
604  GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (navigation);
605  GstPad *peer;
606  GstEvent *event;
607  GstVideoRectangle src = { 0, };
608  GstVideoRectangle dst = { 0, };
609  GstVideoRectangle result;
610  NSRect bounds;
611  gdouble x, y, xscale = 1.0, yscale = 1.0;
612
613  peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (osxvideosink));
614
615  if (!peer || !osxvideosink->osxwindow)
616    return;
617
618  event = gst_event_new_navigation (structure);
619
620  bounds = [osxvideosink->osxwindow->gstview getDrawingBounds];
621
622  if (osxvideosink->keep_par) {
623    /* We get the frame position using the calculated geometry from _setcaps
624       that respect pixel aspect ratios */
625    src.w = GST_VIDEO_SINK_WIDTH (osxvideosink);
626    src.h = GST_VIDEO_SINK_HEIGHT (osxvideosink);
627    dst.w = bounds.size.width;
628    dst.h = bounds.size.height;
629
630    gst_video_sink_center_rect (src, dst, &result, TRUE);
631    result.x += bounds.origin.x;
632    result.y += bounds.origin.y;
633  } else {
634    result.x = bounds.origin.x;
635    result.y = bounds.origin.y;
636    result.w = bounds.size.width;
637    result.h = bounds.size.height;
638  }
639
640  /* We calculate scaling using the original video frames geometry to include
641     pixel aspect ratio scaling. */
642  xscale = (gdouble) osxvideosink->osxwindow->width / result.w;
643  yscale = (gdouble) osxvideosink->osxwindow->height / result.h;
644
645  /* Converting pointer coordinates to the non scaled geometry */
646  if (gst_structure_get_double (structure, "pointer_x", &x)) {
647    x = MIN (x, result.x + result.w);
648    x = MAX (x - result.x, 0);
649    gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
650        (gdouble) x * xscale, NULL);
651  }
652  if (gst_structure_get_double (structure, "pointer_y", &y)) {
653    y = MIN (y, result.y + result.h);
654    y = MAX (y - result.y, 0);
655    gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
656        (gdouble) y * yscale, NULL);
657  }
658
659  gst_pad_send_event (peer, event);
660  gst_object_unref (peer);
661}
662
663static void
664gst_osx_video_sink_navigation_init (GstNavigationInterface * iface)
665{
666  iface->send_event = gst_osx_video_sink_navigation_send_event;
667}
668
669static void
670gst_osx_video_sink_set_window_handle (GstVideoOverlay * overlay, guintptr handle_id)
671{
672  GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (overlay);
673  NSView *view = (NSView *) handle_id;
674
675  gst_osx_video_sink_call_from_main_thread(osxvideosink,
676      osxvideosink->osxvideosinkobject,
677      @selector(setView:), view, YES);
678}
679
680static void
681gst_osx_video_sink_xoverlay_init (GstVideoOverlayInterface * iface)
682{
683  iface->set_window_handle = gst_osx_video_sink_set_window_handle;
684  iface->expose = NULL;
685  iface->handle_events = NULL;
686}
687
688/* ============================================================= */
689/*                                                               */
690/*                       Public Methods                          */
691/*                                                               */
692/* ============================================================= */
693
694/* =========================================== */
695/*                                             */
696/*          Object typing & Creation           */
697/*                                             */
698/* =========================================== */
699
700GType
701gst_osx_video_sink_get_type (void)
702{
703  static GType osxvideosink_type = 0;
704
705  if (!osxvideosink_type) {
706    static const GTypeInfo osxvideosink_info = {
707      sizeof (GstOSXVideoSinkClass),
708      gst_osx_video_sink_base_init,
709      NULL,
710      (GClassInitFunc) gst_osx_video_sink_class_init,
711      NULL,
712      NULL,
713      sizeof (GstOSXVideoSink),
714      0,
715      (GInstanceInitFunc) gst_osx_video_sink_init,
716    };
717
718    static const GInterfaceInfo overlay_info = {
719      (GInterfaceInitFunc) gst_osx_video_sink_xoverlay_init,
720      NULL,
721      NULL,
722    };
723
724    static const GInterfaceInfo navigation_info = {
725      (GInterfaceInitFunc) gst_osx_video_sink_navigation_init,
726      NULL,
727      NULL,
728    };
729    osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
730        "GstOSXVideoSink", &osxvideosink_info, 0);
731
732    g_type_add_interface_static (osxvideosink_type, GST_TYPE_VIDEO_OVERLAY,
733        &overlay_info);
734    g_type_add_interface_static (osxvideosink_type, GST_TYPE_NAVIGATION,
735        &navigation_info);
736  }
737
738  return osxvideosink_type;
739}
740
741@implementation GstWindowDelegate
742- (id) initWithSink: (GstOSXVideoSink *) sink
743{
744  self = [super init];
745  self->osxvideosink = sink;
746  return self;
747}
748
749- (void)windowWillClose:(NSNotification *)notification {
750  /* Only handle close events if the window was closed manually by the user
751   * and not becuase of a state change state to READY */
752  if (osxvideosink->osxwindow == NULL) {
753    return;
754  }
755  if (!osxvideosink->osxwindow->closed) {
756    osxvideosink->osxwindow->closed = TRUE;
757    GST_ELEMENT_ERROR (osxvideosink, RESOURCE, NOT_FOUND, ("Output window was closed"), (NULL));
758    gst_osx_video_sink_osxwindow_destroy(osxvideosink);
759  }
760}
761
762@end
763
764@ implementation GstOSXVideoSinkObject
765
766-(id) initWithSink: (GstOSXVideoSink*) sink
767{
768  self = [super init];
769  self->osxvideosink = gst_object_ref (sink);
770  return self;
771}
772
773-(void) dealloc {
774  gst_object_unref (osxvideosink);
775  [super dealloc];
776}
777
778-(void) createInternalWindow
779{
780  GstOSXWindow *osxwindow = osxvideosink->osxwindow;
781  NSRect rect;
782  unsigned int mask;
783
784  [NSApplication sharedApplication];
785
786  osxwindow->internal = TRUE;
787
788  mask =  NSWindowStyleMaskTitled             |
789          NSWindowStyleMaskClosable           |
790          NSWindowStyleMaskResizable          |
791          NSWindowStyleMaskTexturedBackground |
792          NSWindowStyleMaskMiniaturizable;
793
794  rect.origin.x = 100.0;
795  rect.origin.y = 100.0;
796  rect.size.width = (float) osxwindow->width;
797  rect.size.height = (float) osxwindow->height;
798
799  osxwindow->win =[[[GstOSXVideoSinkWindow alloc]
800                       initWithContentNSRect: rect
801                       styleMask: mask
802                       backing: NSBackingStoreBuffered
803                       defer: NO
804                       screen: nil] retain];
805  GST_DEBUG("VideoSinkWindow created, %p", osxwindow->win);
806  [osxwindow->win orderFrontRegardless];
807  osxwindow->gstview =[osxwindow->win gstView];
808  [osxwindow->win setDelegate:[[GstWindowDelegate alloc]
809      initWithSink:osxvideosink]];
810
811}
812
813+ (BOOL) isMainThread
814{
815  /* FIXME: ideally we should return YES only for ->ns_app_thread here */
816  return YES;
817}
818
819- (void) setView: (NSView*)view
820{
821  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
822
823  if (osxvideosink->superview) {
824    GST_INFO_OBJECT (osxvideosink, "old xwindow id %p", osxvideosink->superview);
825    if (osxvideosink->osxwindow) {
826      [osxvideosink->osxwindow->gstview removeFromSuperview];
827    }
828    [osxvideosink->superview release];
829  }
830  if (osxvideosink->osxwindow != NULL && view != NULL) {
831    if (osxvideosink->osxwindow->internal) {
832      GST_INFO_OBJECT (osxvideosink, "closing internal window");
833      osxvideosink->osxwindow->closed = TRUE;
834      [osxvideosink->osxwindow->win close];
835      [osxvideosink->osxwindow->win release];
836    }
837  }
838
839  GST_INFO_OBJECT (osxvideosink, "set xwindow id %p", view);
840  osxvideosink->superview = [view retain];
841  if (osxvideosink->osxwindow) {
842    [osxvideosink->osxwindow->gstview addToSuperview: osxvideosink->superview];
843    if (view) {
844      osxvideosink->osxwindow->internal = FALSE;
845    }
846  }
847
848  [pool release];
849}
850
851- (void) resize
852{
853  GstOSXWindow *osxwindow = osxvideosink->osxwindow;
854
855  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
856
857  GST_INFO_OBJECT (osxvideosink, "resizing");
858  NSSize size = {osxwindow->width, osxwindow->height};
859  if (osxwindow->internal) {
860    [osxwindow->win setContentSize:size];
861  }
862  if (osxwindow->gstview) {
863      [osxwindow->gstview setVideoSize :(int)osxwindow->width :(int)osxwindow->height];
864  }
865  GST_INFO_OBJECT (osxvideosink, "done");
866
867  [pool release];
868}
869
870- (void) showFrame: (GstBufferObject *) object
871{
872  GstVideoFrame frame;
873  guint8 *data, *readp, *writep;
874  gint i, active_width, stride;
875  guint8 *texture_buffer;
876  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
877  GstBuffer *buf = object->buf;
878
879  GST_OBJECT_LOCK (osxvideosink);
880  if (osxvideosink->osxwindow == NULL)
881      goto no_window;
882
883  texture_buffer = (guint8 *) [osxvideosink->osxwindow->gstview getTextureBuffer];
884  if (G_UNLIKELY (texture_buffer == NULL))
885      goto no_texture_buffer;
886
887  if (!gst_video_frame_map (&frame, &osxvideosink->info, buf, GST_MAP_READ))
888      goto no_map;
889
890  data = readp = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
891  stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0);
892  writep = texture_buffer;
893  active_width = GST_VIDEO_SINK_WIDTH (osxvideosink) * sizeof (short);
894  for (i = 0; i < GST_VIDEO_SINK_HEIGHT (osxvideosink); i++) {
895      memcpy (writep, readp, active_width);
896      writep += active_width;
897      readp += stride;
898  }
899  [osxvideosink->osxwindow->gstview displayTexture];
900
901  gst_video_frame_unmap (&frame);
902
903out:
904  GST_OBJECT_UNLOCK (osxvideosink);
905  [object release];
906
907  [pool release];
908  return;
909
910no_map:
911  GST_WARNING_OBJECT (osxvideosink, "couldn't map frame");
912  goto out;
913
914no_window:
915  GST_WARNING_OBJECT (osxvideosink, "not showing frame since we have no window (!?)");
916  goto out;
917
918no_texture_buffer:
919  GST_ELEMENT_ERROR (osxvideosink, RESOURCE, WRITE, (NULL),
920          ("the texture buffer is NULL"));
921  goto out;
922}
923
924-(void) destroy
925{
926  NSAutoreleasePool *pool;
927  GstOSXWindow *osxwindow;
928
929  pool = [[NSAutoreleasePool alloc] init];
930
931  osxwindow = osxvideosink->osxwindow;
932  osxvideosink->osxwindow = NULL;
933
934  if (osxwindow) {
935    if (osxvideosink->superview) {
936      [osxwindow->gstview removeFromSuperview];
937    }
938    [osxwindow->gstview release];
939    if (osxwindow->internal) {
940      if (!osxwindow->closed) {
941        osxwindow->closed = TRUE;
942        [osxwindow->win close];
943        [osxwindow->win release];
944      }
945    }
946    g_free (osxwindow);
947  }
948  [pool release];
949}
950
951#ifndef GSTREAMER_GLIB_COCOA_NSAPPLICATION
952-(void) nsAppThread
953{
954  NSAutoreleasePool *pool;
955
956  /* set the main runloop as the runloop for the current thread. This has the
957   * effect that calling NSApp nextEventMatchingMask:untilDate:inMode:dequeue
958   * runs the main runloop.
959   */
960  _CFRunLoopSetCurrent(CFRunLoopGetMain());
961
962  /* this is needed to make IsMainThread checks in core foundation work from the
963   * current thread
964   */
965  _CFMainPThread = pthread_self();
966
967  pool = [[NSAutoreleasePool alloc] init];
968
969  [NSApplication sharedApplication];
970  [NSApp finishLaunching];
971
972  g_mutex_lock (&_run_loop_mutex);
973  g_cond_signal (&_run_loop_cond);
974  g_mutex_unlock (&_run_loop_mutex);
975
976  /* run the loop */
977  run_ns_app_loop ();
978
979  [pool release];
980}
981
982-(void) checkMainRunLoop
983{
984  g_mutex_lock (&_run_loop_mutex);
985  g_cond_signal (&_run_loop_cond);
986  g_mutex_unlock (&_run_loop_mutex);
987}
988#endif
989
990@end
991
992@ implementation GstBufferObject
993-(id) initWithBuffer: (GstBuffer*) buffer
994{
995  self = [super init];
996  gst_buffer_ref(buffer);
997  self->buf = buffer;
998  return self;
999}
1000
1001-(void) dealloc{
1002  gst_buffer_unref(buf);
1003  [super dealloc];
1004}
1005@end
1006
1007static gboolean
1008plugin_init (GstPlugin * plugin)
1009{
1010
1011  if (!gst_element_register (plugin, "osxvideosink",
1012          GST_RANK_MARGINAL, GST_TYPE_OSX_VIDEO_SINK))
1013    return FALSE;
1014
1015  GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0,
1016      "osxvideosink element");
1017
1018  return TRUE;
1019}
1020
1021GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1022    GST_VERSION_MINOR,
1023    osxvideo,
1024    "OSX native video output plugin",
1025    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1026