mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 00:01:23 +00:00
1201 lines
32 KiB
C
1201 lines
32 KiB
C
/*
|
|
* Copyright (C) 2009 Ole André Vadla Ravnås <oleavr@soundrop.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.
|
|
*/
|
|
|
|
/* FIXME 1.x: suppress warnings for deprecated API such as GStaticRecMutex
|
|
* with newer GLib versions (>= 2.31.0) */
|
|
#define GLIB_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
#include "miovideosrc.h"
|
|
|
|
#include "coremediabuffer.h"
|
|
|
|
#include <gst/interfaces/propertyprobe.h>
|
|
#include <gst/video/video.h>
|
|
|
|
#include <CoreVideo/CVHostTime.h>
|
|
|
|
#define DEFAULT_DEVICE_INDEX -1
|
|
|
|
#define FRAME_QUEUE_SIZE 2
|
|
|
|
#define FRAME_QUEUE_LOCK(instance) g_mutex_lock (instance->qlock)
|
|
#define FRAME_QUEUE_UNLOCK(instance) g_mutex_unlock (instance->qlock)
|
|
#define FRAME_QUEUE_WAIT(instance) \
|
|
g_cond_wait (instance->qcond, instance->qlock)
|
|
#define FRAME_QUEUE_NOTIFY(instance) g_cond_signal (instance->qcond)
|
|
|
|
#define GST_MIO_REQUIRED_APIS \
|
|
(GST_API_CORE_VIDEO | GST_API_CORE_MEDIA | GST_API_MIO)
|
|
|
|
GST_DEBUG_CATEGORY (gst_mio_video_src_debug);
|
|
#define GST_CAT_DEFAULT gst_mio_video_src_debug
|
|
|
|
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("UYVY") ";"
|
|
GST_VIDEO_CAPS_YUV ("YUY2") ";"
|
|
"image/jpeg, "
|
|
"width = " GST_VIDEO_SIZE_RANGE ", "
|
|
"height = " GST_VIDEO_SIZE_RANGE ", "
|
|
"framerate = " GST_VIDEO_FPS_RANGE ";")
|
|
);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_DEVICE_UID,
|
|
PROP_DEVICE_NAME,
|
|
PROP_DEVICE_INDEX
|
|
};
|
|
|
|
typedef gboolean (*GstMIOCallback) (GstMIOVideoSrc * self, gpointer data);
|
|
#define GST_MIO_CALLBACK(cb) ((GstMIOCallback) (cb))
|
|
|
|
static gboolean gst_mio_video_src_open_device (GstMIOVideoSrc * self);
|
|
static void gst_mio_video_src_close_device (GstMIOVideoSrc * self);
|
|
static gboolean gst_mio_video_src_build_capture_graph_for
|
|
(GstMIOVideoSrc * self, GstMIOVideoDevice * device);
|
|
static TundraStatus gst_mio_video_src_configure_output_node
|
|
(GstMIOVideoSrc * self, TundraGraph * graph, guint node_id);
|
|
|
|
static void gst_mio_video_src_start_dispatcher (GstMIOVideoSrc * self);
|
|
static void gst_mio_video_src_stop_dispatcher (GstMIOVideoSrc * self);
|
|
static gpointer gst_mio_video_src_dispatcher_thread (gpointer data);
|
|
static gboolean gst_mio_video_src_perform (GstMIOVideoSrc * self,
|
|
GstMIOCallback cb, gpointer data);
|
|
static gboolean gst_mio_video_src_perform_proxy (gpointer data);
|
|
|
|
static void gst_mio_video_src_probe_interface_init (gpointer g_iface,
|
|
gpointer iface_data);
|
|
|
|
static void gst_mio_video_src_init_interfaces (GType type);
|
|
|
|
GST_BOILERPLATE_FULL (GstMIOVideoSrc, gst_mio_video_src, GstPushSrc,
|
|
GST_TYPE_PUSH_SRC, gst_mio_video_src_init_interfaces);
|
|
|
|
static void
|
|
gst_mio_video_src_init (GstMIOVideoSrc * self, GstMIOVideoSrcClass * gclass)
|
|
{
|
|
GstBaseSrc *base_src = GST_BASE_SRC_CAST (self);
|
|
guint64 host_freq;
|
|
|
|
gst_base_src_set_live (base_src, TRUE);
|
|
gst_base_src_set_format (base_src, GST_FORMAT_TIME);
|
|
|
|
host_freq = gst_gdouble_to_guint64 (CVGetHostClockFrequency ());
|
|
if (host_freq <= GST_SECOND) {
|
|
self->cv_ratio_n = GST_SECOND / host_freq;
|
|
self->cv_ratio_d = 1;
|
|
} else {
|
|
self->cv_ratio_n = 1;
|
|
self->cv_ratio_d = host_freq / GST_SECOND;
|
|
}
|
|
|
|
self->queue = g_queue_new ();
|
|
self->qlock = g_mutex_new ();
|
|
self->qcond = g_cond_new ();
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_finalize (GObject * object)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (object);
|
|
|
|
g_cond_free (self->qcond);
|
|
g_mutex_free (self->qlock);
|
|
g_queue_free (self->queue);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICE_UID:
|
|
g_value_set_string (value, self->device_uid);
|
|
break;
|
|
case PROP_DEVICE_NAME:
|
|
g_value_set_string (value, self->device_name);
|
|
break;
|
|
case PROP_DEVICE_INDEX:
|
|
g_value_set_int (value, self->device_index);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICE_UID:
|
|
g_free (self->device_uid);
|
|
self->device_uid = g_value_dup_string (value);
|
|
break;
|
|
case PROP_DEVICE_NAME:
|
|
g_free (self->device_name);
|
|
self->device_name = g_value_dup_string (value);
|
|
break;
|
|
case PROP_DEVICE_INDEX:
|
|
self->device_index = g_value_get_int (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_mio_video_src_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (element);
|
|
GstStateChangeReturn ret;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
gst_mio_video_src_start_dispatcher (self);
|
|
if (!gst_mio_video_src_perform (self,
|
|
GST_MIO_CALLBACK (gst_mio_video_src_open_device), NULL)) {
|
|
goto open_failed;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
gst_mio_video_src_perform (self,
|
|
GST_MIO_CALLBACK (gst_mio_video_src_close_device), NULL);
|
|
|
|
gst_mio_video_src_stop_dispatcher (self);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
open_failed:
|
|
{
|
|
gst_mio_video_src_stop_dispatcher (self);
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
}
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_mio_video_src_get_caps (GstBaseSrc * basesrc)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (basesrc);
|
|
GstCaps *result;
|
|
|
|
if (self->device != NULL) {
|
|
result =
|
|
gst_caps_ref (gst_mio_video_device_get_available_caps (self->device));
|
|
} else {
|
|
result = NULL; /* BaseSrc will return template caps */
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_do_set_caps (GstMIOVideoSrc * self, GstCaps * caps)
|
|
{
|
|
TundraStatus status;
|
|
|
|
if (self->device == NULL)
|
|
goto no_device;
|
|
|
|
if (!gst_mio_video_device_set_caps (self->device, caps))
|
|
goto invalid_format;
|
|
|
|
if (!gst_mio_video_src_build_capture_graph_for (self, self->device))
|
|
goto graph_build_error;
|
|
|
|
status = self->ctx->mio->TundraGraphInitialize (self->graph);
|
|
if (status != kTundraSuccess)
|
|
goto graph_init_error;
|
|
|
|
status = self->ctx->mio->TundraGraphStart (self->graph);
|
|
if (status != kTundraSuccess)
|
|
goto graph_start_error;
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
no_device:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("no device"), (NULL));
|
|
return FALSE;
|
|
}
|
|
invalid_format:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("invalid format"), (NULL));
|
|
return FALSE;
|
|
}
|
|
graph_build_error:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
|
("failed to build capture graph"), (NULL));
|
|
return FALSE;
|
|
}
|
|
graph_init_error:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
|
("failed to initialize capture graph: %08x", status), (NULL));
|
|
return FALSE;
|
|
}
|
|
graph_start_error:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
|
("failed to start capture graph: %08x", status), (NULL));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_set_caps (GstBaseSrc * basesrc, GstCaps * caps)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (basesrc);
|
|
|
|
{
|
|
gchar *str;
|
|
|
|
str = gst_caps_to_string (caps);
|
|
GST_DEBUG_OBJECT (self, "caps: %s", str);
|
|
g_free (str);
|
|
}
|
|
|
|
return gst_mio_video_src_perform (self,
|
|
GST_MIO_CALLBACK (gst_mio_video_src_do_set_caps), caps);
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_start (GstBaseSrc * basesrc)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (basesrc);
|
|
|
|
self->running = TRUE;
|
|
self->prev_offset = GST_BUFFER_OFFSET_NONE;
|
|
self->prev_format = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_do_stop (GstMIOVideoSrc * self)
|
|
{
|
|
TundraStatus status;
|
|
|
|
if (self->graph == NULL)
|
|
goto nothing_to_stop;
|
|
|
|
status = self->ctx->mio->TundraGraphStop (self->graph);
|
|
if (status != kTundraSuccess)
|
|
goto graph_failed_to_stop;
|
|
|
|
while (!g_queue_is_empty (self->queue))
|
|
gst_buffer_unref (g_queue_pop_head (self->queue));
|
|
|
|
self->ctx->cm->FigFormatDescriptionRelease (self->prev_format);
|
|
self->prev_format = NULL;
|
|
|
|
return TRUE;
|
|
|
|
nothing_to_stop:
|
|
return TRUE;
|
|
|
|
graph_failed_to_stop:
|
|
GST_WARNING_OBJECT (self, "failed to stop capture graph: %d", status);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_stop (GstBaseSrc * basesrc)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (basesrc);
|
|
|
|
return gst_mio_video_src_perform (self,
|
|
GST_MIO_CALLBACK (gst_mio_video_src_do_stop), NULL);
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_query (GstBaseSrc * basesrc, GstQuery * query)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (basesrc);
|
|
gboolean result = FALSE;
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_LATENCY:{
|
|
GstClockTime min_latency, max_latency;
|
|
|
|
if (self->device == NULL)
|
|
goto beach;
|
|
|
|
if (gst_mio_video_device_get_selected_format (self->device) == NULL)
|
|
goto beach;
|
|
|
|
min_latency = max_latency =
|
|
gst_mio_video_device_get_duration (self->device);
|
|
|
|
GST_DEBUG_OBJECT (self, "reporting latency of min %" GST_TIME_FORMAT
|
|
" max %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
|
|
|
|
gst_query_set_latency (query, TRUE, min_latency, max_latency);
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
default:
|
|
result = GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
|
|
break;
|
|
}
|
|
|
|
beach:
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_unlock (GstBaseSrc * basesrc)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (basesrc);
|
|
|
|
FRAME_QUEUE_LOCK (self);
|
|
self->running = FALSE;
|
|
FRAME_QUEUE_NOTIFY (self);
|
|
FRAME_QUEUE_UNLOCK (self);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_unlock_stop (GstBaseSrc * basesrc)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mio_video_src_create (GstPushSrc * pushsrc, GstBuffer ** buf)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (pushsrc);
|
|
GstCMApi *cm = self->ctx->cm;
|
|
CMFormatDescriptionRef format;
|
|
|
|
FRAME_QUEUE_LOCK (self);
|
|
while (self->running && g_queue_is_empty (self->queue))
|
|
FRAME_QUEUE_WAIT (self);
|
|
*buf = g_queue_pop_tail (self->queue);
|
|
FRAME_QUEUE_UNLOCK (self);
|
|
|
|
if (G_UNLIKELY (!self->running))
|
|
goto shutting_down;
|
|
|
|
format = cm->CMSampleBufferGetFormatDescription
|
|
(GST_CORE_MEDIA_BUFFER (*buf)->sample_buf);
|
|
if (self->prev_format != NULL &&
|
|
!cm->CMFormatDescriptionEqual (format, self->prev_format)) {
|
|
goto unexpected_format;
|
|
}
|
|
cm->FigFormatDescriptionRelease (self->prev_format);
|
|
self->prev_format = cm->FigFormatDescriptionRetain (format);
|
|
|
|
if (self->prev_offset == GST_BUFFER_OFFSET_NONE ||
|
|
GST_BUFFER_OFFSET (*buf) - self->prev_offset != 1) {
|
|
GST_BUFFER_FLAG_SET (*buf, GST_BUFFER_FLAG_DISCONT);
|
|
}
|
|
self->prev_offset = GST_BUFFER_OFFSET (*buf);
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
/* ERRORS */
|
|
shutting_down:
|
|
{
|
|
if (*buf != NULL) {
|
|
gst_buffer_unref (*buf);
|
|
*buf = NULL;
|
|
}
|
|
|
|
return GST_FLOW_FLUSHING;
|
|
}
|
|
unexpected_format:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, READ,
|
|
("capture format changed unexpectedly"),
|
|
("another application likely reconfigured the device"));
|
|
|
|
if (*buf != NULL) {
|
|
gst_buffer_unref (*buf);
|
|
*buf = NULL;
|
|
}
|
|
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_open_device (GstMIOVideoSrc * self)
|
|
{
|
|
GError *error = NULL;
|
|
GList *devices = NULL, *walk;
|
|
guint device_idx;
|
|
|
|
self->ctx = gst_core_media_ctx_new (GST_API_CORE_VIDEO | GST_API_CORE_MEDIA
|
|
| GST_API_MIO, &error);
|
|
if (error != NULL)
|
|
goto api_error;
|
|
|
|
devices = gst_mio_video_device_list_create (self->ctx);
|
|
if (devices == NULL)
|
|
goto no_devices;
|
|
|
|
for (walk = devices, device_idx = 0; walk != NULL; walk = walk->next) {
|
|
GstMIOVideoDevice *device = walk->data;
|
|
gboolean match;
|
|
|
|
if (self->device_uid != NULL) {
|
|
match = g_ascii_strcasecmp (gst_mio_video_device_get_uid (device),
|
|
self->device_uid) == 0;
|
|
} else if (self->device_name != NULL) {
|
|
match = g_ascii_strcasecmp (gst_mio_video_device_get_name (device),
|
|
self->device_name) == 0;
|
|
} else if (self->device_index >= 0) {
|
|
match = device_idx == self->device_index;
|
|
} else {
|
|
match = TRUE; /* pick the first entry */
|
|
}
|
|
|
|
if (self->device != NULL)
|
|
match = FALSE;
|
|
|
|
GST_DEBUG_OBJECT (self, "%c device[%u] = handle: %d name: '%s' uid: '%s'",
|
|
(match) ? '*' : '-', device_idx,
|
|
gst_mio_video_device_get_handle (device),
|
|
gst_mio_video_device_get_name (device),
|
|
gst_mio_video_device_get_uid (device));
|
|
|
|
/*gst_mio_video_device_print_debug_info (device); */
|
|
|
|
if (match)
|
|
self->device = g_object_ref (device);
|
|
|
|
device_idx++;
|
|
}
|
|
|
|
if (self->device == NULL)
|
|
goto no_such_device;
|
|
|
|
if (!gst_mio_video_device_open (self->device))
|
|
goto device_busy_or_gone;
|
|
|
|
gst_mio_video_device_list_destroy (devices);
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
api_error:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("API error"),
|
|
("%s", error->message));
|
|
g_clear_error (&error);
|
|
goto any_error;
|
|
}
|
|
no_devices:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
|
("no video capture devices found"), (NULL));
|
|
goto any_error;
|
|
}
|
|
no_such_device:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
|
|
("specified video capture device not found"), (NULL));
|
|
goto any_error;
|
|
}
|
|
device_busy_or_gone:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, BUSY,
|
|
("failed to start capture (device already in use or gone)"), (NULL));
|
|
goto any_error;
|
|
}
|
|
any_error:
|
|
{
|
|
if (devices != NULL) {
|
|
gst_mio_video_device_list_destroy (devices);
|
|
}
|
|
if (self->ctx != NULL) {
|
|
g_object_unref (self->ctx);
|
|
self->ctx = NULL;
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_close_device (GstMIOVideoSrc * self)
|
|
{
|
|
self->ctx->mio->TundraGraphUninitialize (self->graph);
|
|
self->ctx->mio->TundraGraphRelease (self->graph);
|
|
self->graph = NULL;
|
|
|
|
gst_mio_video_device_close (self->device);
|
|
g_object_unref (self->device);
|
|
self->device = NULL;
|
|
|
|
g_object_unref (self->ctx);
|
|
self->ctx = NULL;
|
|
}
|
|
|
|
#define CHECK_TUNDRA_ERROR(fn) \
|
|
if (status != kTundraSuccess) { \
|
|
last_function_name = fn; \
|
|
goto tundra_error; \
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_build_capture_graph_for (GstMIOVideoSrc * self,
|
|
GstMIOVideoDevice * device)
|
|
{
|
|
GstMIOApi *mio = self->ctx->mio;
|
|
const gchar *last_function_name;
|
|
TundraGraph *graph = NULL;
|
|
TundraTargetSpec spec = { 0, };
|
|
TundraUnitID input_node = -1;
|
|
gpointer input_info;
|
|
TundraObjectID device_handle;
|
|
TundraUnitID sync_node = -1;
|
|
guint8 is_master;
|
|
guint sync_direction;
|
|
TundraUnitID output_node = -1;
|
|
TundraStatus status;
|
|
|
|
const gint node_id_input = 1;
|
|
const gint node_id_sync = 22;
|
|
const gint node_id_output = 16;
|
|
|
|
/*
|
|
* Graph
|
|
*/
|
|
status = mio->TundraGraphCreate (kCFAllocatorDefault, &graph);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphCreate");
|
|
|
|
/*
|
|
* Node: input
|
|
*/
|
|
spec.name = kTundraUnitInput;
|
|
spec.scope = kTundraScopeDAL;
|
|
spec.vendor = kTundraVendorApple;
|
|
status = mio->TundraGraphCreateNode (graph, node_id_input, 0, 0, &spec, 0,
|
|
&input_node);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphCreateNode(input)");
|
|
|
|
/* store node info for setting clock provider */
|
|
input_info = NULL;
|
|
status = mio->TundraGraphGetNodeInfo (graph, input_node, 0, 0, 0, 0,
|
|
&input_info);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphGetNodeInfo(input)");
|
|
|
|
/* set device handle */
|
|
device_handle = gst_mio_video_device_get_handle (device);
|
|
status = mio->TundraGraphSetProperty (graph, node_id_input, 0,
|
|
kTundraInputPropertyDeviceID, 0, 0, &device_handle,
|
|
sizeof (device_handle));
|
|
CHECK_TUNDRA_ERROR ("TundraGraphSetProperty(input, DeviceID)");
|
|
|
|
/*
|
|
* Node: sync
|
|
*/
|
|
spec.name = kTundraUnitSync;
|
|
spec.scope = kTundraScopeVSyn;
|
|
status = mio->TundraGraphCreateNode (graph, node_id_sync, 0, 0, &spec, 0,
|
|
&sync_node);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphCreateNode(sync)");
|
|
status = mio->TundraGraphSetProperty (graph, node_id_sync, 0,
|
|
kTundraSyncPropertyClockProvider, 0, 0, &input_info, sizeof (input_info));
|
|
CHECK_TUNDRA_ERROR ("TundraGraphSetProperty(sync, ClockProvider)");
|
|
is_master = TRUE;
|
|
status = mio->TundraGraphSetProperty (graph, node_id_sync, 0,
|
|
kTundraSyncPropertyMasterSynchronizer, 0, 0,
|
|
&is_master, sizeof (is_master));
|
|
CHECK_TUNDRA_ERROR ("TundraGraphSetProperty(sync, MasterSynchronizer)");
|
|
sync_direction = 0;
|
|
status = mio->TundraGraphSetProperty (graph, node_id_sync, 0,
|
|
kTundraSyncPropertySynchronizationDirection, 0, 0,
|
|
&sync_direction, sizeof (sync_direction));
|
|
CHECK_TUNDRA_ERROR ("TundraGraphSetProperty(sync, SynchronizationDirection)");
|
|
|
|
/*
|
|
* Node: output
|
|
*/
|
|
spec.name = kTundraUnitOutput;
|
|
spec.scope = kTundraScope2PRC;
|
|
status = mio->TundraGraphCreateNode (graph, node_id_output, 0, 0, &spec, 0,
|
|
&output_node);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphCreateNode(output)");
|
|
status = gst_mio_video_src_configure_output_node (self, graph,
|
|
node_id_output);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphSetProperty(output, Delegate)");
|
|
|
|
/*
|
|
* Connect the nodes
|
|
*/
|
|
status = mio->TundraGraphConnectNodeInput (graph, input_node, 0,
|
|
sync_node, 0);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphConnectNodeInput(input, sync)");
|
|
status = mio->TundraGraphConnectNodeInput (graph, sync_node, 0,
|
|
output_node, 0);
|
|
CHECK_TUNDRA_ERROR ("TundraGraphConnectNodeInput(sync, output)");
|
|
|
|
self->graph = graph;
|
|
|
|
return TRUE;
|
|
|
|
tundra_error:
|
|
{
|
|
GST_ELEMENT_ERROR (self, RESOURCE, FAILED,
|
|
("%s failed (status=%d)", last_function_name, (gint) status), (NULL));
|
|
goto any_error;
|
|
}
|
|
any_error:
|
|
{
|
|
mio->TundraGraphRelease (graph);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstClockTime
|
|
gst_mio_video_src_get_timestamp (GstMIOVideoSrc * self, CMSampleBufferRef sbuf)
|
|
{
|
|
GstClock *clock;
|
|
GstClockTime base_time;
|
|
GstClockTime timestamp;
|
|
|
|
GST_OBJECT_LOCK (self);
|
|
if ((clock = GST_ELEMENT_CLOCK (self)) != NULL) {
|
|
gst_object_ref (clock);
|
|
}
|
|
base_time = GST_ELEMENT_CAST (self)->base_time;
|
|
GST_OBJECT_UNLOCK (self);
|
|
|
|
if (G_UNLIKELY (clock == NULL))
|
|
goto no_clock;
|
|
|
|
timestamp = GST_CLOCK_TIME_NONE;
|
|
|
|
/*
|
|
* If the current clock is GstSystemClock, we know that it's using the
|
|
* CoreAudio/CoreVideo clock. As such we may use the timestamp attached
|
|
* to the CMSampleBuffer.
|
|
*/
|
|
if (G_TYPE_FROM_INSTANCE (clock) == GST_TYPE_SYSTEM_CLOCK) {
|
|
CFNumberRef number;
|
|
UInt64 ht;
|
|
|
|
number = self->ctx->cm->CMGetAttachment (sbuf,
|
|
*self->ctx->mio->kTundraSampleBufferAttachmentKey_HostTime, NULL);
|
|
if (number != NULL && CFNumberGetValue (number, kCFNumberSInt64Type, &ht)) {
|
|
timestamp = gst_util_uint64_scale_int (ht,
|
|
self->cv_ratio_n, self->cv_ratio_d);
|
|
}
|
|
}
|
|
|
|
if (!GST_CLOCK_TIME_IS_VALID (timestamp)) {
|
|
timestamp = gst_clock_get_time (clock);
|
|
}
|
|
|
|
if (timestamp > base_time)
|
|
timestamp -= base_time;
|
|
else
|
|
timestamp = 0;
|
|
|
|
gst_object_unref (clock);
|
|
|
|
return timestamp;
|
|
|
|
no_clock:
|
|
return GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_render (gpointer instance, gpointer unk1,
|
|
gpointer unk2, gpointer unk3, CMSampleBufferRef sample_buf)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC_CAST (instance);
|
|
GstBuffer *buf;
|
|
CFNumberRef number;
|
|
UInt32 seq;
|
|
|
|
buf = gst_core_media_buffer_new (self->ctx, sample_buf);
|
|
if (G_UNLIKELY (buf == NULL))
|
|
goto buffer_creation_failed;
|
|
|
|
number = self->ctx->cm->CMGetAttachment (sample_buf,
|
|
*self->ctx->mio->kTundraSampleBufferAttachmentKey_SequenceNumber, NULL);
|
|
if (number != NULL && CFNumberGetValue (number, kCFNumberSInt32Type, &seq)) {
|
|
GST_BUFFER_OFFSET (buf) = seq;
|
|
GST_BUFFER_OFFSET_END (buf) = seq + 1;
|
|
}
|
|
|
|
GST_BUFFER_TIMESTAMP (buf) = gst_mio_video_src_get_timestamp (self,
|
|
sample_buf);
|
|
|
|
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
|
|
GST_BUFFER_DURATION (buf) =
|
|
gst_mio_video_device_get_duration (self->device);
|
|
}
|
|
|
|
FRAME_QUEUE_LOCK (self);
|
|
if (g_queue_get_length (self->queue) == FRAME_QUEUE_SIZE)
|
|
gst_buffer_unref (g_queue_pop_tail (self->queue));
|
|
g_queue_push_head (self->queue, buf);
|
|
FRAME_QUEUE_NOTIFY (self);
|
|
FRAME_QUEUE_UNLOCK (self);
|
|
|
|
return kTundraSuccess;
|
|
|
|
buffer_creation_failed:
|
|
GST_WARNING_OBJECT (instance, "failed to create buffer");
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_initialize (gpointer instance)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s", G_STRFUNC);
|
|
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_uninitialize (gpointer instance)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s", G_STRFUNC);
|
|
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_start (gpointer instance)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s", G_STRFUNC);
|
|
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_stop (gpointer instance)
|
|
{
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_reset (gpointer instance)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s", G_STRFUNC);
|
|
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_deallocate (gpointer instance)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s", G_STRFUNC);
|
|
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_output_can_render_now (gpointer instance, guint * unk)
|
|
{
|
|
if (unk != NULL)
|
|
*unk = 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static CFArrayRef
|
|
gst_mio_video_src_output_available_formats (gpointer instance,
|
|
gboolean ensure_only)
|
|
{
|
|
GstMIOVideoSrc *self = GST_MIO_VIDEO_SRC (instance);
|
|
CMFormatDescriptionRef format_desc;
|
|
|
|
GST_DEBUG_OBJECT (self, "%s: ensure_only=%d", G_STRFUNC, ensure_only);
|
|
|
|
if (ensure_only)
|
|
return NULL;
|
|
|
|
g_assert (self->device != NULL);
|
|
format_desc = gst_mio_video_device_get_selected_format (self->device);
|
|
g_assert (format_desc != NULL);
|
|
|
|
return CFArrayCreate (kCFAllocatorDefault, (const void **) &format_desc, 1,
|
|
&kCFTypeArrayCallBacks);
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_copy_clock (gpointer instance)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s", G_STRFUNC);
|
|
|
|
return kTundraSuccess;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_get_property_info (gpointer instance, guint prop_id)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s: prop_id=%u", G_STRFUNC, prop_id);
|
|
|
|
if (prop_id == kTundraInputUnitProperty_SourcePath)
|
|
return kTundraSuccess;
|
|
|
|
return kTundraNotSupported;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_get_property (gpointer instance, guint prop_id)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s", G_STRFUNC);
|
|
|
|
if (prop_id == kTundraInputUnitProperty_SourcePath)
|
|
return kTundraSuccess;
|
|
|
|
return kTundraNotSupported;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_output_set_property (gpointer instance, guint prop_id)
|
|
{
|
|
GST_DEBUG_OBJECT (instance, "%s: prop_id=%u", G_STRFUNC, prop_id);
|
|
|
|
if (prop_id == kTundraInputUnitProperty_SourcePath)
|
|
return kTundraSuccess;
|
|
|
|
return kTundraNotSupported;
|
|
}
|
|
|
|
static TundraStatus
|
|
gst_mio_video_src_configure_output_node (GstMIOVideoSrc * self,
|
|
TundraGraph * graph, guint node_id)
|
|
{
|
|
TundraStatus status;
|
|
TundraOutputDelegate d = { 0, };
|
|
|
|
d.unk1 = 2;
|
|
d.instance = self;
|
|
d.Render = gst_mio_video_src_output_render;
|
|
d.Initialize = gst_mio_video_src_output_initialize;
|
|
d.Uninitialize = gst_mio_video_src_output_uninitialize;
|
|
d.Start = gst_mio_video_src_output_start;
|
|
d.Stop = gst_mio_video_src_output_stop;
|
|
d.Reset = gst_mio_video_src_output_reset;
|
|
d.Deallocate = gst_mio_video_src_output_deallocate;
|
|
d.CanRenderNow = gst_mio_video_src_output_can_render_now;
|
|
d.AvailableFormats = gst_mio_video_src_output_available_formats;
|
|
d.CopyClock = gst_mio_video_src_output_copy_clock;
|
|
d.GetPropertyInfo = gst_mio_video_src_output_get_property_info;
|
|
d.GetProperty = gst_mio_video_src_output_get_property;
|
|
d.SetProperty = gst_mio_video_src_output_set_property;
|
|
|
|
status = self->ctx->mio->TundraGraphSetProperty (graph, node_id, 0,
|
|
kTundraOutputPropertyDelegate, 0, 0, &d, sizeof (d));
|
|
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_start_dispatcher (GstMIOVideoSrc * self)
|
|
{
|
|
g_assert (self->dispatcher_ctx == NULL && self->dispatcher_loop == NULL);
|
|
g_assert (self->dispatcher_thread == NULL);
|
|
|
|
self->dispatcher_ctx = g_main_context_new ();
|
|
self->dispatcher_loop = g_main_loop_new (self->dispatcher_ctx, TRUE);
|
|
self->dispatcher_thread =
|
|
g_thread_create (gst_mio_video_src_dispatcher_thread, self, TRUE, NULL);
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_stop_dispatcher (GstMIOVideoSrc * self)
|
|
{
|
|
g_assert (self->dispatcher_ctx != NULL && self->dispatcher_loop != NULL);
|
|
g_assert (self->dispatcher_thread != NULL);
|
|
|
|
g_main_loop_quit (self->dispatcher_loop);
|
|
g_thread_join (self->dispatcher_thread);
|
|
self->dispatcher_thread = NULL;
|
|
|
|
g_main_loop_unref (self->dispatcher_loop);
|
|
self->dispatcher_loop = NULL;
|
|
|
|
g_main_context_unref (self->dispatcher_ctx);
|
|
self->dispatcher_ctx = NULL;
|
|
}
|
|
|
|
static gpointer
|
|
gst_mio_video_src_dispatcher_thread (gpointer data)
|
|
{
|
|
GstMIOVideoSrc *self = data;
|
|
|
|
g_main_loop_run (self->dispatcher_loop);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GstMIOVideoSrc *self;
|
|
GstMIOCallback callback;
|
|
gpointer data;
|
|
gboolean result;
|
|
|
|
GMutex *mutex;
|
|
GCond *cond;
|
|
gboolean finished;
|
|
} GstMIOPerformCtx;
|
|
|
|
static gboolean
|
|
gst_mio_video_src_perform (GstMIOVideoSrc * self, GstMIOCallback cb,
|
|
gpointer data)
|
|
{
|
|
GstMIOPerformCtx ctx;
|
|
GSource *source;
|
|
|
|
ctx.self = self;
|
|
ctx.callback = cb;
|
|
ctx.data = data;
|
|
ctx.result = FALSE;
|
|
|
|
ctx.mutex = g_mutex_new ();
|
|
ctx.cond = g_cond_new ();
|
|
ctx.finished = FALSE;
|
|
|
|
source = g_idle_source_new ();
|
|
g_source_set_callback (source, gst_mio_video_src_perform_proxy, &ctx, NULL);
|
|
g_source_attach (source, self->dispatcher_ctx);
|
|
|
|
g_mutex_lock (ctx.mutex);
|
|
while (!ctx.finished)
|
|
g_cond_wait (ctx.cond, ctx.mutex);
|
|
g_mutex_unlock (ctx.mutex);
|
|
|
|
g_source_destroy (source);
|
|
g_source_unref (source);
|
|
|
|
g_cond_free (ctx.cond);
|
|
g_mutex_free (ctx.mutex);
|
|
|
|
return ctx.result;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mio_video_src_perform_proxy (gpointer data)
|
|
{
|
|
GstMIOPerformCtx *ctx = data;
|
|
|
|
ctx->result = ctx->callback (ctx->self, ctx->data);
|
|
|
|
g_mutex_lock (ctx->mutex);
|
|
ctx->finished = TRUE;
|
|
g_cond_signal (ctx->cond);
|
|
g_mutex_unlock (ctx->mutex);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static const GList *
|
|
gst_mio_video_src_probe_get_properties (GstPropertyProbe * probe)
|
|
{
|
|
static gsize init_value = 0;
|
|
|
|
if (g_once_init_enter (&init_value)) {
|
|
GObjectClass *klass;
|
|
GList *props = NULL;
|
|
|
|
klass = G_OBJECT_GET_CLASS (probe);
|
|
|
|
props = g_list_append (props,
|
|
g_object_class_find_property (klass, "device-uid"));
|
|
props = g_list_append (props,
|
|
g_object_class_find_property (klass, "device-name"));
|
|
props = g_list_append (props,
|
|
g_object_class_find_property (klass, "device-index"));
|
|
|
|
g_once_init_leave (&init_value, GPOINTER_TO_SIZE (props));
|
|
}
|
|
|
|
return GSIZE_TO_POINTER (init_value);
|
|
}
|
|
|
|
static GValueArray *
|
|
gst_mio_video_src_probe_get_values (GstPropertyProbe * probe, guint prop_id,
|
|
const GParamSpec * pspec)
|
|
{
|
|
GValueArray *values;
|
|
GstCoreMediaCtx *ctx = NULL;
|
|
GError *error = NULL;
|
|
GList *devices = NULL, *walk;
|
|
guint device_idx;
|
|
|
|
values = g_value_array_new (3);
|
|
|
|
ctx = gst_core_media_ctx_new (GST_MIO_REQUIRED_APIS, &error);
|
|
if (error != NULL)
|
|
goto beach;
|
|
|
|
devices = gst_mio_video_device_list_create (ctx);
|
|
if (devices == NULL)
|
|
goto beach;
|
|
|
|
for (walk = devices, device_idx = 0; walk != NULL; walk = walk->next) {
|
|
GstMIOVideoDevice *device = walk->data;
|
|
GValue value = { 0, };
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICE_UID:
|
|
case PROP_DEVICE_NAME:
|
|
{
|
|
const gchar *str;
|
|
|
|
if (prop_id == PROP_DEVICE_UID)
|
|
str = gst_mio_video_device_get_uid (device);
|
|
else
|
|
str = gst_mio_video_device_get_name (device);
|
|
|
|
g_value_init (&value, G_TYPE_STRING);
|
|
g_value_set_string (&value, str);
|
|
|
|
break;
|
|
}
|
|
case PROP_DEVICE_INDEX:
|
|
{
|
|
g_value_init (&value, G_TYPE_INT);
|
|
g_value_set_int (&value, device_idx);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
|
|
goto beach;
|
|
}
|
|
|
|
g_value_array_append (values, &value);
|
|
g_value_unset (&value);
|
|
|
|
device_idx++;
|
|
}
|
|
|
|
beach:
|
|
if (devices != NULL)
|
|
gst_mio_video_device_list_destroy (devices);
|
|
if (ctx != NULL)
|
|
g_object_unref (ctx);
|
|
g_clear_error (&error);
|
|
|
|
return values;
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_base_init (gpointer gclass)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
|
|
|
|
gst_element_class_set_metadata (element_class,
|
|
"Video Source (MIO)", "Source/Video",
|
|
"Reads frames from a Mac OS X MIO device",
|
|
"Ole André Vadla Ravnås <oleavr@soundrop.com>");
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&src_template));
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_class_init (GstMIOVideoSrcClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
|
GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
|
|
GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
|
|
|
|
gobject_class->finalize = gst_mio_video_src_finalize;
|
|
gobject_class->get_property = gst_mio_video_src_get_property;
|
|
gobject_class->set_property = gst_mio_video_src_set_property;
|
|
|
|
gstelement_class->change_state = gst_mio_video_src_change_state;
|
|
|
|
gstbasesrc_class->get_caps = gst_mio_video_src_get_caps;
|
|
gstbasesrc_class->set_caps = gst_mio_video_src_set_caps;
|
|
gstbasesrc_class->start = gst_mio_video_src_start;
|
|
gstbasesrc_class->stop = gst_mio_video_src_stop;
|
|
gstbasesrc_class->query = gst_mio_video_src_query;
|
|
gstbasesrc_class->unlock = gst_mio_video_src_unlock;
|
|
gstbasesrc_class->unlock_stop = gst_mio_video_src_unlock_stop;
|
|
|
|
gstpushsrc_class->create = gst_mio_video_src_create;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DEVICE_UID,
|
|
g_param_spec_string ("device-uid", "Device UID",
|
|
"Unique ID of the desired device", NULL,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
|
|
g_param_spec_string ("device-name", "Device Name",
|
|
"Name of the desired device", NULL,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX,
|
|
g_param_spec_int ("device-index", "Device Index",
|
|
"Zero-based device index of the desired device",
|
|
-1, G_MAXINT, DEFAULT_DEVICE_INDEX,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_mio_video_src_debug, "miovideosrc",
|
|
0, "Mac OS X CoreMedia video source");
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_init_interfaces (GType type)
|
|
{
|
|
static const GInterfaceInfo probe_info = {
|
|
gst_mio_video_src_probe_interface_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE, &probe_info);
|
|
}
|
|
|
|
static void
|
|
gst_mio_video_src_probe_interface_init (gpointer g_iface, gpointer iface_data)
|
|
{
|
|
GstPropertyProbeInterface *iface = g_iface;
|
|
|
|
iface->get_properties = gst_mio_video_src_probe_get_properties;
|
|
iface->get_values = gst_mio_video_src_probe_get_values;
|
|
}
|