mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 10:40:34 +00:00
acc098a736
Binding the vertex array to 0 will unbind everything else already. In the previous order older versions of the Intel GL driver caused errors to be printed for every single call when disabling the vertex attrib arrays after binding the vertex array to 0.
1013 lines
29 KiB
Objective-C
1013 lines
29 KiB
Objective-C
/*
|
|
* GStreamer
|
|
* Copyright (C) 2015 Matthew Waters <matthew@centricular.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.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-caopengllayersink
|
|
*
|
|
* caopengllayersink renders incoming video frames to CAOpenGLLayer that
|
|
* can be retrieved through the layer property and placed in the Core
|
|
* Animation render tree.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "caopengllayersink.h"
|
|
#include "gstglsinkbin.h"
|
|
#include <QuartzCore/QuartzCore.h>
|
|
|
|
GST_DEBUG_CATEGORY (gst_debug_ca_sink);
|
|
#define GST_CAT_DEFAULT gst_debug_ca_sink
|
|
|
|
typedef GstGLSinkBin GstCAOpenGLLayerSinkBin;
|
|
typedef GstGLSinkBinClass GstCAOpenGLLayerSinkBinClass;
|
|
|
|
G_DEFINE_TYPE (GstCAOpenGLLayerSinkBin, gst_ca_opengl_layer_sink_bin,
|
|
GST_TYPE_GL_SINK_BIN);
|
|
|
|
enum
|
|
{
|
|
PROP_BIN_0,
|
|
PROP_BIN_QOS,
|
|
PROP_BIN_FORCE_ASPECT_RATIO,
|
|
PROP_BIN_LAST_SAMPLE,
|
|
PROP_BIN_LAYER,
|
|
};
|
|
|
|
static void
|
|
_on_notify_layer (GObject * object, GParamSpec *pspec, gpointer user_data)
|
|
{
|
|
GstCAOpenGLLayerSinkBin *self = user_data;
|
|
|
|
g_object_notify (G_OBJECT (self), "layer");
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_bin_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * param_spec)
|
|
{
|
|
g_object_set_property (G_OBJECT (GST_GL_SINK_BIN (object)->sink),
|
|
param_spec->name, value);
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_bin_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * param_spec)
|
|
{
|
|
g_object_get_property (G_OBJECT (GST_GL_SINK_BIN (object)->sink),
|
|
param_spec->name, value);
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_bin_init (GstCAOpenGLLayerSinkBin * self)
|
|
{
|
|
gpointer *sink = g_object_new (GST_TYPE_CA_OPENGL_LAYER_SINK, NULL);
|
|
|
|
g_signal_connect (sink, "notify::layer", G_CALLBACK (_on_notify_layer), self);
|
|
|
|
gst_gl_sink_bin_finish_init_with_element (GST_GL_SINK_BIN (self),
|
|
GST_ELEMENT (sink));
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_bin_class_init (GstCAOpenGLLayerSinkBinClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->get_property = gst_ca_opengl_layer_sink_bin_get_property;
|
|
gobject_class->set_property = gst_ca_opengl_layer_sink_bin_set_property;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BIN_FORCE_ASPECT_RATIO,
|
|
g_param_spec_boolean ("force-aspect-ratio",
|
|
"Force aspect ratio",
|
|
"When enabled, scaling will respect original aspect ratio", TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BIN_LAST_SAMPLE,
|
|
g_param_spec_boxed ("last-sample", "Last Sample",
|
|
"The last sample received in the sink", GST_TYPE_SAMPLE,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BIN_LAYER,
|
|
g_param_spec_pointer ("layer", "CAOpenGLLayer",
|
|
"OpenGL Core Animation layer",
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BIN_QOS,
|
|
g_param_spec_boolean ("qos", "Quality of Service",
|
|
"Generate Quality-of-Service events upstream", TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
#define GST_CA_OPENGL_LAYER_SINK_GET_LOCK(glsink) \
|
|
(GST_CA_OPENGL_LAYER_SINK(glsink)->drawing_lock)
|
|
#define GST_CA_OPENGL_LAYER_SINK_LOCK(glsink) \
|
|
(g_mutex_lock(&GST_CA_OPENGL_LAYER_SINK_GET_LOCK (glsink)))
|
|
#define GST_CA_OPENGL_LAYER_SINK_UNLOCK(glsink) \
|
|
(g_mutex_unlock(&GST_CA_OPENGL_LAYER_SINK_GET_LOCK (glsink)))
|
|
|
|
#define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
|
|
#define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
|
|
#define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
|
|
#define USING_GLES2(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 2, 0))
|
|
#define USING_GLES3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 3, 0))
|
|
|
|
#define SUPPORTED_GL_APIS GST_GL_API_OPENGL | GST_GL_API_GLES2 | GST_GL_API_OPENGL3
|
|
|
|
static void gst_ca_opengl_layer_sink_thread_init_redisplay (GstCAOpenGLLayerSink * ca_sink);
|
|
static void gst_ca_opengl_layer_sink_cleanup_glthread (GstCAOpenGLLayerSink * ca_sink);
|
|
static void gst_ca_opengl_layer_sink_on_resize (GstCAOpenGLLayerSink * ca_sink,
|
|
gint width, gint height);
|
|
static void gst_ca_opengl_layer_sink_on_draw (GstCAOpenGLLayerSink * ca_sink);
|
|
|
|
static void gst_ca_opengl_layer_sink_finalize (GObject * object);
|
|
static void gst_ca_opengl_layer_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * param_spec);
|
|
static void gst_ca_opengl_layer_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * param_spec);
|
|
|
|
static gboolean gst_ca_opengl_layer_sink_stop (GstBaseSink * bsink);
|
|
|
|
static gboolean gst_ca_opengl_layer_sink_query (GstBaseSink * bsink, GstQuery * query);
|
|
static void gst_ca_opengl_layer_sink_set_context (GstElement * element,
|
|
GstContext * context);
|
|
|
|
static GstStateChangeReturn gst_ca_opengl_layer_sink_change_state (GstElement *
|
|
element, GstStateChange transition);
|
|
|
|
static void gst_ca_opengl_layer_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
|
GstClockTime * start, GstClockTime * end);
|
|
static gboolean gst_ca_opengl_layer_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
|
|
static GstFlowReturn gst_ca_opengl_layer_sink_prepare (GstBaseSink * bsink,
|
|
GstBuffer * buf);
|
|
static GstFlowReturn gst_ca_opengl_layer_sink_show_frame (GstVideoSink * bsink,
|
|
GstBuffer * buf);
|
|
static gboolean gst_ca_opengl_layer_sink_propose_allocation (GstBaseSink * bsink,
|
|
GstQuery * query);
|
|
|
|
static GstStaticPadTemplate gst_ca_opengl_layer_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
|
|
"format = (string) RGBA, "
|
|
"width = " GST_VIDEO_SIZE_RANGE ", "
|
|
"height = " GST_VIDEO_SIZE_RANGE ", "
|
|
"framerate = " GST_VIDEO_FPS_RANGE ","
|
|
"texture-target = (string) 2D")
|
|
);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_FORCE_ASPECT_RATIO,
|
|
PROP_CONTEXT,
|
|
PROP_LAYER,
|
|
};
|
|
|
|
#define gst_ca_opengl_layer_sink_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstCAOpenGLLayerSink, gst_ca_opengl_layer_sink,
|
|
GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_ca_sink,
|
|
"caopengllayersink", 0, "CAOpenGLLayer Video Sink"));
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_class_init (GstCAOpenGLLayerSinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstBaseSinkClass *gstbasesink_class;
|
|
GstVideoSinkClass *gstvideosink_class;
|
|
GstElementClass *element_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstbasesink_class = (GstBaseSinkClass *) klass;
|
|
gstvideosink_class = (GstVideoSinkClass *) klass;
|
|
element_class = GST_ELEMENT_CLASS (klass);
|
|
|
|
gobject_class->set_property = gst_ca_opengl_layer_sink_set_property;
|
|
gobject_class->get_property = gst_ca_opengl_layer_sink_get_property;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
|
|
g_param_spec_boolean ("force-aspect-ratio",
|
|
"Force aspect ratio",
|
|
"When enabled, scaling will respect original aspect ratio", TRUE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_CONTEXT,
|
|
g_param_spec_object ("context",
|
|
"OpenGL context",
|
|
"Get OpenGL context",
|
|
GST_TYPE_GL_CONTEXT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_LAYER,
|
|
g_param_spec_pointer ("layer", "CAOpenGLLayer",
|
|
"OpenGL Core Animation layer",
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_set_metadata (element_class, "CAOpenGLLayer video sink",
|
|
"Sink/Video", "A video sink based on CAOpenGLLayer",
|
|
"Matthew Waters <matthew@centricular.com>");
|
|
|
|
gst_element_class_add_static_pad_template (element_class, &gst_ca_opengl_layer_sink_template);
|
|
|
|
gobject_class->finalize = gst_ca_opengl_layer_sink_finalize;
|
|
|
|
gstelement_class->change_state = gst_ca_opengl_layer_sink_change_state;
|
|
gstelement_class->set_context = gst_ca_opengl_layer_sink_set_context;
|
|
gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_ca_opengl_layer_sink_query);
|
|
gstbasesink_class->set_caps = gst_ca_opengl_layer_sink_set_caps;
|
|
gstbasesink_class->get_times = gst_ca_opengl_layer_sink_get_times;
|
|
gstbasesink_class->prepare = gst_ca_opengl_layer_sink_prepare;
|
|
gstbasesink_class->propose_allocation = gst_ca_opengl_layer_sink_propose_allocation;
|
|
gstbasesink_class->stop = gst_ca_opengl_layer_sink_stop;
|
|
|
|
gstvideosink_class->show_frame =
|
|
GST_DEBUG_FUNCPTR (gst_ca_opengl_layer_sink_show_frame);
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_init (GstCAOpenGLLayerSink * ca_sink)
|
|
{
|
|
ca_sink->display = NULL;
|
|
ca_sink->keep_aspect_ratio = TRUE;
|
|
ca_sink->stored_buffer = NULL;
|
|
ca_sink->redisplay_texture = 0;
|
|
|
|
g_mutex_init (&ca_sink->drawing_lock);
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_finalize (GObject * object)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
|
|
g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (object));
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (object);
|
|
|
|
g_mutex_clear (&ca_sink->drawing_lock);
|
|
|
|
if (ca_sink->layer) {
|
|
CFRelease(ca_sink->layer);
|
|
ca_sink->layer = NULL;
|
|
}
|
|
|
|
GST_DEBUG ("finalized");
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
|
|
g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (object));
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FORCE_ASPECT_RATIO:
|
|
{
|
|
ca_sink->keep_aspect_ratio = g_value_get_boolean (value);
|
|
break;
|
|
}
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
|
|
g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (object));
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_FORCE_ASPECT_RATIO:
|
|
g_value_set_boolean (value, ca_sink->keep_aspect_ratio);
|
|
break;
|
|
case PROP_CONTEXT:
|
|
g_value_set_object (value, ca_sink->context);
|
|
break;
|
|
case PROP_LAYER:
|
|
g_value_set_pointer (value, ca_sink->layer);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_create_layer (gpointer data)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink = data;
|
|
id layer;
|
|
|
|
if (!ca_sink->layer) {
|
|
layer = [[NSClassFromString(@"GstGLCAOpenGLLayer") alloc]
|
|
initWithGstGLContext:ca_sink->context];
|
|
|
|
ca_sink->layer = (__bridge_retained gpointer)layer;
|
|
[layer setDrawCallback:(GstGLWindowCB)gst_ca_opengl_layer_sink_on_draw
|
|
data:ca_sink notify:NULL];
|
|
[layer setResizeCallback:(GstGLWindowResizeCB)gst_ca_opengl_layer_sink_on_resize
|
|
data:ca_sink notify:NULL];
|
|
g_object_notify (G_OBJECT (ca_sink), "layer");
|
|
}
|
|
}
|
|
|
|
static void
|
|
_invoke_on_main (GstGLWindowCB func, gpointer data)
|
|
{
|
|
if ([NSThread isMainThread]) {
|
|
func (data);
|
|
} else {
|
|
dispatch_sync (dispatch_get_main_queue (), ^{
|
|
func (data);
|
|
});
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
_ensure_gl_setup (GstCAOpenGLLayerSink * ca_sink)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!gst_gl_ensure_element_data (ca_sink, &ca_sink->display,
|
|
&ca_sink->other_context))
|
|
return FALSE;
|
|
|
|
gst_gl_display_filter_gl_api (ca_sink->display, SUPPORTED_GL_APIS);
|
|
|
|
if (!ca_sink->context) {
|
|
if (!gst_gl_display_create_context (ca_sink->display,
|
|
ca_sink->other_context, &ca_sink->context, &error)) {
|
|
goto context_error;
|
|
}
|
|
}
|
|
|
|
if (!ca_sink->layer)
|
|
_invoke_on_main ((GstGLWindowCB) _create_layer, ca_sink);
|
|
|
|
return TRUE;
|
|
|
|
context_error:
|
|
{
|
|
GST_ELEMENT_ERROR (ca_sink, RESOURCE, NOT_FOUND, ("%s", error->message),
|
|
(NULL));
|
|
gst_object_unref (ca_sink->context);
|
|
ca_sink->context = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_ca_opengl_layer_sink_query (GstBaseSink * bsink, GstQuery * query)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CONTEXT:
|
|
{
|
|
if (gst_gl_handle_context_query ((GstElement *) ca_sink, query,
|
|
ca_sink->display, ca_sink->context, ca_sink->other_context))
|
|
return TRUE;
|
|
break;
|
|
}
|
|
case GST_QUERY_DRAIN:
|
|
{
|
|
GstBuffer *buf = NULL;
|
|
|
|
GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
|
|
ca_sink->redisplay_texture = 0;
|
|
buf = ca_sink->stored_buffer;
|
|
ca_sink->stored_buffer = NULL;
|
|
GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
|
|
|
|
if (buf)
|
|
gst_buffer_unref (buf);
|
|
|
|
gst_buffer_replace (&ca_sink->next_buffer, NULL);
|
|
gst_buffer_replace (&ca_sink->next_sync, NULL);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
|
|
}
|
|
|
|
static gboolean
|
|
gst_ca_opengl_layer_sink_stop (GstBaseSink * bsink)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
|
|
|
|
if (ca_sink->gl_caps) {
|
|
gst_caps_unref (ca_sink->gl_caps);
|
|
ca_sink->gl_caps = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_set_context (GstElement * element, GstContext * context)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (element);
|
|
|
|
gst_gl_handle_set_context (element, context, &ca_sink->display,
|
|
&ca_sink->other_context);
|
|
|
|
if (ca_sink->display)
|
|
gst_gl_display_filter_gl_api (ca_sink->display, SUPPORTED_GL_APIS);
|
|
|
|
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_ca_opengl_layer_sink_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
|
|
|
GST_DEBUG ("changing state: %s => %s",
|
|
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
|
|
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
_ensure_gl_setup (ca_sink);
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
return ret;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
{
|
|
/* mark the redisplay_texture as unavailable (=0)
|
|
* to avoid drawing
|
|
*/
|
|
GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
|
|
ca_sink->redisplay_texture = 0;
|
|
|
|
gst_buffer_replace (&ca_sink->stored_sync, NULL);
|
|
|
|
if (ca_sink->stored_buffer) {
|
|
gst_buffer_unref (ca_sink->stored_buffer);
|
|
ca_sink->stored_buffer = NULL;
|
|
}
|
|
gst_buffer_replace (&ca_sink->next_buffer, NULL);
|
|
gst_buffer_replace (&ca_sink->next_sync, NULL);
|
|
GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
|
|
|
|
GST_VIDEO_SINK_WIDTH (ca_sink) = 1;
|
|
GST_VIDEO_SINK_HEIGHT (ca_sink) = 1;
|
|
if (ca_sink->context) {
|
|
gst_object_unref (ca_sink->context);
|
|
ca_sink->context = NULL;
|
|
}
|
|
|
|
if (ca_sink->display) {
|
|
gst_object_unref (ca_sink->display);
|
|
ca_sink->display = NULL;
|
|
}
|
|
break;
|
|
}
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
if (ca_sink->layer) {
|
|
CFRelease(ca_sink->layer);
|
|
ca_sink->layer = NULL;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
|
GstClockTime * start, GstClockTime * end)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
|
|
|
|
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
|
|
*start = GST_BUFFER_TIMESTAMP (buf);
|
|
if (GST_BUFFER_DURATION_IS_VALID (buf))
|
|
*end = *start + GST_BUFFER_DURATION (buf);
|
|
else {
|
|
if (GST_VIDEO_INFO_FPS_N (&ca_sink->info) > 0) {
|
|
*end = *start +
|
|
gst_util_uint64_scale_int (GST_SECOND,
|
|
GST_VIDEO_INFO_FPS_D (&ca_sink->info),
|
|
GST_VIDEO_INFO_FPS_N (&ca_sink->info));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_ca_opengl_layer_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
gint width;
|
|
gint height;
|
|
gboolean ok;
|
|
gint par_n, par_d;
|
|
gint display_par_n, display_par_d;
|
|
guint display_ratio_num, display_ratio_den;
|
|
GstVideoInfo vinfo;
|
|
|
|
GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
|
|
|
|
ok = gst_video_info_from_caps (&vinfo, caps);
|
|
if (!ok)
|
|
return FALSE;
|
|
|
|
width = GST_VIDEO_INFO_WIDTH (&vinfo);
|
|
height = GST_VIDEO_INFO_HEIGHT (&vinfo);
|
|
|
|
par_n = GST_VIDEO_INFO_PAR_N (&vinfo);
|
|
par_d = GST_VIDEO_INFO_PAR_D (&vinfo);
|
|
|
|
if (!par_n)
|
|
par_n = 1;
|
|
|
|
display_par_n = 1;
|
|
display_par_d = 1;
|
|
|
|
ok = gst_video_calculate_display_ratio (&display_ratio_num,
|
|
&display_ratio_den, width, height, par_n, par_d, display_par_n,
|
|
display_par_d);
|
|
|
|
if (!ok)
|
|
return FALSE;
|
|
|
|
GST_TRACE ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
|
|
display_par_d);
|
|
|
|
if (height % display_ratio_den == 0) {
|
|
GST_DEBUG ("keeping video height");
|
|
GST_VIDEO_SINK_WIDTH (ca_sink) = (guint)
|
|
gst_util_uint64_scale_int (height, display_ratio_num,
|
|
display_ratio_den);
|
|
GST_VIDEO_SINK_HEIGHT (ca_sink) = height;
|
|
} else if (width % display_ratio_num == 0) {
|
|
GST_DEBUG ("keeping video width");
|
|
GST_VIDEO_SINK_WIDTH (ca_sink) = width;
|
|
GST_VIDEO_SINK_HEIGHT (ca_sink) = (guint)
|
|
gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
|
|
} else {
|
|
GST_DEBUG ("approximating while keeping video height");
|
|
GST_VIDEO_SINK_WIDTH (ca_sink) = (guint)
|
|
gst_util_uint64_scale_int (height, display_ratio_num,
|
|
display_ratio_den);
|
|
GST_VIDEO_SINK_HEIGHT (ca_sink) = height;
|
|
}
|
|
GST_DEBUG ("scaling to %dx%d", GST_VIDEO_SINK_WIDTH (ca_sink),
|
|
GST_VIDEO_SINK_HEIGHT (ca_sink));
|
|
|
|
ca_sink->info = vinfo;
|
|
if (!_ensure_gl_setup (ca_sink))
|
|
return FALSE;
|
|
|
|
ca_sink->caps_change = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_ca_opengl_layer_sink_prepare (GstBaseSink * bsink, GstBuffer * buf)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
GstBuffer *next_sync, *old_sync, *old_buffer;
|
|
GstVideoFrame gl_frame;
|
|
GstGLSyncMeta *sync_meta;
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
|
|
|
|
GST_TRACE ("preparing buffer:%p", buf);
|
|
|
|
if (GST_VIDEO_SINK_WIDTH (ca_sink) < 1 ||
|
|
GST_VIDEO_SINK_HEIGHT (ca_sink) < 1) {
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
|
|
if (!_ensure_gl_setup (ca_sink))
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
|
|
if (!gst_video_frame_map (&gl_frame, &ca_sink->info, buf,
|
|
GST_MAP_READ | GST_MAP_GL)) {
|
|
goto upload_failed;
|
|
}
|
|
|
|
ca_sink->next_tex = *(guint *) gl_frame.data[0];
|
|
|
|
next_sync = gst_buffer_new ();
|
|
sync_meta = gst_buffer_add_gl_sync_meta (ca_sink->context, next_sync);
|
|
gst_gl_sync_meta_set_sync_point (sync_meta, ca_sink->context);
|
|
|
|
GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
|
|
ca_sink->next_tex = *(guint *) gl_frame.data[0];
|
|
|
|
old_buffer = ca_sink->next_buffer;
|
|
ca_sink->next_buffer = gst_buffer_ref (buf);
|
|
|
|
old_sync = ca_sink->next_sync;
|
|
ca_sink->next_sync = next_sync;
|
|
GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
|
|
|
|
if (old_buffer)
|
|
gst_buffer_unref (old_buffer);
|
|
if (old_sync)
|
|
gst_buffer_unref (old_sync);
|
|
|
|
gst_video_frame_unmap (&gl_frame);
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
upload_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (ca_sink, RESOURCE, NOT_FOUND,
|
|
("%s", "Failed to upload buffer"), (NULL));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_ca_opengl_layer_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink;
|
|
GstBuffer *stored_buffer, *old_sync;
|
|
|
|
GST_TRACE ("rendering buffer:%p", buf);
|
|
|
|
ca_sink = GST_CA_OPENGL_LAYER_SINK (vsink);
|
|
|
|
GST_TRACE ("redisplay texture:%u of size:%ux%u, window size:%ux%u",
|
|
ca_sink->next_tex, GST_VIDEO_INFO_WIDTH (&ca_sink->info),
|
|
GST_VIDEO_INFO_HEIGHT (&ca_sink->info),
|
|
GST_VIDEO_SINK_WIDTH (ca_sink),
|
|
GST_VIDEO_SINK_HEIGHT (ca_sink));
|
|
|
|
/* Avoid to release the texture while drawing */
|
|
GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
|
|
ca_sink->redisplay_texture = ca_sink->next_tex;
|
|
|
|
stored_buffer = ca_sink->stored_buffer;
|
|
ca_sink->stored_buffer = gst_buffer_ref (ca_sink->next_buffer);
|
|
|
|
old_sync = ca_sink->stored_sync;
|
|
ca_sink->stored_sync = gst_buffer_ref (ca_sink->next_sync);
|
|
GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
|
|
|
|
/* The layer will automatically call the draw callback to draw the new
|
|
* content */
|
|
[CATransaction begin];
|
|
[(__bridge GstGLCAOpenGLLayer *)(ca_sink->layer) setNeedsDisplay];
|
|
[CATransaction commit];
|
|
|
|
GST_TRACE ("post redisplay");
|
|
|
|
if (stored_buffer)
|
|
gst_buffer_unref (stored_buffer);
|
|
if (old_sync)
|
|
gst_buffer_unref (old_sync);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gboolean
|
|
gst_ca_opengl_layer_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
|
|
{
|
|
GstCAOpenGLLayerSink *ca_sink = GST_CA_OPENGL_LAYER_SINK (bsink);
|
|
GstBufferPool *pool = NULL;
|
|
GstStructure *config;
|
|
GstCaps *caps;
|
|
GstVideoInfo info;
|
|
guint size;
|
|
gboolean need_pool;
|
|
|
|
if (!_ensure_gl_setup (ca_sink))
|
|
return FALSE;
|
|
|
|
gst_query_parse_allocation (query, &caps, &need_pool);
|
|
|
|
if (caps == NULL)
|
|
goto no_caps;
|
|
|
|
if (!gst_video_info_from_caps (&info, caps))
|
|
goto invalid_caps;
|
|
|
|
/* the normal size of a frame */
|
|
size = info.size;
|
|
|
|
if (need_pool) {
|
|
GST_DEBUG_OBJECT (ca_sink, "create new pool");
|
|
|
|
pool = gst_gl_buffer_pool_new (ca_sink->context);
|
|
config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
|
|
|
|
if (!gst_buffer_pool_set_config (pool, config))
|
|
goto config_failed;
|
|
}
|
|
|
|
/* we need at least 2 buffer because we hold on to the last one */
|
|
gst_query_add_allocation_pool (query, pool, size, 2, 0);
|
|
if (pool)
|
|
gst_object_unref (pool);
|
|
|
|
if (ca_sink->context->gl_vtable->FenceSync)
|
|
gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
no_caps:
|
|
{
|
|
GST_DEBUG_OBJECT (bsink, "no caps specified");
|
|
return FALSE;
|
|
}
|
|
invalid_caps:
|
|
{
|
|
GST_DEBUG_OBJECT (bsink, "invalid caps specified");
|
|
return FALSE;
|
|
}
|
|
config_failed:
|
|
{
|
|
GST_DEBUG_OBJECT (bsink, "failed setting config");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
static const GLfloat vertices[] = {
|
|
1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
|
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
|
|
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
|
|
1.0f, -1.0f, 0.0f, 1.0f, 1.0f
|
|
};
|
|
|
|
static const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
|
|
/* *INDENT-ON* */
|
|
|
|
static void
|
|
_bind_buffer (GstCAOpenGLLayerSink * ca_sink)
|
|
{
|
|
const GstGLFuncs *gl = ca_sink->context->gl_vtable;
|
|
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, ca_sink->vbo_indices);
|
|
gl->BindBuffer (GL_ARRAY_BUFFER, ca_sink->vertex_buffer);
|
|
|
|
/* Load the vertex position */
|
|
gl->VertexAttribPointer (ca_sink->attr_position, 3, GL_FLOAT, GL_FALSE,
|
|
5 * sizeof (GLfloat), (void *) 0);
|
|
|
|
/* Load the texture coordinate */
|
|
gl->VertexAttribPointer (ca_sink->attr_texture, 2, GL_FLOAT, GL_FALSE,
|
|
5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
|
|
|
|
gl->EnableVertexAttribArray (ca_sink->attr_position);
|
|
gl->EnableVertexAttribArray (ca_sink->attr_texture);
|
|
}
|
|
|
|
static void
|
|
_unbind_buffer (GstCAOpenGLLayerSink * ca_sink)
|
|
{
|
|
const GstGLFuncs *gl = ca_sink->context->gl_vtable;
|
|
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
|
|
|
gl->DisableVertexAttribArray (ca_sink->attr_position);
|
|
gl->DisableVertexAttribArray (ca_sink->attr_texture);
|
|
}
|
|
|
|
/* Called in the gl thread */
|
|
static void
|
|
gst_ca_opengl_layer_sink_thread_init_redisplay (GstCAOpenGLLayerSink * ca_sink)
|
|
{
|
|
const GstGLFuncs *gl = ca_sink->context->gl_vtable;
|
|
GError *error = NULL;
|
|
|
|
if (!(ca_sink->redisplay_shader = gst_gl_shader_new_default (ca_sink->context, &error))) {
|
|
GST_ERROR_OBJECT (ca_sink, "Failed to link shader: %s", error->message);
|
|
gst_ca_opengl_layer_sink_cleanup_glthread (ca_sink);
|
|
return;
|
|
}
|
|
|
|
ca_sink->attr_position =
|
|
gst_gl_shader_get_attribute_location (ca_sink->redisplay_shader,
|
|
"a_position");
|
|
ca_sink->attr_texture =
|
|
gst_gl_shader_get_attribute_location (ca_sink->redisplay_shader,
|
|
"a_texcoord");
|
|
|
|
if (gl->GenVertexArrays) {
|
|
gl->GenVertexArrays (1, &ca_sink->vao);
|
|
gl->BindVertexArray (ca_sink->vao);
|
|
}
|
|
|
|
if (!ca_sink->vertex_buffer) {
|
|
gl->GenBuffers (1, &ca_sink->vertex_buffer);
|
|
gl->BindBuffer (GL_ARRAY_BUFFER, ca_sink->vertex_buffer);
|
|
gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
|
|
GL_STATIC_DRAW);
|
|
}
|
|
|
|
if (!ca_sink->vbo_indices) {
|
|
gl->GenBuffers (1, &ca_sink->vbo_indices);
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, ca_sink->vbo_indices);
|
|
gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
|
|
GL_STATIC_DRAW);
|
|
}
|
|
|
|
if (gl->GenVertexArrays) {
|
|
_bind_buffer (ca_sink);
|
|
gl->BindVertexArray (0);
|
|
}
|
|
|
|
gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_cleanup_glthread (GstCAOpenGLLayerSink * ca_sink)
|
|
{
|
|
const GstGLFuncs *gl = ca_sink->context->gl_vtable;
|
|
|
|
if (ca_sink->redisplay_shader) {
|
|
gst_object_unref (ca_sink->redisplay_shader);
|
|
ca_sink->redisplay_shader = NULL;
|
|
}
|
|
|
|
if (ca_sink->vao) {
|
|
gl->DeleteVertexArrays (1, &ca_sink->vao);
|
|
ca_sink->vao = 0;
|
|
}
|
|
|
|
if (ca_sink->vbo_indices) {
|
|
gl->DeleteBuffers (1, &ca_sink->vbo_indices);
|
|
ca_sink->vbo_indices = 0;
|
|
}
|
|
|
|
if (ca_sink->vertex_buffer) {
|
|
gl->DeleteBuffers (1, &ca_sink->vertex_buffer);
|
|
ca_sink->vertex_buffer = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_on_resize (GstCAOpenGLLayerSink * ca_sink, gint width, gint height)
|
|
{
|
|
/* Here ca_sink members (ex:ca_sink->info) have a life time of set_caps.
|
|
* It means that they cannot not change between two set_caps
|
|
*/
|
|
const GstGLFuncs *gl = ca_sink->context->gl_vtable;
|
|
|
|
GST_TRACE ("GL Window resized to %ux%u", width, height);
|
|
|
|
width = MAX (1, width);
|
|
height = MAX (1, height);
|
|
|
|
ca_sink->window_width = width;
|
|
ca_sink->window_height = height;
|
|
|
|
/* default reshape */
|
|
if (ca_sink->keep_aspect_ratio) {
|
|
GstVideoRectangle src, dst, result;
|
|
|
|
src.x = 0;
|
|
src.y = 0;
|
|
src.w = GST_VIDEO_SINK_WIDTH (ca_sink);
|
|
src.h = GST_VIDEO_SINK_HEIGHT (ca_sink);
|
|
|
|
dst.x = 0;
|
|
dst.y = 0;
|
|
dst.w = width;
|
|
dst.h = height;
|
|
|
|
gst_video_sink_center_rect (src, dst, &result, TRUE);
|
|
gl->Viewport (result.x, result.y, result.w, result.h);
|
|
} else {
|
|
gl->Viewport (0, 0, width, height);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_ca_opengl_layer_sink_on_draw (GstCAOpenGLLayerSink * ca_sink)
|
|
{
|
|
/* Here ca_sink members (ex:ca_sink->info) have a life time of set_caps.
|
|
* It means that they cannot not change between two set_caps as well as
|
|
* for the redisplay_texture size.
|
|
* Whereas redisplay_texture id changes every sink_render
|
|
*/
|
|
|
|
const GstGLFuncs *gl = NULL;
|
|
GstGLSyncMeta *sync_meta;
|
|
|
|
g_return_if_fail (GST_IS_CA_OPENGL_LAYER_SINK (ca_sink));
|
|
|
|
gl = ca_sink->context->gl_vtable;
|
|
|
|
GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
|
|
|
|
if (G_UNLIKELY (!ca_sink->redisplay_shader)) {
|
|
gst_ca_opengl_layer_sink_thread_init_redisplay (ca_sink);
|
|
}
|
|
|
|
/* check if texture is ready for being drawn */
|
|
if (!ca_sink->redisplay_texture) {
|
|
gl->ClearColor (0.0f, 0.0f, 0.0f, 1.0f);
|
|
gl->Clear (GL_COLOR_BUFFER_BIT);
|
|
GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
|
|
return;
|
|
}
|
|
|
|
/* opengl scene */
|
|
GST_TRACE ("redrawing texture:%u", ca_sink->redisplay_texture);
|
|
|
|
if (ca_sink->caps_change) {
|
|
GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
|
|
gst_ca_opengl_layer_sink_on_resize (ca_sink, ca_sink->window_width,
|
|
ca_sink->window_height);
|
|
GST_CA_OPENGL_LAYER_SINK_LOCK (ca_sink);
|
|
ca_sink->caps_change = FALSE;
|
|
}
|
|
|
|
sync_meta = gst_buffer_get_gl_sync_meta (ca_sink->stored_sync);
|
|
if (sync_meta)
|
|
gst_gl_sync_meta_wait (sync_meta, gst_gl_context_get_current ());
|
|
|
|
gl->BindTexture (GL_TEXTURE_2D, 0);
|
|
|
|
gl->ClearColor (0.0, 0.0, 0.0, 0.0);
|
|
gl->Clear (GL_COLOR_BUFFER_BIT);
|
|
|
|
gst_gl_shader_use (ca_sink->redisplay_shader);
|
|
|
|
if (gl->GenVertexArrays)
|
|
gl->BindVertexArray (ca_sink->vao);
|
|
_bind_buffer (ca_sink);
|
|
|
|
gl->ActiveTexture (GL_TEXTURE0);
|
|
gl->BindTexture (GL_TEXTURE_2D, ca_sink->redisplay_texture);
|
|
gst_gl_shader_set_uniform_1i (ca_sink->redisplay_shader, "tex", 0);
|
|
|
|
gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
|
|
|
|
if (gl->GenVertexArrays)
|
|
gl->BindVertexArray (0);
|
|
else
|
|
_unbind_buffer (ca_sink);
|
|
|
|
/* end default opengl scene */
|
|
GST_CA_OPENGL_LAYER_SINK_UNLOCK (ca_sink);
|
|
}
|