mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 08:11:16 +00:00
f60c87769f
This is no longer needed since the introduction of `gst_macos_main()` in 1.22. Before that existed, we had a patch for GLib in Cerbero, which did work but made it impossible to update GLib at all. The code being removed was a fail-safe in case of running without said patch being applied. It's no longer needed, since for macOS we just wrap our GStreamer with an NSApplication using `gst_macos_main()`. Warnings will be displayed if no NSApp/NSRunLoop is found wherever needed, pointing the user towards using the new API. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4366>
832 lines
24 KiB
Objective-C
832 lines
24 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.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <gst/video/videooverlay.h>
|
|
#include <gst/video/navigation.h>
|
|
#include <gst/video/video.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
|
|
|
|
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 GstOSXVideoSinkClass *sink_class = NULL;
|
|
static GstVideoSinkClass *parent_class = NULL;
|
|
|
|
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
|
|
#define NSEventMaskAny NSAnyEventMask
|
|
#define NSWindowStyleMaskTitled NSTitledWindowMask
|
|
#define NSWindowStyleMaskClosable NSClosableWindowMask
|
|
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
|
#define NSWindowStyleMaskTexturedBackground NSTexturedBackgroundWindowMask
|
|
#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
|
|
#endif
|
|
|
|
/* 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)
|
|
{
|
|
|
|
NSThread *thread;
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
if (sink_class->ns_app_thread == NULL){
|
|
thread = [NSThread mainThread];
|
|
} else {
|
|
thread = sink_class->ns_app_thread;
|
|
}
|
|
|
|
[object performSelector:function onThread:thread
|
|
withObject:data waitUntilDone:waitUntilDone];
|
|
[pool release];
|
|
}
|
|
|
|
/* 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;
|
|
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];
|
|
|
|
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 {
|
|
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);
|
|
[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);
|
|
|
|
gst_video_info_from_caps (&osxvideosink->info, caps);
|
|
|
|
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;
|
|
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:
|
|
g_warning ("The \"embed\" property of osxvideosink is deprecated and "
|
|
"has no effect anymore. Use the GstVideoOverlay "
|
|
"instead.");
|
|
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, FALSE);
|
|
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 gboolean
|
|
gst_osx_video_sink_propose_allocation (GstBaseSink * base_sink, GstQuery * query)
|
|
{
|
|
gst_query_add_allocation_meta (query,
|
|
GST_VIDEO_META_API_TYPE, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_osx_video_sink_init (GstOSXVideoSink * sink)
|
|
{
|
|
if ([[NSRunLoop mainRunLoop] currentMode] == nil)
|
|
g_warning ("An NSRunLoop needs to be running on the main thread "
|
|
"to ensure correct behaviour on macOS. Use gst_macos_main() or call "
|
|
"[NSApplication sharedApplication] in your code before using this element.");
|
|
|
|
sink->osxwindow = NULL;
|
|
sink->superview = NULL;
|
|
sink->osxvideosinkobject = [[GstOSXVideoSinkObject alloc] initWithSink:sink];
|
|
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, "macOS Video sink",
|
|
"Sink/Video", "macOS native videosink",
|
|
"Zaheer Abbas Merali <zaheerabbas at merali dot org>");
|
|
|
|
gst_element_class_add_static_pad_template (element_class, &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_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);
|
|
sink_class = klass;
|
|
|
|
klass->run_loop_state = GST_OSX_VIDEO_SINK_RUN_LOOP_STATE_UNKNOWN;
|
|
klass->ns_app_thread = NULL;
|
|
|
|
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;
|
|
gstbasesink_class->propose_allocation = gst_osx_video_sink_propose_allocation;
|
|
gstelement_class->change_state = gst_osx_video_sink_change_state;
|
|
|
|
g_object_class_install_property (gobject_class, ARG_EMBED,
|
|
g_param_spec_boolean ("embed", "embed", "For ABI compatibility only, do not use",
|
|
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
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 = { 0, };
|
|
GstVideoRectangle dst = { 0, };
|
|
GstVideoRectangle 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);
|
|
NSView *view = (NSView *) handle_id;
|
|
|
|
gst_osx_video_sink_call_from_main_thread(osxvideosink,
|
|
osxvideosink->osxvideosinkobject,
|
|
@selector(setView:), view, YES);
|
|
}
|
|
|
|
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 because of a state change state to READY */
|
|
if (osxvideosink->osxwindow == NULL) {
|
|
return;
|
|
}
|
|
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 = gst_object_ref (sink);
|
|
return self;
|
|
}
|
|
|
|
-(void) dealloc {
|
|
gst_object_unref (osxvideosink);
|
|
[super dealloc];
|
|
}
|
|
|
|
-(void) createInternalWindow
|
|
{
|
|
GstOSXWindow *osxwindow = osxvideosink->osxwindow;
|
|
NSRect rect;
|
|
unsigned int mask;
|
|
|
|
[NSApplication sharedApplication];
|
|
|
|
osxwindow->internal = TRUE;
|
|
|
|
mask = NSWindowStyleMaskTitled |
|
|
NSWindowStyleMaskClosable |
|
|
NSWindowStyleMaskResizable |
|
|
NSWindowStyleMaskTexturedBackground |
|
|
NSWindowStyleMaskMiniaturizable;
|
|
|
|
rect.origin.x = 100.0;
|
|
rect.origin.y = 100.0;
|
|
rect.size.width = (float) osxwindow->width;
|
|
rect.size.height = (float) osxwindow->height;
|
|
|
|
osxwindow->win =[[[GstOSXVideoSinkWindow alloc]
|
|
initWithContentNSRect: rect
|
|
styleMask: mask
|
|
backing: NSBackingStoreBuffered
|
|
defer: NO
|
|
screen: nil] retain];
|
|
GST_DEBUG("VideoSinkWindow created, %p", osxwindow->win);
|
|
[osxwindow->win orderFrontRegardless];
|
|
osxwindow->gstview =[osxwindow->win gstView];
|
|
[osxwindow->win setDelegate:[[GstWindowDelegate alloc]
|
|
initWithSink:osxvideosink]];
|
|
|
|
}
|
|
|
|
- (void) setView: (NSView*)view
|
|
{
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
if (osxvideosink->superview) {
|
|
GST_INFO_OBJECT (osxvideosink, "old xwindow id %p", osxvideosink->superview);
|
|
if (osxvideosink->osxwindow) {
|
|
[osxvideosink->osxwindow->gstview removeFromSuperview];
|
|
}
|
|
[osxvideosink->superview release];
|
|
}
|
|
if (osxvideosink->osxwindow != NULL && view != NULL) {
|
|
if (osxvideosink->osxwindow->internal) {
|
|
GST_INFO_OBJECT (osxvideosink, "closing internal window");
|
|
osxvideosink->osxwindow->closed = TRUE;
|
|
[osxvideosink->osxwindow->win close];
|
|
[osxvideosink->osxwindow->win release];
|
|
}
|
|
}
|
|
|
|
GST_INFO_OBJECT (osxvideosink, "set xwindow id %p", view);
|
|
osxvideosink->superview = [view retain];
|
|
if (osxvideosink->osxwindow) {
|
|
[osxvideosink->osxwindow->gstview addToSuperview: osxvideosink->superview];
|
|
if (view) {
|
|
osxvideosink->osxwindow->internal = FALSE;
|
|
}
|
|
}
|
|
|
|
[pool release];
|
|
}
|
|
|
|
- (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
|
|
{
|
|
GstVideoFrame frame;
|
|
guint8 *readp, *writep;
|
|
gint i, active_width, stride;
|
|
guint8 *texture_buffer;
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
GstBuffer *buf = object->buf;
|
|
|
|
GST_OBJECT_LOCK (osxvideosink);
|
|
if (osxvideosink->osxwindow == NULL)
|
|
goto no_window;
|
|
|
|
texture_buffer = (guint8 *) [osxvideosink->osxwindow->gstview getTextureBuffer];
|
|
if (G_UNLIKELY (texture_buffer == NULL))
|
|
goto no_texture_buffer;
|
|
|
|
if (!gst_video_frame_map (&frame, &osxvideosink->info, buf, GST_MAP_READ))
|
|
goto no_map;
|
|
|
|
readp = GST_VIDEO_FRAME_PLANE_DATA (&frame, 0);
|
|
stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame, 0);
|
|
writep = texture_buffer;
|
|
active_width = GST_VIDEO_SINK_WIDTH (osxvideosink) * sizeof (short);
|
|
for (i = 0; i < GST_VIDEO_SINK_HEIGHT (osxvideosink); i++) {
|
|
memcpy (writep, readp, active_width);
|
|
writep += active_width;
|
|
readp += stride;
|
|
}
|
|
[osxvideosink->osxwindow->gstview displayTexture];
|
|
|
|
gst_video_frame_unmap (&frame);
|
|
|
|
out:
|
|
GST_OBJECT_UNLOCK (osxvideosink);
|
|
[object release];
|
|
|
|
[pool release];
|
|
return;
|
|
|
|
no_map:
|
|
GST_WARNING_OBJECT (osxvideosink, "couldn't map frame");
|
|
goto out;
|
|
|
|
no_window:
|
|
GST_WARNING_OBJECT (osxvideosink, "not showing frame since we have no window (!?)");
|
|
goto out;
|
|
|
|
no_texture_buffer:
|
|
GST_ELEMENT_ERROR (osxvideosink, RESOURCE, WRITE, (NULL),
|
|
("the texture buffer is NULL"));
|
|
goto out;
|
|
}
|
|
|
|
-(void) destroy
|
|
{
|
|
NSAutoreleasePool *pool;
|
|
GstOSXWindow *osxwindow;
|
|
|
|
pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
GST_OBJECT_LOCK (osxvideosink);
|
|
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 close];
|
|
[osxwindow->win release];
|
|
}
|
|
}
|
|
g_free (osxwindow);
|
|
}
|
|
GST_OBJECT_UNLOCK (osxvideosink);
|
|
|
|
[pool release];
|
|
}
|
|
|
|
@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_MARGINAL, 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)
|