mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 09:10:36 +00:00
988 lines
29 KiB
Objective-C
988 lines
29 KiB
Objective-C
/* GStreamer
|
|
* OSX video sink
|
|
* Copyright (C) 2004-6 Zaheer Abbas Merali <zaheerabbas at merali dot org>
|
|
* Copyright (C) 2007,2008,2009 Pioneers of the Inevitable <songbird@songbirdnest.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*
|
|
* The development of this code was made possible due to the involvement of
|
|
* Pioneers of the Inevitable, the creators of the Songbird Music player.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-osxvideosink
|
|
*
|
|
* The OSXVideoSink renders video frames to a MacOSX window. The video output
|
|
* must be directed to a window embedded in an existing NSApp.
|
|
*
|
|
* When the NSView to be embedded is created an element #GstMessage with a
|
|
* name of 'have-ns-view' will be created and posted on the bus.
|
|
* The pointer to the NSView to embed will be in the 'nsview' field of that
|
|
* message. The application MUST handle this message and embed the view
|
|
* appropriately.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <gst/video/videooverlay.h>
|
|
#include <gst/video/navigation.h>
|
|
|
|
#include "osxvideosink.h"
|
|
#include <unistd.h>
|
|
#import "cocoawindow.h"
|
|
|
|
GST_DEBUG_CATEGORY (gst_debug_osx_video_sink);
|
|
#define GST_CAT_DEFAULT gst_debug_osx_video_sink
|
|
|
|
#ifdef RUN_NS_APP_THREAD
|
|
#include <pthread.h>
|
|
extern void _CFRunLoopSetCurrent(CFRunLoopRef rl);
|
|
extern pthread_t _CFMainPThread;
|
|
#endif
|
|
|
|
static GstStaticPadTemplate gst_osx_video_sink_sink_template_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-raw, "
|
|
"framerate = (fraction) [ 0, MAX ], "
|
|
"width = (int) [ 1, MAX ], "
|
|
"height = (int) [ 1, MAX ], "
|
|
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
|
"format = (string) YUY2")
|
|
#else
|
|
"format = (string) UYVY")
|
|
#endif
|
|
);
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
ARG_EMBED,
|
|
ARG_FORCE_PAR,
|
|
};
|
|
|
|
static void gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink);
|
|
|
|
static GstVideoSinkClass *parent_class = NULL;
|
|
|
|
/* Helper to trigger calls from the main thread */
|
|
static void
|
|
gst_osx_video_sink_call_from_main_thread(GstOSXVideoSink *osxvideosink,
|
|
NSObject * object, SEL function, NSObject *data, BOOL waitUntilDone)
|
|
{
|
|
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
[object performSelector:function onThread:osxvideosink->ns_app_thread
|
|
withObject:data waitUntilDone:waitUntilDone];
|
|
[pool release];
|
|
}
|
|
|
|
/* Poll for cocoa events */
|
|
static void
|
|
run_ns_app_loop (void) {
|
|
NSEvent *event;
|
|
NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
|
|
NSDate *pollTime = nil;
|
|
|
|
#ifdef RUN_NS_APP_THREAD
|
|
/* when running the loop in a thread we want to sleep as long as possible */
|
|
pollTime = [NSDate distantFuture];
|
|
#else
|
|
pollTime = [NSDate distantPast];
|
|
#endif
|
|
|
|
do {
|
|
event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:pollTime
|
|
inMode:NSDefaultRunLoopMode dequeue:YES];
|
|
[NSApp sendEvent:event];
|
|
}
|
|
while (event != nil);
|
|
[pool release];
|
|
}
|
|
|
|
static void
|
|
gst_osx_videosink_check_main_run_loop (GstOSXVideoSink *sink)
|
|
{
|
|
/* check if the main run loop is running */
|
|
gboolean is_running;
|
|
|
|
if (sink->mrl_check_done) {
|
|
return;
|
|
}
|
|
/* the easy way */
|
|
is_running = [[NSRunLoop mainRunLoop] currentMode] != nil;
|
|
if (is_running) {
|
|
goto exit;
|
|
} else {
|
|
/* the previous check doesn't always work with main loops that run
|
|
* cocoa's main run loop manually, like the gdk one, giving false
|
|
* negatives. This check defers a call to the main thread and waits to
|
|
* be awaken by this function. */
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
GstOSXVideoSinkObject * object = (GstOSXVideoSinkObject *) sink->osxvideosinkobject;
|
|
gint64 abstime;
|
|
|
|
g_mutex_lock (&sink->mrl_check_lock);
|
|
[object performSelectorOnMainThread:
|
|
@selector(checkMainRunLoop)
|
|
withObject:nil waitUntilDone:NO];
|
|
/* Wait 100 ms */
|
|
abstime = g_get_monotonic_time () + 100 * 1000;
|
|
is_running = g_cond_wait_until (&sink->mrl_check_cond,
|
|
&sink->mrl_check_lock, abstime);
|
|
g_mutex_unlock (&sink->mrl_check_lock);
|
|
|
|
[pool release];
|
|
}
|
|
|
|
exit:
|
|
{
|
|
GST_DEBUG_OBJECT(sink, "The main runloop %s is running",
|
|
is_running ? "" : " not ");
|
|
sink->main_run_loop_running = is_running;
|
|
sink->mrl_check_done = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_run_cocoa_loop (GstOSXVideoSink * sink )
|
|
{
|
|
/* Cocoa applications require a main runloop running to dispatch UI
|
|
* events and process deferred calls to the main thread through
|
|
* perfermSelectorOnMainThread.
|
|
* Since the sink needs to create it's own Cocoa window when no
|
|
* external NSView is passed to the sink through the GstVideoOverlay API,
|
|
* we need to run the cocoa mainloop somehow.
|
|
*/
|
|
if (!sink->main_run_loop_running) {
|
|
#ifdef RUN_NS_APP_THREAD
|
|
/* run the main runloop in a separate thread */
|
|
|
|
/* override [NSThread isMainThread] with our own implementation so that we can
|
|
* make it believe our dedicated thread is the main thread
|
|
*/
|
|
Method origIsMainThread = class_getClassMethod([NSThread class],
|
|
NSSelectorFromString(@"isMainThread"));
|
|
Method ourIsMainThread = class_getClassMethod([GstOSXVideoSinkObject class],
|
|
NSSelectorFromString(@"isMainThread"));
|
|
|
|
method_exchangeImplementations(origIsMainThread, ourIsMainThread);
|
|
|
|
sink->ns_app_thread = [[NSThread alloc]
|
|
initWithTarget:sink->osxvideosinkobject
|
|
selector:@selector(nsAppThread) object:nil];
|
|
[sink->ns_app_thread start];
|
|
|
|
g_mutex_lock (&sink->loop_thread_lock);
|
|
while (!sink->app_started)
|
|
g_cond_wait (&sink->loop_thread_cond, &sink->loop_thread_lock);
|
|
g_mutex_unlock (&sink->loop_thread_lock);
|
|
#else
|
|
/* assume that there is a GMainLoop and iterate the main runloop from there
|
|
*/
|
|
sink->cocoa_timeout = g_timeout_add (10,
|
|
(GSourceFunc) run_ns_app_loop, NULL);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_stop_cocoa_loop (GstOSXVideoSink * osxvideosink)
|
|
{
|
|
#ifndef RUN_NS_APP_THREAD
|
|
if (osxvideosink->cocoa_timeout)
|
|
g_source_remove(osxvideosink->cocoa_timeout);
|
|
#endif
|
|
}
|
|
|
|
/* This function handles osx window creation */
|
|
static gboolean
|
|
gst_osx_video_sink_osxwindow_create (GstOSXVideoSink * osxvideosink, gint width,
|
|
gint height)
|
|
{
|
|
NSRect rect;
|
|
GstOSXWindow *osxwindow = NULL;
|
|
GstStructure *s;
|
|
GstMessage *msg;
|
|
gboolean res = TRUE;
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
g_return_val_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink), FALSE);
|
|
|
|
GST_DEBUG_OBJECT (osxvideosink, "Creating new OSX window");
|
|
|
|
osxvideosink->osxwindow = osxwindow = g_new0 (GstOSXWindow, 1);
|
|
|
|
osxwindow->width = width;
|
|
osxwindow->height = height;
|
|
osxwindow->closed = FALSE;
|
|
osxwindow->internal = FALSE;
|
|
|
|
/* Allocate our GstGLView for the window, and then tell the application
|
|
* about it (hopefully it's listening...) */
|
|
rect.origin.x = 0.0;
|
|
rect.origin.y = 0.0;
|
|
rect.size.width = (float) osxwindow->width;
|
|
rect.size.height = (float) osxwindow->height;
|
|
osxwindow->gstview =[[GstGLView alloc] initWithFrame:rect];
|
|
|
|
s = gst_structure_new ("have-ns-view",
|
|
"nsview", G_TYPE_POINTER, osxwindow->gstview,
|
|
nil);
|
|
|
|
msg = gst_message_new_element (GST_OBJECT (osxvideosink), s);
|
|
gst_element_post_message (GST_ELEMENT (osxvideosink), msg);
|
|
|
|
GST_INFO_OBJECT (osxvideosink, "'have-ns-view' message sent");
|
|
|
|
osxvideosink->ns_app_thread = [NSThread mainThread];
|
|
gst_osx_videosink_check_main_run_loop (osxvideosink);
|
|
gst_osx_video_sink_run_cocoa_loop (osxvideosink);
|
|
[osxwindow->gstview setMainThread:osxvideosink->ns_app_thread];
|
|
|
|
/* check if have-ns-view was handled and osxwindow->gstview was added to a
|
|
* superview
|
|
*/
|
|
if ([osxwindow->gstview haveSuperview] == NO) {
|
|
/* have-ns-view wasn't handled, post prepare-xwindow-id */
|
|
if (osxvideosink->superview == NULL) {
|
|
GST_INFO_OBJECT (osxvideosink, "emitting prepare-xwindow-id");
|
|
gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (osxvideosink));
|
|
}
|
|
|
|
if (osxvideosink->superview != NULL) {
|
|
/* prepare-xwindow-id was handled, we have the superview in
|
|
* osxvideosink->superview. We now add osxwindow->gstview to the superview
|
|
* from the main thread
|
|
*/
|
|
GST_INFO_OBJECT (osxvideosink, "we have a superview, adding our view to it");
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink, osxwindow->gstview,
|
|
@selector(addToSuperview:), osxvideosink->superview, NO);
|
|
|
|
} else {
|
|
if (osxvideosink->embed) {
|
|
/* the view wasn't added to a superview. It's possible that the
|
|
* application handled have-ns-view, stored our view internally and is
|
|
* going to add it to a superview later (webkit does that now).
|
|
*/
|
|
GST_INFO_OBJECT (osxvideosink, "no superview");
|
|
} else {
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink,
|
|
osxvideosink->osxvideosinkobject,
|
|
@selector(createInternalWindow), nil, YES);
|
|
GST_INFO_OBJECT (osxvideosink, "No superview, creating an internal window.");
|
|
}
|
|
}
|
|
}
|
|
[osxwindow->gstview setNavigation: GST_NAVIGATION(osxvideosink)];
|
|
[osxvideosink->osxwindow->gstview setKeepAspectRatio: osxvideosink->keep_par];
|
|
|
|
[pool release];
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_osxwindow_destroy (GstOSXVideoSink * osxvideosink)
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
|
|
g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink,
|
|
osxvideosink->osxvideosinkobject,
|
|
@selector(destroy), (id) nil, YES);
|
|
gst_osx_video_sink_stop_cocoa_loop (osxvideosink);
|
|
[pool release];
|
|
}
|
|
|
|
/* This function resizes a GstXWindow */
|
|
static void
|
|
gst_osx_video_sink_osxwindow_resize (GstOSXVideoSink * osxvideosink,
|
|
GstOSXWindow * osxwindow, guint width, guint height)
|
|
{
|
|
GstOSXVideoSinkObject *object = osxvideosink->osxvideosinkobject;
|
|
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
g_return_if_fail (osxwindow != NULL);
|
|
g_return_if_fail (GST_IS_OSX_VIDEO_SINK (osxvideosink));
|
|
|
|
osxwindow->width = width;
|
|
osxwindow->height = height;
|
|
|
|
GST_DEBUG_OBJECT (osxvideosink, "Resizing window to (%d,%d)", width, height);
|
|
|
|
/* Directly resize the underlying view */
|
|
GST_DEBUG_OBJECT (osxvideosink, "Calling setVideoSize on %p", osxwindow->gstview);
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink, object,
|
|
@selector(resize), (id)nil, YES);
|
|
|
|
[pool release];
|
|
}
|
|
|
|
static gboolean
|
|
gst_osx_video_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
|
|
{
|
|
GstOSXVideoSink *osxvideosink;
|
|
GstStructure *structure;
|
|
gboolean res, result = FALSE;
|
|
gint video_width, video_height;
|
|
|
|
osxvideosink = GST_OSX_VIDEO_SINK (bsink);
|
|
|
|
GST_DEBUG_OBJECT (osxvideosink, "caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
structure = gst_caps_get_structure (caps, 0);
|
|
res = gst_structure_get_int (structure, "width", &video_width);
|
|
res &= gst_structure_get_int (structure, "height", &video_height);
|
|
|
|
if (!res) {
|
|
goto beach;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (osxvideosink, "our format is: %dx%d video",
|
|
video_width, video_height);
|
|
|
|
GST_VIDEO_SINK_WIDTH (osxvideosink) = video_width;
|
|
GST_VIDEO_SINK_HEIGHT (osxvideosink) = video_height;
|
|
|
|
gst_osx_video_sink_osxwindow_resize (osxvideosink, osxvideosink->osxwindow,
|
|
video_width, video_height);
|
|
result = TRUE;
|
|
|
|
beach:
|
|
return result;
|
|
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_osx_video_sink_change_state (GstElement * element,
|
|
GstStateChange transition)
|
|
{
|
|
GstOSXVideoSink *osxvideosink;
|
|
GstStateChangeReturn ret;
|
|
|
|
osxvideosink = GST_OSX_VIDEO_SINK (element);
|
|
|
|
GST_DEBUG_OBJECT (osxvideosink, "%s => %s",
|
|
gst_element_state_get_name(GST_STATE_TRANSITION_CURRENT (transition)),
|
|
gst_element_state_get_name(GST_STATE_TRANSITION_NEXT (transition)));
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
/* Creating our window and our image */
|
|
GST_VIDEO_SINK_WIDTH (osxvideosink) = 320;
|
|
GST_VIDEO_SINK_HEIGHT (osxvideosink) = 240;
|
|
if (!gst_osx_video_sink_osxwindow_create (osxvideosink,
|
|
GST_VIDEO_SINK_WIDTH (osxvideosink),
|
|
GST_VIDEO_SINK_HEIGHT (osxvideosink))) {
|
|
ret = GST_STATE_CHANGE_FAILURE;
|
|
goto done;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = (GST_ELEMENT_CLASS (parent_class))->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
GST_VIDEO_SINK_WIDTH (osxvideosink) = 0;
|
|
GST_VIDEO_SINK_HEIGHT (osxvideosink) = 0;
|
|
osxvideosink->app_started = FALSE;
|
|
gst_osx_video_sink_osxwindow_destroy (osxvideosink);
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_osx_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
|
|
{
|
|
GstOSXVideoSink *osxvideosink;
|
|
GstBufferObject* bufferobject;
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
osxvideosink = GST_OSX_VIDEO_SINK (bsink);
|
|
|
|
GST_DEBUG ("show_frame");
|
|
bufferobject = [[GstBufferObject alloc] initWithBuffer:buf];
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink,
|
|
osxvideosink->osxvideosinkobject,
|
|
@selector(showFrame:), bufferobject, NO);
|
|
[pool release];
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
/* Buffer management */
|
|
|
|
|
|
|
|
/* =========================================== */
|
|
/* */
|
|
/* Init & Class init */
|
|
/* */
|
|
/* =========================================== */
|
|
|
|
static void
|
|
gst_osx_video_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstOSXVideoSink *osxvideosink;
|
|
|
|
g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
|
|
|
|
osxvideosink = GST_OSX_VIDEO_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case ARG_EMBED:
|
|
osxvideosink->embed = g_value_get_boolean(value);
|
|
break;
|
|
case ARG_FORCE_PAR:
|
|
osxvideosink->keep_par = g_value_get_boolean(value);
|
|
if (osxvideosink->osxwindow)
|
|
[osxvideosink->osxwindow->gstview
|
|
setKeepAspectRatio: osxvideosink->keep_par];
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstOSXVideoSink *osxvideosink;
|
|
|
|
g_return_if_fail (GST_IS_OSX_VIDEO_SINK (object));
|
|
|
|
osxvideosink = GST_OSX_VIDEO_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case ARG_EMBED:
|
|
g_value_set_boolean (value, osxvideosink->embed);
|
|
break;
|
|
case ARG_FORCE_PAR:
|
|
g_value_set_boolean (value, osxvideosink->keep_par);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gst_osx_video_sink_init (GstOSXVideoSink * sink)
|
|
{
|
|
sink->osxwindow = NULL;
|
|
sink->superview = NULL;
|
|
sink->osxvideosinkobject = [[GstOSXVideoSinkObject alloc] initWithSink:sink];
|
|
#ifdef RUN_NS_APP_THREAD
|
|
g_mutex_init (&sink->loop_thread_lock);
|
|
g_cond_init (&sink->loop_thread_cond);
|
|
#endif
|
|
g_mutex_init (&sink->mrl_check_lock);
|
|
g_cond_init (&sink->mrl_check_cond);
|
|
sink->mrl_check_done = FALSE;
|
|
sink->main_run_loop_running = FALSE;
|
|
sink->app_started = FALSE;
|
|
sink->keep_par = FALSE;
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_base_init (gpointer g_class)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
|
|
|
gst_element_class_set_static_metadata (element_class, "OSX Video sink",
|
|
"Sink/Video", "OSX native videosink",
|
|
"Zaheer Abbas Merali <zaheerabbas at merali dot org>");
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_osx_video_sink_sink_template_factory));
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_finalize (GObject *object)
|
|
{
|
|
GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (object);
|
|
|
|
if (osxvideosink->superview)
|
|
[osxvideosink->superview release];
|
|
|
|
if (osxvideosink->osxvideosinkobject)
|
|
[(GstOSXVideoSinkObject*)(osxvideosink->osxvideosinkobject) release];
|
|
|
|
g_mutex_clear (&osxvideosink->mrl_check_lock);
|
|
g_cond_clear (&osxvideosink->mrl_check_cond);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_class_init (GstOSXVideoSinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstBaseSinkClass *gstbasesink_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstbasesink_class = (GstBaseSinkClass *) klass;
|
|
|
|
|
|
parent_class = g_type_class_ref (GST_TYPE_VIDEO_SINK);
|
|
|
|
gobject_class->set_property = gst_osx_video_sink_set_property;
|
|
gobject_class->get_property = gst_osx_video_sink_get_property;
|
|
gobject_class->finalize = gst_osx_video_sink_finalize;
|
|
|
|
gstbasesink_class->set_caps = gst_osx_video_sink_setcaps;
|
|
gstbasesink_class->preroll = gst_osx_video_sink_show_frame;
|
|
gstbasesink_class->render = gst_osx_video_sink_show_frame;
|
|
gstelement_class->change_state = gst_osx_video_sink_change_state;
|
|
|
|
/**
|
|
* GstOSXVideoSink:embed
|
|
*
|
|
* Set to #TRUE if you are embedding the video window in an application.
|
|
*
|
|
**/
|
|
|
|
g_object_class_install_property (gobject_class, ARG_EMBED,
|
|
g_param_spec_boolean ("embed", "embed", "For ABI compatiblity only, do not use",
|
|
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* GstOSXVideoSink:force-aspect-ratio
|
|
*
|
|
* When enabled, scaling will respect original aspect ratio.
|
|
*
|
|
**/
|
|
|
|
g_object_class_install_property (gobject_class, ARG_FORCE_PAR,
|
|
g_param_spec_boolean ("force-aspect-ratio", "force aspect ration",
|
|
"When enabled, scaling will respect original aspect ration",
|
|
TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_navigation_send_event (GstNavigation * navigation,
|
|
GstStructure * structure)
|
|
{
|
|
GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (navigation);
|
|
GstPad *peer;
|
|
GstEvent *event;
|
|
GstVideoRectangle src, dst, result;
|
|
NSRect bounds;
|
|
gdouble x, y, xscale = 1.0, yscale = 1.0;
|
|
|
|
peer = gst_pad_get_peer (GST_VIDEO_SINK_PAD (osxvideosink));
|
|
|
|
if (!peer || !osxvideosink->osxwindow)
|
|
return;
|
|
|
|
event = gst_event_new_navigation (structure);
|
|
|
|
bounds = [osxvideosink->osxwindow->gstview getDrawingBounds];
|
|
|
|
if (osxvideosink->keep_par) {
|
|
/* We get the frame position using the calculated geometry from _setcaps
|
|
that respect pixel aspect ratios */
|
|
src.w = GST_VIDEO_SINK_WIDTH (osxvideosink);
|
|
src.h = GST_VIDEO_SINK_HEIGHT (osxvideosink);
|
|
dst.w = bounds.size.width;
|
|
dst.h = bounds.size.height;
|
|
|
|
gst_video_sink_center_rect (src, dst, &result, TRUE);
|
|
result.x += bounds.origin.x;
|
|
result.y += bounds.origin.y;
|
|
} else {
|
|
result.x = bounds.origin.x;
|
|
result.y = bounds.origin.y;
|
|
result.w = bounds.size.width;
|
|
result.h = bounds.size.height;
|
|
}
|
|
|
|
/* We calculate scaling using the original video frames geometry to include
|
|
pixel aspect ratio scaling. */
|
|
xscale = (gdouble) osxvideosink->osxwindow->width / result.w;
|
|
yscale = (gdouble) osxvideosink->osxwindow->height / result.h;
|
|
|
|
/* Converting pointer coordinates to the non scaled geometry */
|
|
if (gst_structure_get_double (structure, "pointer_x", &x)) {
|
|
x = MIN (x, result.x + result.w);
|
|
x = MAX (x - result.x, 0);
|
|
gst_structure_set (structure, "pointer_x", G_TYPE_DOUBLE,
|
|
(gdouble) x * xscale, NULL);
|
|
}
|
|
if (gst_structure_get_double (structure, "pointer_y", &y)) {
|
|
y = MIN (y, result.y + result.h);
|
|
y = MAX (y - result.y, 0);
|
|
gst_structure_set (structure, "pointer_y", G_TYPE_DOUBLE,
|
|
(gdouble) y * yscale, NULL);
|
|
}
|
|
|
|
gst_pad_send_event (peer, event);
|
|
gst_object_unref (peer);
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_navigation_init (GstNavigationInterface * iface)
|
|
{
|
|
iface->send_event = gst_osx_video_sink_navigation_send_event;
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_set_window_handle (GstVideoOverlay * overlay, guintptr handle_id)
|
|
{
|
|
GstOSXVideoSink *osxvideosink = GST_OSX_VIDEO_SINK (overlay);
|
|
gulong window_id = (gulong) handle_id;
|
|
|
|
if (osxvideosink->superview) {
|
|
GST_INFO_OBJECT (osxvideosink, "old xwindow id %p", osxvideosink->superview);
|
|
if (osxvideosink->osxwindow) {
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink,
|
|
osxvideosink->osxwindow->gstview,
|
|
@selector(removeFromSuperview:), (id)nil, YES);
|
|
}
|
|
[osxvideosink->superview release];
|
|
|
|
}
|
|
|
|
GST_INFO_OBJECT (osxvideosink, "set xwindow id 0x%lx", window_id);
|
|
osxvideosink->superview = [((NSView *) window_id) retain];
|
|
if (osxvideosink->osxwindow) {
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink,
|
|
osxvideosink->osxwindow->gstview,
|
|
@selector(addToSuperview:), osxvideosink->superview, YES);
|
|
if (window_id) {
|
|
osxvideosink->osxwindow->internal = FALSE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_xoverlay_init (GstVideoOverlayInterface * iface)
|
|
{
|
|
iface->set_window_handle = gst_osx_video_sink_set_window_handle;
|
|
iface->expose = NULL;
|
|
iface->handle_events = NULL;
|
|
}
|
|
|
|
/* ============================================================= */
|
|
/* */
|
|
/* Public Methods */
|
|
/* */
|
|
/* ============================================================= */
|
|
|
|
/* =========================================== */
|
|
/* */
|
|
/* Object typing & Creation */
|
|
/* */
|
|
/* =========================================== */
|
|
|
|
GType
|
|
gst_osx_video_sink_get_type (void)
|
|
{
|
|
static GType osxvideosink_type = 0;
|
|
|
|
if (!osxvideosink_type) {
|
|
static const GTypeInfo osxvideosink_info = {
|
|
sizeof (GstOSXVideoSinkClass),
|
|
gst_osx_video_sink_base_init,
|
|
NULL,
|
|
(GClassInitFunc) gst_osx_video_sink_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstOSXVideoSink),
|
|
0,
|
|
(GInstanceInitFunc) gst_osx_video_sink_init,
|
|
};
|
|
|
|
static const GInterfaceInfo overlay_info = {
|
|
(GInterfaceInitFunc) gst_osx_video_sink_xoverlay_init,
|
|
NULL,
|
|
NULL,
|
|
};
|
|
|
|
static const GInterfaceInfo navigation_info = {
|
|
(GInterfaceInitFunc) gst_osx_video_sink_navigation_init,
|
|
NULL,
|
|
NULL,
|
|
};
|
|
osxvideosink_type = g_type_register_static (GST_TYPE_VIDEO_SINK,
|
|
"GstOSXVideoSink", &osxvideosink_info, 0);
|
|
|
|
g_type_add_interface_static (osxvideosink_type, GST_TYPE_VIDEO_OVERLAY,
|
|
&overlay_info);
|
|
g_type_add_interface_static (osxvideosink_type, GST_TYPE_NAVIGATION,
|
|
&navigation_info);
|
|
}
|
|
|
|
return osxvideosink_type;
|
|
}
|
|
|
|
@implementation GstWindowDelegate
|
|
- (id) initWithSink: (GstOSXVideoSink *) sink
|
|
{
|
|
self = [super init];
|
|
self->osxvideosink = sink;
|
|
return self;
|
|
}
|
|
|
|
- (void)windowWillClose:(NSNotification *)notification {
|
|
/* Only handle close events if the window was closed manually by the user
|
|
* and not becuase of a state change state to READY */
|
|
if (!osxvideosink->osxwindow->closed) {
|
|
osxvideosink->osxwindow->closed = TRUE;
|
|
GST_ELEMENT_ERROR (osxvideosink, RESOURCE, NOT_FOUND, ("Output window was closed"), (NULL));
|
|
gst_osx_video_sink_osxwindow_destroy(osxvideosink);
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@ implementation GstOSXVideoSinkObject
|
|
|
|
-(id) initWithSink: (GstOSXVideoSink*) sink
|
|
{
|
|
self = [super init];
|
|
self->osxvideosink = sink;
|
|
return self;
|
|
}
|
|
|
|
-(void) createInternalWindow
|
|
{
|
|
GstOSXWindow *osxwindow = osxvideosink->osxwindow;
|
|
ProcessSerialNumber psn;
|
|
NSRect rect;
|
|
unsigned int mask;
|
|
|
|
osxwindow->internal = TRUE;
|
|
|
|
mask = NSTitledWindowMask |
|
|
NSClosableWindowMask |
|
|
NSResizableWindowMask |
|
|
NSTexturedBackgroundWindowMask |
|
|
NSMiniaturizableWindowMask;
|
|
|
|
rect.origin.x = 100.0;
|
|
rect.origin.y = 100.0;
|
|
rect.size.width = (float) osxwindow->width;
|
|
rect.size.height = (float) osxwindow->height;
|
|
|
|
#ifndef RUN_NS_APP_THREAD
|
|
if (!osxvideosink->app_started) {
|
|
[NSApplication sharedApplication];
|
|
[NSApp finishLaunching];
|
|
osxvideosink->app_started = TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (!GetCurrentProcess(&psn)) {
|
|
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
|
|
SetFrontProcess(&psn);
|
|
}
|
|
|
|
osxwindow->win =[[GstOSXVideoSinkWindow alloc]
|
|
initWithContentNSRect: rect
|
|
styleMask: mask
|
|
backing: NSBackingStoreBuffered
|
|
defer: NO
|
|
screen: nil];
|
|
GST_DEBUG("VideoSinkWindow created, %p", osxwindow->win);
|
|
[osxwindow->win makeKeyAndOrderFront:NSApp];
|
|
osxwindow->gstview =[osxwindow->win gstView];
|
|
[osxwindow->win setDelegate:[[GstWindowDelegate alloc]
|
|
initWithSink:osxvideosink]];
|
|
|
|
}
|
|
|
|
#ifdef RUN_NS_APP_THREAD
|
|
+ (BOOL) isMainThread
|
|
{
|
|
/* FIXME: ideally we should return YES only for ->ns_app_thread here */
|
|
return YES;
|
|
}
|
|
#endif
|
|
|
|
- (void) resize
|
|
{
|
|
GstOSXWindow *osxwindow = osxvideosink->osxwindow;
|
|
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
GST_INFO_OBJECT (osxvideosink, "resizing");
|
|
NSSize size = {osxwindow->width, osxwindow->height};
|
|
if (osxwindow->internal) {
|
|
[osxwindow->win setContentSize:size];
|
|
}
|
|
if (osxwindow->gstview) {
|
|
[osxwindow->gstview setVideoSize :(int)osxwindow->width :(int)osxwindow->height];
|
|
}
|
|
GST_INFO_OBJECT (osxvideosink, "done");
|
|
|
|
[pool release];
|
|
}
|
|
|
|
- (void) showFrame: (GstBufferObject *) object
|
|
{
|
|
GstMapInfo info;
|
|
guint8 *viewdata;
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
GstBuffer *buf = object->buf;
|
|
|
|
if (osxvideosink->osxwindow != NULL)
|
|
{
|
|
gst_buffer_map (buf, &info, GST_MAP_READ);
|
|
viewdata = (guint8 *) [osxvideosink->osxwindow->gstview getTextureBuffer];
|
|
|
|
if (G_UNLIKELY (viewdata == NULL)) {
|
|
GST_ELEMENT_ERROR (osxvideosink, RESOURCE, WRITE,
|
|
("Could not get a texture buffer"), (NULL));
|
|
} else {
|
|
memcpy (viewdata, info.data, info.size);
|
|
[osxvideosink->osxwindow->gstview displayTexture];
|
|
gst_buffer_unmap (buf, &info);
|
|
}
|
|
}
|
|
|
|
[object release];
|
|
|
|
[pool release];
|
|
}
|
|
|
|
-(void) destroy
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
GstOSXWindow *osxwindow;
|
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
osxwindow = osxvideosink->osxwindow;
|
|
osxvideosink->osxwindow = NULL;
|
|
|
|
if (osxwindow) {
|
|
if (osxvideosink->superview) {
|
|
[osxwindow->gstview removeFromSuperview];
|
|
}
|
|
[osxwindow->gstview release];
|
|
if (osxwindow->internal) {
|
|
if (!osxwindow->closed) {
|
|
osxwindow->closed = TRUE;
|
|
[osxwindow->win release];
|
|
}
|
|
}
|
|
|
|
g_free (osxwindow);
|
|
}
|
|
[pool release];
|
|
}
|
|
|
|
#ifdef RUN_NS_APP_THREAD
|
|
-(void) nsAppThread
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
GstOSXVideoSink *sink = osxvideosink;
|
|
|
|
/* set the main runloop as the runloop for the current thread. This has the
|
|
* effect that calling NSApp nextEventMatchingMask:untilDate:inMode:dequeue
|
|
* runs the main runloop.
|
|
*/
|
|
_CFRunLoopSetCurrent(CFRunLoopGetMain());
|
|
|
|
/* this is needed to make IsMainThread checks in core foundation work from the
|
|
* current thread
|
|
*/
|
|
_CFMainPThread = pthread_self();
|
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
[NSApplication sharedApplication];
|
|
[NSApp finishLaunching];
|
|
|
|
g_mutex_lock (&sink->loop_thread_lock);
|
|
sink->app_started = TRUE;
|
|
g_cond_signal (&sink->loop_thread_cond);
|
|
g_mutex_unlock (&sink->loop_thread_lock);
|
|
|
|
/* run the loop */
|
|
run_ns_app_loop ();
|
|
|
|
[pool release];
|
|
}
|
|
#endif
|
|
|
|
-(void) checkMainRunLoop
|
|
{
|
|
g_mutex_lock (&osxvideosink->mrl_check_lock);
|
|
g_cond_signal (&osxvideosink->mrl_check_cond);
|
|
g_mutex_unlock (&osxvideosink->mrl_check_lock);
|
|
}
|
|
|
|
@end
|
|
|
|
@ implementation GstBufferObject
|
|
-(id) initWithBuffer: (GstBuffer*) buffer
|
|
{
|
|
self = [super init];
|
|
gst_buffer_ref(buffer);
|
|
self->buf = buffer;
|
|
return self;
|
|
}
|
|
|
|
-(void) dealloc{
|
|
gst_buffer_unref(buf);
|
|
[super dealloc];
|
|
}
|
|
@end
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
|
|
if (!gst_element_register (plugin, "osxvideosink",
|
|
GST_RANK_PRIMARY, GST_TYPE_OSX_VIDEO_SINK))
|
|
return FALSE;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_sink, "osxvideosink", 0,
|
|
"osxvideosink element");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
osxvideo,
|
|
"OSX native video output plugin",
|
|
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|