| /* |
| * Copyright (C) 2009 Ole André Vadla Ravnås <oravnas@cisco.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., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #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_WRONG_STATE; |
| } |
| 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_details_simple (element_class, |
| "Video Source (MIO)", "Source/Video", |
| "Reads frames from a Mac OS X MIO device", |
| "Ole André Vadla Ravnås <oravnas@cisco.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; |
| } |