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