/* * Copyright (C) 2009 Ole André Vadla Ravnås * * 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 #include #include #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 "); 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; }