• 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, "macOS Video sink",
529      "Sink/Video", "macOS 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  g_object_class_install_property (gobject_class, ARG_EMBED,
577      g_param_spec_boolean ("embed", "embed", "For ABI compatibility only, do not use",
578          FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
579
580  g_object_class_install_property (gobject_class, ARG_FORCE_PAR,
581      g_param_spec_boolean ("force-aspect-ratio", "force aspect ration",
582          "When enabled, scaling will respect original aspect ration",
583          TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
584}
585
586static void
587gst_osx_video_sink_navigation_send_event (GstNavigation * navigation,
588    GstStructure * structure)
589{
590  GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (navigation);
591  GstPad *peer;
592  GstEvent *event;
593  GstVideoRectangle src = { 0, };
594  GstVideoRectangle dst = { 0, };
595  GstVideoRectangle result;
596  NSRect bounds;
597  gdouble x, y, xscale = 1.0, yscale = 1.0;
598
599  peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (osxvideosink));
600
601  if (!peer || !osxvideosink->osxwindow)
602    return;
603
604  event = gst_event_new_navigation (structure);
605
606  bounds = [osxvideosink->osxwindow->gstview getDrawingBounds];
607
608  if (osxvideosink->keep_par) {
609    /* We get the frame position using the calculated geometry from _setcaps
610       that respect pixel aspect ratios */
611    src.w = GST_VIDEO_SINK_WIDTH (osxvideosink);
612    src.h = GST_VIDEO_SINK_HEIGHT (osxvideosink);
613    dst.w = bounds.size.width;
614    dst.h = bounds.size.height;
615
616    gst_video_sink_center_rect (src, dst, &result, TRUE);
617    result.x += bounds.origin.x;
618    result.y += bounds.origin.y;
619  } else {
620    result.x = bounds.origin.x;
621    result.y = bounds.origin.y;
622    result.w = bounds.size.width;
623    result.h = bounds.size.height;
624  }
625
626  /* We calculate scaling using the original video frames geometry to include
627     pixel aspect ratio scaling. */
628  xscale = (gdouble) osxvideosink->osxwindow->width / result.w;
629  yscale = (gdouble) osxvideosink->osxwindow->height / result.h;
630
631  /* Converting pointer coordinates to the non scaled geometry */
632  if (gst_structure_get_double (structure, "pointer_x", &x)) {
633    x = MIN (x, result.x + result.w);
634    x = MAX (x - result.x, 0);
635    gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
636        (gdouble) x * xscale, NULL);
637  }
638  if (gst_structure_get_double (structure, "pointer_y", &y)) {
639    y = MIN (y, result.y + result.h);
640    y = MAX (y - result.y, 0);
641    gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
642        (gdouble) y * yscale, NULL);
643  }
644
645  gst_pad_send_event (peer, event);
646  gst_object_unref (peer);
647}
648
649static void
650gst_osx_video_sink_navigation_init (GstNavigationInterface * iface)
651{
652  iface->send_event = gst_osx_video_sink_navigation_send_event;
653}
654
655static void
656gst_osx_video_sink_set_window_handle (GstVideoOverlay * overlay, guintptr handle_id)
657{
658  GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (overlay);
659  NSView *view = (NSView *) handle_id;
660
661  gst_osx_video_sink_call_from_main_thread(osxvideosink,
662      osxvideosink->osxvideosinkobject,
663      @selector(setView:), view, YES);
664}
665
666static void
667gst_osx_video_sink_xoverlay_init (GstVideoOverlayInterface * iface)
668{
669  iface->set_window_handle = gst_osx_video_sink_set_window_handle;
670  iface->expose = NULL;
671  iface->handle_events = NULL;
672}
673
674/* ============================================================= */
675/*                                                               */
676/*                       Public Methods                          */
677/*                                                               */
678/* ============================================================= */
679
680/* =========================================== */
681/*                                             */
682/*          Object typing & Creation           */
683/*                                             */
684/* =========================================== */
685
686GType
687gst_osx_video_sink_get_type (void)
688{
689  static GType osxvideosink_type = 0;
690
691  if (!osxvideosink_type) {
692    static const GTypeInfo osxvideosink_info = {
693      sizeof (GstOSXVideoSinkClass),
694      gst_osx_video_sink_base_init,
695      NULL,
696      (GClassInitFunc) gst_osx_video_sink_class_init,
697      NULL,
698      NULL,
699      sizeof (GstOSXVideoSink),
700      0,
701      (GInstanceInitFunc) gst_osx_video_sink_init,
702    };
703
704    static const GInterfaceInfo overlay_info = {
705      (GInterfaceInitFunc) gst_osx_video_sink_xoverlay_init,
706      NULL,
707      NULL,
708    };
709
710    static const GInterfaceInfo navigation_info = {
711      (GInterfaceInitFunc) gst_osx_video_sink_navigation_init,
712      NULL,
713      NULL,
714    };
715    osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
716        "GstOSXVideoSink", &osxvideosink_info, 0);
717
718    g_type_add_interface_static (osxvideosink_type, GST_TYPE_VIDEO_OVERLAY,
719        &overlay_info);
720    g_type_add_interface_static (osxvideosink_type, GST_TYPE_NAVIGATION,
721        &navigation_info);
722  }
723
724  return osxvideosink_type;
725}
726
727@implementation GstWindowDelegate
728- (id) initWithSink: (GstOSXVideoSink *) sink
729{
730  self = [super init];
731  self->osxvideosink = sink;
732  return self;
733}
734
735- (void)windowWillClose:(NSNotification *)notification {
736  /* Only handle close events if the window was closed manually by the user
737   * and not because of a state change state to READY */
738  if (osxvideosink->osxwindow == NULL) {
739    return;
740  }
741  if (!osxvideosink->osxwindow->closed) {
742    osxvideosink->osxwindow->closed = TRUE;
743    GST_ELEMENT_ERROR (osxvideosink, RESOURCE, NOT_FOUND, ("Output window was closed"), (NULL));
744    gst_osx_video_sink_osxwindow_destroy(osxvideosink);
745  }
746}
747
748@end
749
750@ implementation GstOSXVideoSinkObject
751
752-(id) initWithSink: (GstOSXVideoSink*) sink
753{
754  self = [super init];
755  self->osxvideosink = gst_object_ref (sink);
756  return self;
757}
758
759-(void) dealloc {
760  gst_object_unref (osxvideosink);
761  [super dealloc];
762}
763
764-(void) createInternalWindow
765{
766  GstOSXWindow *osxwindow = osxvideosink->osxwindow;
767  NSRect rect;
768  unsigned int mask;
769
770  [NSApplication sharedApplication];
771
772  osxwindow->internal = TRUE;
773
774  mask =  NSWindowStyleMaskTitled             |
775          NSWindowStyleMaskClosable           |
776          NSWindowStyleMaskResizable          |
777          NSWindowStyleMaskTexturedBackground |
778          NSWindowStyleMaskMiniaturizable;
779
780  rect.origin.x = 100.0;
781  rect.origin.y = 100.0;
782  rect.size.width = (float) osxwindow->width;
783  rect.size.height = (float) osxwindow->height;
784
785  osxwindow->win =[[[GstOSXVideoSinkWindow alloc]
786                       initWithContentNSRect: rect
787                       styleMask: mask
788                       backing: NSBackingStoreBuffered
789                       defer: NO
790                       screen: nil] retain];
791  GST_DEBUG("VideoSinkWindow created, %p", osxwindow->win);
792  [osxwindow->win orderFrontRegardless];
793  osxwindow->gstview =[osxwindow->win gstView];
794  [osxwindow->win setDelegate:[[GstWindowDelegate alloc]
795      initWithSink:osxvideosink]];
796
797}
798
799+ (BOOL) isMainThread
800{
801  /* FIXME: ideally we should return YES only for ->ns_app_thread here */
802  return YES;
803}
804
805- (void) setView: (NSView*)view
806{
807  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
808
809  if (osxvideosink->superview) {
810    GST_INFO_OBJECT (osxvideosink, "old xwindow id %p", osxvideosink->superview);
811    if (osxvideosink->osxwindow) {
812      [osxvideosink->osxwindow->gstview removeFromSuperview];
813    }
814    [osxvideosink->superview release];
815  }
816  if (osxvideosink->osxwindow != NULL && view != NULL) {
817    if (osxvideosink->osxwindow->internal) {
818      GST_INFO_OBJECT (osxvideosink, "closing internal window");
819      osxvideosink->osxwindow->closed = TRUE;
820      [osxvideosink->osxwindow->win close];
821      [osxvideosink->osxwindow->win release];
822    }
823  }
824
825  GST_INFO_OBJECT (osxvideosink, "set xwindow id %p", view);
826  osxvideosink->superview = [view retain];
827  if (osxvideosink->osxwindow) {
828    [osxvideosink->osxwindow->gstview addToSuperview: osxvideosink->superview];
829    if (view) {
830      osxvideosink->osxwindow->internal = FALSE;
831    }
832  }
833
834  [pool release];
835}
836
837- (void) resize
838{
839  GstOSXWindow *osxwindow = osxvideosink->osxwindow;
840
841  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
842
843  GST_INFO_OBJECT (osxvideosink, "resizing");
844  NSSize size = {osxwindow->width, osxwindow->height};
845  if (osxwindow->internal) {
846    [osxwindow->win setContentSize:size];
847  }
848  if (osxwindow->gstview) {
849      [osxwindow->gstview setVideoSize :(int)osxwindow->width :(int)osxwindow->height];
850  }
851  GST_INFO_OBJECT (osxvideosink, "done");
852
853  [pool release];
854}
855
856- (void) showFrame: (GstBufferObject *) object
857{
858  GstVideoFrame frame;
859  guint8 *readp, *writep;
860  gint i, active_width, stride;
861  guint8 *texture_buffer;
862  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
863  GstBuffer *buf = object->buf;
864
865  GST_OBJECT_LOCK (osxvideosink);
866  if (osxvideosink->osxwindow == NULL)
867      goto no_window;
868
869  texture_buffer = (guint8 *) [osxvideosink->osxwindow->gstview getTextureBuffer];
870  if (G_UNLIKELY (texture_buffer == NULL))
871      goto no_texture_buffer;
872
873  if (!gst_video_frame_map (&frame, &osxvideosink->info, buf, GST_MAP_READ))
874      goto no_map;
875
876  readp = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
877  stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0);
878  writep = texture_buffer;
879  active_width = GST_VIDEO_SINK_WIDTH (osxvideosink) * sizeof (short);
880  for (i = 0; i < GST_VIDEO_SINK_HEIGHT (osxvideosink); i++) {
881      memcpy (writep, readp, active_width);
882      writep += active_width;
883      readp += stride;
884  }
885  [osxvideosink->osxwindow->gstview displayTexture];
886
887  gst_video_frame_unmap (&frame);
888
889out:
890  GST_OBJECT_UNLOCK (osxvideosink);
891  [object release];
892
893  [pool release];
894  return;
895
896no_map:
897  GST_WARNING_OBJECT (osxvideosink, "couldn't map frame");
898  goto out;
899
900no_window:
901  GST_WARNING_OBJECT (osxvideosink, "not showing frame since we have no window (!?)");
902  goto out;
903
904no_texture_buffer:
905  GST_ELEMENT_ERROR (osxvideosink, RESOURCE, WRITE, (NULL),
906          ("the texture buffer is NULL"));
907  goto out;
908}
909
910-(void) destroy
911{
912  NSAutoreleasePool *pool;
913  GstOSXWindow *osxwindow;
914
915  pool = [[NSAutoreleasePool alloc] init];
916
917  osxwindow = osxvideosink->osxwindow;
918  osxvideosink->osxwindow = NULL;
919
920  if (osxwindow) {
921    if (osxvideosink->superview) {
922      [osxwindow->gstview removeFromSuperview];
923    }
924    [osxwindow->gstview release];
925    if (osxwindow->internal) {
926      if (!osxwindow->closed) {
927        osxwindow->closed = TRUE;
928        [osxwindow->win close];
929        [osxwindow->win release];
930      }
931    }
932    g_free (osxwindow);
933  }
934  [pool release];
935}
936
937#ifndef GSTREAMER_GLIB_COCOA_NSAPPLICATION
938-(void) nsAppThread
939{
940  NSAutoreleasePool *pool;
941
942  /* set the main runloop as the runloop for the current thread. This has the
943   * effect that calling NSApp nextEventMatchingMask:untilDate:inMode:dequeue
944   * runs the main runloop.
945   */
946  _CFRunLoopSetCurrent(CFRunLoopGetMain());
947
948  /* this is needed to make IsMainThread checks in core foundation work from the
949   * current thread
950   */
951  _CFMainPThread = pthread_self();
952
953  pool = [[NSAutoreleasePool alloc] init];
954
955  [NSApplication sharedApplication];
956  [NSApp finishLaunching];
957
958  g_mutex_lock (&_run_loop_mutex);
959  g_cond_signal (&_run_loop_cond);
960  g_mutex_unlock (&_run_loop_mutex);
961
962  /* run the loop */
963  run_ns_app_loop ();
964
965  [pool release];
966}
967
968-(void) checkMainRunLoop
969{
970  g_mutex_lock (&_run_loop_mutex);
971  g_cond_signal (&_run_loop_cond);
972  g_mutex_unlock (&_run_loop_mutex);
973}
974#endif
975
976@end
977
978@ implementation GstBufferObject
979-(id) initWithBuffer: (GstBuffer*) buffer
980{
981  self = [super init];
982  gst_buffer_ref(buffer);
983  self->buf = buffer;
984  return self;
985}
986
987-(void) dealloc{
988  gst_buffer_unref(buf);
989  [super dealloc];
990}
991@end
992
993static gboolean
994plugin_init (GstPlugin * plugin)
995{
996
997  if (!gst_element_register (plugin, "osxvideosink",
998          GST_RANK_MARGINAL, GST_TYPE_OSX_VIDEO_SINK))
999    return FALSE;
1000
1001  GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0,
1002      "osxvideosink element");
1003
1004  return TRUE;
1005}
1006
1007GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1008    GST_VERSION_MINOR,
1009    osxvideo,
1010    "OSX native video output plugin",
1011    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1012