| /* |
| * GStreamer |
| * Copyright 2007 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com> |
| * Copyright 2007 Ali Sabil <ali.sabil@tandberg.com> |
| * Copyright 2008 Barracuda Networks <justin@affinix.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. |
| */ |
| |
| /** |
| * SECTION:element-osxvideosrc |
| * |
| * <refsect2> |
| * osxvideosrc can be used to capture video from capture devices on OS X. |
| * <title>Example launch line</title> |
| * <para> |
| * <programlisting> |
| * gst-launch osxvideosrc ! osxvideosink |
| * </programlisting> |
| * This pipeline shows the video captured from the default capture device. |
| * </para> |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| #include <string.h> |
| |
| // for usleep |
| #include <unistd.h> |
| |
| #include <gst/interfaces/propertyprobe.h> |
| #include "osxvideosrc.h" |
| |
| /* for now, framerate is hard-coded */ |
| #define FRAMERATE 30 |
| |
| // TODO: for completeness, write an _unlock function |
| |
| /* |
| QuickTime notes: |
| |
| EnterMovies |
| initialize QT subsystem |
| there is no deinit |
| |
| OpenDefaultComponent of type SeqGrabComponentType |
| this gets a handle to a sequence grabber |
| |
| CloseComponent |
| release the sequence grabber |
| |
| SGInitialize |
| initialize the SG |
| there is no deinit, simply close the component |
| |
| SGSetDataRef of seqGrabDontMakeMovie |
| this is to disable file creation. we only want frames |
| |
| SGNewChannel of VideoMediaType |
| make a video capture channel |
| |
| QTNewGWorld |
| specify format (e.g. k32ARGBPixelFormat) |
| specify size |
| |
| LockPixels |
| this makes it so the base address of the image doesn't "move". |
| you can UnlockPixels also, if you care to. |
| |
| CocoaSequenceGrabber locks (GetPortPixMap(gWorld)) for the entire session. |
| it also locks/unlocks the pixmaphandle |
| [ PixMapHandle pixMapHandle = GetGWorldPixMap(gworld); ] |
| during the moment where it extracts the frame from the gworld |
| |
| SGSetGWorld |
| assign the gworld to the component |
| pass GetMainDevice() as the last arg, which is just a formality? |
| |
| SGSetChannelBounds |
| use this to set our desired capture size. the camera might not actually |
| capture at this size, but it will pick something close. |
| |
| SGSetChannelUsage of seqGrabRecord |
| enable recording |
| |
| SGSetDataProc |
| set callback handler |
| |
| SGPrepare |
| prepares for recording. this initializes the camera (the light should |
| come on) so that when you call SGStartRecord you hit the ground running. |
| maybe we should call SGPrepare when READY->PAUSED happens? |
| |
| SGRelease |
| unprepare the recording |
| |
| SGStartRecord |
| kick off the recording |
| |
| SGStop |
| stop recording |
| |
| SGGetChannelSampleDescription |
| obtain the size the camera is actually capturing at |
| |
| DecompressSequenceBegin |
| i'm pretty sure you have to use this to receive the raw frames. |
| you can also use it to scale the image. to scale, create a matrix |
| from the source and desired sizes and pass the matrix to this function. |
| *** deprecated: use DecompressSequenceBeginS instead |
| |
| CDSequenceEnd |
| stop a decompress sequence |
| |
| DecompressSequenceFrameS |
| use this to obtain a raw frame. the result ends up in the gworld |
| *** deprecated: use DecompressSequenceFrameWhen instead |
| |
| SGGetChannelDeviceList of sgDeviceListIncludeInputs |
| obtain the list of devices for the video channel |
| |
| SGSetChannelDevice |
| set the master device (DV, USB, etc) on the channel, by string name |
| |
| SGSetChannelDeviceInput |
| set the sub device on the channel (iSight), by integer id |
| device ids should be a concatenation of the above two values. |
| |
| */ |
| |
| GST_DEBUG_CATEGORY (gst_debug_osx_video_src); |
| #define GST_CAT_DEFAULT gst_debug_osx_video_src |
| |
| /* Filter signals and args */ |
| enum |
| { |
| /* FILL ME */ |
| LAST_SIGNAL |
| }; |
| |
| enum |
| { |
| ARG_0, |
| ARG_DEVICE, |
| ARG_DEVICE_NAME |
| }; |
| |
| static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("video/x-raw-yuv, " |
| "format = (fourcc) UYVY, " |
| "width = (int) [ 1, MAX ], " "height = (int) [ 1, MAX ], " |
| //"framerate = (fraction) 0/1") |
| "framerate = (fraction) 30/1") |
| ); |
| |
| static void |
| gst_osx_video_src_init_interfaces (GType type); |
| static void |
| gst_osx_video_src_type_add_device_property_probe_interface (GType type); |
| |
| GST_BOILERPLATE_FULL (GstOSXVideoSrc, gst_osx_video_src, GstPushSrc, |
| GST_TYPE_PUSH_SRC, gst_osx_video_src_init_interfaces); |
| |
| static void gst_osx_video_src_dispose (GObject * object); |
| static void gst_osx_video_src_finalize (GstOSXVideoSrc * osx_video_src); |
| static void gst_osx_video_src_set_property (GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec); |
| static void gst_osx_video_src_get_property (GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec); |
| |
| static GstStateChangeReturn gst_osx_video_src_change_state (GstElement * |
| element, GstStateChange transition); |
| |
| static GstCaps *gst_osx_video_src_get_caps (GstBaseSrc * src); |
| static gboolean gst_osx_video_src_set_caps (GstBaseSrc * src, |
| GstCaps * caps); |
| static gboolean gst_osx_video_src_start (GstBaseSrc * src); |
| static gboolean gst_osx_video_src_stop (GstBaseSrc * src); |
| static gboolean gst_osx_video_src_query (GstBaseSrc * bsrc, |
| GstQuery * query); |
| static GstFlowReturn gst_osx_video_src_create (GstPushSrc * src, |
| GstBuffer ** buf); |
| static void gst_osx_video_src_fixate (GstBaseSrc * bsrc, GstCaps * caps); |
| |
| static gboolean prepare_capture (GstOSXVideoSrc * self); |
| |
| /* \ = \\, : = \c */ |
| static GString *escape_string (const GString * in) |
| { |
| GString *out; |
| int n; |
| |
| out = g_string_sized_new (64); |
| for (n = 0; n < (int) in->len; ++n) { |
| if (in->str[n] == '\\') |
| g_string_append (out, "\\\\"); |
| else if (in->str[n] == ':') |
| g_string_append (out, "\\:"); |
| else |
| g_string_append_c (out, in->str[n]); |
| } |
| |
| return out; |
| } |
| |
| /* \\ = \, \c = : */ |
| static GString * |
| unescape_string (const GString * in) |
| { |
| GString *out; |
| int n; |
| |
| out = g_string_sized_new (64); |
| for (n = 0; n < (int) in->len; ++n) { |
| if (in->str[n] == '\\') { |
| if (n + 1 < (int) in->len) { |
| ++n; |
| if (in->str[n] == '\\') |
| g_string_append_c (out, '\\'); |
| else if (in->str[n] == 'c') |
| g_string_append_c (out, ':'); |
| else { |
| /* unknown code, we will eat the escape sequence */ |
| } |
| } else { |
| /* string ends with backslash, we will eat it */ |
| } |
| } else |
| g_string_append_c (out, in->str[n]); |
| } |
| |
| return out; |
| } |
| |
| static gchar * |
| create_device_id (const gchar * sgname, int inputIndex) |
| { |
| GString *out; |
| GString *name; |
| GString *nameenc; |
| gchar *ret; |
| |
| name = g_string_new (sgname); |
| nameenc = escape_string (name); |
| g_string_free (name, TRUE); |
| |
| if (inputIndex >= 0) { |
| out = g_string_new (""); |
| g_string_printf (out, "%s:%d", nameenc->str, inputIndex); |
| } else { |
| /* unspecified index */ |
| out = g_string_new (nameenc->str); |
| } |
| |
| g_string_free (nameenc, TRUE); |
| ret = g_string_free (out, FALSE); |
| return ret; |
| } |
| |
| static gboolean |
| parse_device_id (const gchar * id, gchar ** sgname, int *inputIndex) |
| { |
| gchar **parts; |
| int numparts; |
| GString *p1; |
| GString *out1; |
| int out2 = 0; |
| |
| parts = g_strsplit (id, ":", -1); |
| numparts = 0; |
| while (parts[numparts]) |
| ++numparts; |
| |
| /* must be exactly 1 or 2 parts */ |
| if (numparts < 1 || numparts > 2) { |
| g_strfreev (parts); |
| return FALSE; |
| } |
| |
| p1 = g_string_new (parts[0]); |
| out1 = unescape_string (p1); |
| g_string_free (p1, TRUE); |
| |
| if (numparts >= 2) { |
| errno = 0; |
| out2 = strtol (parts[1], NULL, 10); |
| if (out2 == 0 && (errno == ERANGE || errno == EINVAL)) { |
| g_string_free (out1, TRUE); |
| g_strfreev (parts); |
| return FALSE; |
| } |
| } |
| |
| g_strfreev (parts); |
| |
| *sgname = g_string_free (out1, FALSE); |
| *inputIndex = out2; |
| return TRUE; |
| } |
| |
| typedef struct |
| { |
| gchar *id; |
| gchar *name; |
| } video_device; |
| |
| static video_device * |
| video_device_alloc (void) |
| { |
| video_device *dev; |
| dev = g_malloc (sizeof (video_device)); |
| dev->id = NULL; |
| dev->name = NULL; |
| return dev; |
| } |
| |
| static void |
| video_device_free (video_device * dev) |
| { |
| if (!dev) |
| return; |
| |
| if (dev->id) |
| g_free (dev->id); |
| if (dev->name) |
| g_free (dev->name); |
| |
| g_free (dev); |
| } |
| |
| static void |
| video_device_free_func (gpointer data, gpointer user_data) |
| { |
| video_device_free ((video_device *) data); |
| } |
| |
| /* return a list of available devices. the default device (if any) will be |
| * the first in the list. |
| */ |
| static GList * |
| device_list (GstOSXVideoSrc * src) |
| { |
| SeqGrabComponent component = NULL; |
| SGChannel channel; |
| SGDeviceList deviceList; |
| SGDeviceName *deviceEntry; |
| SGDeviceInputList inputList; |
| SGDeviceInputName *inputEntry; |
| ComponentResult err; |
| int n, i; |
| GList *list; |
| video_device *dev, *default_dev; |
| gchar sgname[256]; |
| gchar friendly_name[256]; |
| |
| list = NULL; |
| default_dev = NULL; |
| |
| if (src->video_chan) { |
| /* if we already have a video channel allocated, use that */ |
| GST_DEBUG_OBJECT (src, "reusing existing channel for device_list"); |
| channel = src->video_chan; |
| } else { |
| /* otherwise, allocate a temporary one */ |
| component = OpenDefaultComponent (SeqGrabComponentType, 0); |
| if (!component) { |
| err = paramErr; |
| GST_ERROR_OBJECT (src, "OpenDefaultComponent failed. paramErr=%d", |
| (int) err); |
| goto end; |
| } |
| |
| err = SGInitialize (component); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (src, "SGInitialize returned %d", (int) err); |
| goto end; |
| } |
| |
| err = SGSetDataRef (component, 0, 0, seqGrabDontMakeMovie); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (src, "SGSetDataRef returned %d", (int) err); |
| goto end; |
| } |
| |
| err = SGNewChannel (component, VideoMediaType, &channel); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (src, "SGNewChannel returned %d", (int) err); |
| goto end; |
| } |
| } |
| |
| err = |
| SGGetChannelDeviceList (channel, sgDeviceListIncludeInputs, &deviceList); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (src, "SGGetChannelDeviceList returned %d", (int) err); |
| goto end; |
| } |
| |
| for (n = 0; n < (*deviceList)->count; ++n) { |
| deviceEntry = &(*deviceList)->entry[n]; |
| |
| if (deviceEntry->flags & sgDeviceNameFlagDeviceUnavailable) |
| continue; |
| |
| p2cstrcpy (sgname, deviceEntry->name); |
| inputList = deviceEntry->inputs; |
| |
| if (inputList && (*inputList)->count >= 1) { |
| for (i = 0; i < (*inputList)->count; ++i) { |
| inputEntry = &(*inputList)->entry[i]; |
| |
| p2cstrcpy (friendly_name, inputEntry->name); |
| |
| dev = video_device_alloc (); |
| dev->id = create_device_id (sgname, i); |
| if (!dev->id) { |
| video_device_free (dev); |
| i = -1; |
| break; |
| } |
| |
| dev->name = g_strdup (friendly_name); |
| list = g_list_append (list, dev); |
| |
| /* if this is the default device, note it */ |
| if (n == (*deviceList)->selectedIndex |
| && i == (*inputList)->selectedIndex) { |
| default_dev = dev; |
| } |
| } |
| |
| /* error */ |
| if (i == -1) |
| break; |
| } else { |
| /* ### can a device have no defined inputs? */ |
| dev = video_device_alloc (); |
| dev->id = create_device_id (sgname, -1); |
| if (!dev->id) { |
| video_device_free (dev); |
| break; |
| } |
| |
| dev->name = g_strdup (sgname); |
| list = g_list_append (list, dev); |
| |
| /* if this is the default device, note it */ |
| if (n == (*deviceList)->selectedIndex) { |
| default_dev = dev; |
| } |
| } |
| } |
| |
| /* move default device to the front */ |
| if (default_dev) { |
| list = g_list_remove (list, default_dev); |
| list = g_list_prepend (list, default_dev); |
| } |
| |
| end: |
| if (!src->video_chan && component) { |
| err = CloseComponent (component); |
| if (err != noErr) |
| GST_WARNING_OBJECT (src, "CloseComponent returned %d", (int) err); |
| } |
| |
| return list; |
| } |
| |
| static gboolean |
| device_set_default (GstOSXVideoSrc * src) |
| { |
| GList *list; |
| video_device *dev; |
| gboolean ret; |
| |
| /* obtain the device list */ |
| list = device_list (src); |
| if (!list) |
| return FALSE; |
| |
| ret = FALSE; |
| |
| /* the first item is the default */ |
| if (g_list_length (list) >= 1) { |
| dev = (video_device *) list->data; |
| |
| /* take the strings, no need to copy */ |
| src->device_id = dev->id; |
| src->device_name = dev->name; |
| dev->id = NULL; |
| dev->name = NULL; |
| |
| /* null out the item */ |
| video_device_free (dev); |
| list->data = NULL; |
| |
| ret = TRUE; |
| } |
| |
| /* clean up */ |
| g_list_foreach (list, video_device_free_func, NULL); |
| g_list_free (list); |
| |
| return ret; |
| } |
| |
| static gboolean |
| device_get_name (GstOSXVideoSrc * src) |
| { |
| GList *l, *list; |
| video_device *dev; |
| gboolean ret; |
| |
| /* if there is no device set, then attempt to set up with the default, |
| * which will also grab the name in the process. |
| */ |
| if (!src->device_id) |
| return device_set_default (src); |
| |
| /* if we already have a name, free it */ |
| if (src->device_name) { |
| g_free (src->device_name); |
| src->device_name = NULL; |
| } |
| |
| /* obtain the device list */ |
| list = device_list (src); |
| if (!list) |
| return FALSE; |
| |
| ret = FALSE; |
| |
| /* look up the id */ |
| for (l = list; l != NULL; l = l->next) { |
| dev = (video_device *) l->data; |
| if (g_str_equal (dev->id, src->device_id)) { |
| /* take the string, no need to copy */ |
| src->device_name = dev->name; |
| dev->name = NULL; |
| ret = TRUE; |
| break; |
| } |
| } |
| |
| g_list_foreach (list, video_device_free_func, NULL); |
| g_list_free (list); |
| |
| return ret; |
| } |
| |
| static gboolean |
| device_select (GstOSXVideoSrc * src) |
| { |
| Str63 pstr; |
| ComponentResult err; |
| gchar *sgname; |
| int inputIndex; |
| |
| /* if there's no device id set, attempt to select default device */ |
| if (!src->device_id && !device_set_default (src)) |
| return FALSE; |
| |
| if (!parse_device_id (src->device_id, &sgname, &inputIndex)) { |
| GST_ERROR_OBJECT (src, "unable to parse device id: [%s]", src->device_id); |
| return FALSE; |
| } |
| |
| c2pstrcpy (pstr, sgname); |
| g_free (sgname); |
| |
| err = SGSetChannelDevice (src->video_chan, (StringPtr) & pstr); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (src, "SGSetChannelDevice returned %d", (int) err); |
| return FALSE; |
| } |
| |
| err = SGSetChannelDeviceInput (src->video_chan, inputIndex); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (src, "SGSetChannelDeviceInput returned %d", (int) err); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_osx_video_src_iface_supported (GstImplementsInterface * iface, |
| GType iface_type) |
| { |
| return FALSE; |
| } |
| |
| static void |
| gst_osx_video_src_interface_init (GstImplementsInterfaceClass * klass) |
| { |
| /* default virtual functions */ |
| klass->supported = gst_osx_video_src_iface_supported; |
| } |
| |
| static void |
| gst_osx_video_src_init_interfaces (GType type) |
| { |
| static const GInterfaceInfo implements_iface_info = { |
| (GInterfaceInitFunc) gst_osx_video_src_interface_init, |
| NULL, |
| NULL, |
| }; |
| |
| g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, |
| &implements_iface_info); |
| |
| gst_osx_video_src_type_add_device_property_probe_interface (type); |
| } |
| |
| static void |
| gst_osx_video_src_base_init (gpointer gclass) |
| { |
| |
| GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); |
| |
| GST_DEBUG ("%s", G_STRFUNC); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&src_template)); |
| |
| gst_element_class_set_details_simple (element_class, "Video Source (OSX)", |
| "Source/Video", |
| "Reads raw frames from a capture device on OS X", |
| "Ole Andre Vadla Ravnaas <ole.andre.ravnas@tandberg.com>, " |
| "Ali Sabil <ali.sabil@tandberg.com>"); |
| } |
| |
| static void |
| gst_osx_video_src_class_init (GstOSXVideoSrcClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *element_class; |
| GstBaseSrcClass *basesrc_class; |
| GstPushSrcClass *pushsrc_class; |
| OSErr err; |
| |
| GST_DEBUG ("%s", G_STRFUNC); |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| element_class = GST_ELEMENT_CLASS (klass); |
| basesrc_class = GST_BASE_SRC_CLASS (klass); |
| pushsrc_class = GST_PUSH_SRC_CLASS (klass); |
| |
| gobject_class->dispose = gst_osx_video_src_dispose; |
| gobject_class->finalize = (GObjectFinalizeFunc) gst_osx_video_src_finalize; |
| gobject_class->set_property = |
| GST_DEBUG_FUNCPTR (gst_osx_video_src_set_property); |
| gobject_class->get_property = |
| GST_DEBUG_FUNCPTR (gst_osx_video_src_get_property); |
| |
| element_class->change_state = gst_osx_video_src_change_state; |
| |
| basesrc_class->get_caps = gst_osx_video_src_get_caps; |
| basesrc_class->set_caps = gst_osx_video_src_set_caps; |
| basesrc_class->start = gst_osx_video_src_start; |
| basesrc_class->stop = gst_osx_video_src_stop; |
| basesrc_class->query = gst_osx_video_src_query; |
| basesrc_class->fixate = gst_osx_video_src_fixate; |
| |
| pushsrc_class->create = gst_osx_video_src_create; |
| |
| g_object_class_install_property (gobject_class, ARG_DEVICE, |
| g_param_spec_string ("device", "Device", |
| "Sequence Grabber input device in format 'sgname:input#'", |
| NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, ARG_DEVICE_NAME, |
| g_param_spec_string ("device-name", "Device name", |
| "Human-readable name of the video device", |
| NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); |
| |
| err = EnterMovies (); |
| if (err == noErr) { |
| klass->movies_enabled = TRUE; |
| } else { |
| klass->movies_enabled = FALSE; |
| GST_ERROR ("EnterMovies returned %d", err); |
| } |
| } |
| |
| static void |
| gst_osx_video_src_init (GstOSXVideoSrc * self, GstOSXVideoSrcClass * klass) |
| { |
| GST_DEBUG_OBJECT (self, "%s", G_STRFUNC); |
| |
| gst_base_src_set_format (GST_BASE_SRC (self), GST_FORMAT_TIME); |
| gst_base_src_set_live (GST_BASE_SRC (self), TRUE); |
| } |
| |
| static void |
| gst_osx_video_src_dispose (GObject * object) |
| { |
| GstOSXVideoSrc *self = GST_OSX_VIDEO_SRC (object); |
| GST_DEBUG_OBJECT (object, "%s", G_STRFUNC); |
| |
| if (self->device_id) { |
| g_free (self->device_id); |
| self->device_id = NULL; |
| } |
| |
| if (self->device_name) { |
| g_free (self->device_name); |
| self->device_name = NULL; |
| } |
| |
| if (self->buffer != NULL) { |
| gst_buffer_unref (self->buffer); |
| self->buffer = NULL; |
| } |
| |
| G_OBJECT_CLASS (parent_class)->dispose (object); |
| } |
| |
| static void |
| gst_osx_video_src_finalize (GstOSXVideoSrc * self) |
| { |
| GST_DEBUG_OBJECT (self, "%s", G_STRFUNC); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (self)); |
| } |
| |
| static void |
| gst_osx_video_src_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstOSXVideoSrc *src = GST_OSX_VIDEO_SRC (object); |
| |
| switch (prop_id) { |
| case ARG_DEVICE: |
| if (src->device_id) { |
| g_free (src->device_id); |
| src->device_id = NULL; |
| } |
| if (src->device_name) { |
| g_free (src->device_name); |
| src->device_name = NULL; |
| } |
| src->device_id = g_strdup (g_value_get_string (value)); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_osx_video_src_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstOSXVideoSrc *src = GST_OSX_VIDEO_SRC (object); |
| |
| switch (prop_id) { |
| case ARG_DEVICE: |
| if (!src->device_id) |
| device_set_default (src); |
| g_value_set_string (value, src->device_id); |
| break; |
| case ARG_DEVICE_NAME: |
| if (!src->device_name) |
| device_get_name (src); |
| g_value_set_string (value, src->device_name); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static GstCaps * |
| gst_osx_video_src_get_caps (GstBaseSrc * src) |
| { |
| GstElementClass *gstelement_class; |
| GstOSXVideoSrc *self; |
| GstPadTemplate *pad_template; |
| GstCaps *caps; |
| GstStructure *structure; |
| gint width, height; |
| |
| gstelement_class = GST_ELEMENT_GET_CLASS (src); |
| self = GST_OSX_VIDEO_SRC (src); |
| |
| /* if we don't have the resolution set up, return template caps */ |
| if (!self->world) |
| return NULL; |
| |
| pad_template = gst_element_class_get_pad_template (gstelement_class, "src"); |
| /* i don't think this can actually fail... */ |
| if (!pad_template) |
| return NULL; |
| |
| width = self->rect.right; |
| height = self->rect.bottom; |
| |
| caps = gst_caps_copy (gst_pad_template_get_caps (pad_template)); |
| |
| structure = gst_caps_get_structure (caps, 0); |
| gst_structure_set (structure, "width", G_TYPE_INT, width, NULL); |
| gst_structure_set (structure, "height", G_TYPE_INT, height, NULL); |
| |
| return caps; |
| } |
| |
| static gboolean |
| gst_osx_video_src_set_caps (GstBaseSrc * src, GstCaps * caps) |
| { |
| GstOSXVideoSrc *self = GST_OSX_VIDEO_SRC (src); |
| GstStructure *structure = gst_caps_get_structure (caps, 0); |
| gint width, height, framerate_num, framerate_denom; |
| float fps; |
| ComponentResult err; |
| |
| GST_DEBUG_OBJECT (src, "%s", G_STRFUNC); |
| |
| if (!self->seq_grab) |
| return FALSE; |
| |
| gst_structure_get_int (structure, "width", &width); |
| gst_structure_get_int (structure, "height", &height); |
| gst_structure_get_fraction (structure, "framerate", &framerate_num, |
| &framerate_denom); |
| fps = (float) framerate_num / framerate_denom; |
| |
| GST_DEBUG_OBJECT (src, "changing caps to %dx%d@%f", width, height, fps); |
| |
| SetRect (&self->rect, 0, 0, width, height); |
| |
| err = QTNewGWorld (&self->world, k422YpCbCr8PixelFormat, &self->rect, 0, |
| NULL, 0); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "QTNewGWorld returned %d", (int) err); |
| goto fail; |
| } |
| |
| if (!LockPixels (GetPortPixMap (self->world))) { |
| GST_ERROR_OBJECT (self, "LockPixels failed"); |
| goto fail; |
| } |
| |
| err = SGSetGWorld (self->seq_grab, self->world, NULL); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGSetGWorld returned %d", (int) err); |
| goto fail; |
| } |
| |
| err = SGSetChannelBounds (self->video_chan, &self->rect); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGSetChannelBounds returned %d", (int) err); |
| goto fail; |
| } |
| // ###: if we ever support choosing framerates, do something with this |
| /*err = SGSetFrameRate (self->video_chan, FloatToFixed(fps)); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGSetFrameRate returned %d", (int) err); |
| goto fail; |
| } */ |
| |
| return TRUE; |
| |
| fail: |
| if (self->world) { |
| SGSetGWorld (self->seq_grab, NULL, NULL); |
| DisposeGWorld (self->world); |
| self->world = NULL; |
| } |
| |
| return FALSE; |
| } |
| |
| static void |
| gst_osx_video_src_fixate (GstBaseSrc * bsrc, GstCaps * caps) |
| { |
| GstStructure *structure; |
| int i; |
| |
| /* this function is for choosing defaults as a last resort */ |
| for (i = 0; i < (int) gst_caps_get_size (caps); ++i) { |
| structure = gst_caps_get_structure (caps, i); |
| gst_structure_fixate_field_nearest_int (structure, "width", 640); |
| gst_structure_fixate_field_nearest_int (structure, "height", 480); |
| |
| // ###: if we ever support choosing framerates, do something with this |
| //gst_structure_fixate_field_nearest_fraction (structure, "framerate", 15, 2); |
| } |
| } |
| |
| static gboolean |
| gst_osx_video_src_start (GstBaseSrc * src) |
| { |
| GstOSXVideoSrc *self; |
| GObjectClass *gobject_class; |
| GstOSXVideoSrcClass *klass; |
| ComponentResult err; |
| |
| self = GST_OSX_VIDEO_SRC (src); |
| gobject_class = G_OBJECT_GET_CLASS (src); |
| klass = GST_OSX_VIDEO_SRC_CLASS (gobject_class); |
| |
| GST_DEBUG_OBJECT (src, "entering"); |
| |
| if (!klass->movies_enabled) |
| return FALSE; |
| |
| self->seq_num = 0; |
| |
| self->seq_grab = OpenDefaultComponent (SeqGrabComponentType, 0); |
| if (self->seq_grab == NULL) { |
| err = paramErr; |
| GST_ERROR_OBJECT (self, "OpenDefaultComponent failed. paramErr=%d", |
| (int) err); |
| goto fail; |
| } |
| |
| err = SGInitialize (self->seq_grab); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGInitialize returned %d", (int) err); |
| goto fail; |
| } |
| |
| err = SGSetDataRef (self->seq_grab, 0, 0, seqGrabDontMakeMovie); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGSetDataRef returned %d", (int) err); |
| goto fail; |
| } |
| |
| err = SGNewChannel (self->seq_grab, VideoMediaType, &self->video_chan); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGNewChannel returned %d", (int) err); |
| goto fail; |
| } |
| |
| if (!device_select (self)) |
| goto fail; |
| |
| GST_DEBUG_OBJECT (self, "started"); |
| return TRUE; |
| |
| fail: |
| self->video_chan = NULL; |
| |
| if (self->seq_grab) { |
| err = CloseComponent (self->seq_grab); |
| if (err != noErr) |
| GST_WARNING_OBJECT (self, "CloseComponent returned %d", (int) err); |
| self->seq_grab = NULL; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_osx_video_src_stop (GstBaseSrc * src) |
| { |
| GstOSXVideoSrc *self; |
| ComponentResult err; |
| |
| self = GST_OSX_VIDEO_SRC (src); |
| |
| GST_DEBUG_OBJECT (src, "stopping"); |
| |
| self->video_chan = NULL; |
| |
| err = CloseComponent (self->seq_grab); |
| if (err != noErr) |
| GST_WARNING_OBJECT (self, "CloseComponent returned %d", (int) err); |
| self->seq_grab = NULL; |
| |
| DisposeGWorld (self->world); |
| self->world = NULL; |
| |
| if (self->buffer != NULL) { |
| gst_buffer_unref (self->buffer); |
| self->buffer = NULL; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_osx_video_src_query (GstBaseSrc * bsrc, GstQuery * query) |
| { |
| GstOSXVideoSrc *self; |
| gboolean res = FALSE; |
| |
| self = GST_OSX_VIDEO_SRC (bsrc); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_LATENCY: |
| { |
| GstClockTime min_latency, max_latency; |
| gint fps_n, fps_d; |
| |
| fps_n = FRAMERATE; |
| fps_d = 1; |
| |
| /* min latency is the time to capture one frame */ |
| min_latency = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); |
| |
| /* max latency is total duration of the frame buffer */ |
| // FIXME: we don't know what this is, so we'll just say 2 frames |
| max_latency = 2 * min_latency; |
| |
| GST_DEBUG_OBJECT (bsrc, |
| "report latency min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT, |
| GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); |
| |
| /* we are always live, the min latency is 1 frame and the max latency is |
| * the complete buffer of frames. */ |
| gst_query_set_latency (query, TRUE, min_latency, max_latency); |
| |
| res = TRUE; |
| break; |
| } |
| default: |
| res = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static GstStateChangeReturn |
| gst_osx_video_src_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstStateChangeReturn result; |
| GstOSXVideoSrc *self; |
| ComponentResult err; |
| |
| result = GST_STATE_CHANGE_SUCCESS; |
| self = GST_OSX_VIDEO_SRC (element); |
| |
| // ###: prepare_capture in READY->PAUSED? |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_PLAYING: |
| { |
| ImageDescriptionHandle imageDesc; |
| Rect sourceRect; |
| MatrixRecord scaleMatrix; |
| |
| if (!prepare_capture (self)) |
| return GST_STATE_CHANGE_FAILURE; |
| |
| // ###: should we start recording /after/ making the decompressionsequence? |
| // CocoaSequenceGrabber does it beforehand, so we do too, but it feels |
| // wrong. |
| err = SGStartRecord (self->seq_grab); |
| if (err != noErr) { |
| /* since we prepare here, we should also unprepare */ |
| SGRelease (self->seq_grab); |
| |
| GST_ERROR_OBJECT (self, "SGStartRecord returned %d", (int) err); |
| return GST_STATE_CHANGE_FAILURE; |
| } |
| |
| imageDesc = (ImageDescriptionHandle) NewHandle (0); |
| |
| err = SGGetChannelSampleDescription (self->video_chan, |
| (Handle) imageDesc); |
| if (err != noErr) { |
| SGStop (self->seq_grab); |
| SGRelease (self->seq_grab); |
| DisposeHandle ((Handle) imageDesc); |
| GST_ERROR_OBJECT (self, "SGGetChannelSampleDescription returned %d", |
| (int) err); |
| return GST_STATE_CHANGE_FAILURE; |
| } |
| |
| GST_DEBUG_OBJECT (self, "actual capture resolution is %dx%d", |
| (int) (**imageDesc).width, (int) (**imageDesc).height); |
| |
| SetRect (&sourceRect, 0, 0, (**imageDesc).width, (**imageDesc).height); |
| RectMatrix (&scaleMatrix, &sourceRect, &self->rect); |
| |
| err = DecompressSequenceBegin (&self->dec_seq, imageDesc, self->world, |
| NULL, NULL, &scaleMatrix, srcCopy, NULL, 0, codecNormalQuality, |
| bestSpeedCodec); |
| if (err != noErr) { |
| SGStop (self->seq_grab); |
| SGRelease (self->seq_grab); |
| DisposeHandle ((Handle) imageDesc); |
| GST_ERROR_OBJECT (self, "DecompressSequenceBegin returned %d", |
| (int) err); |
| return GST_STATE_CHANGE_FAILURE; |
| } |
| |
| DisposeHandle ((Handle) imageDesc); |
| break; |
| } |
| default: |
| break; |
| } |
| |
| result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| if (result == GST_STATE_CHANGE_FAILURE) |
| return result; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| SGStop (self->seq_grab); |
| |
| err = CDSequenceEnd (self->dec_seq); |
| if (err != noErr) |
| GST_WARNING_OBJECT (self, "CDSequenceEnd returned %d", (int) err); |
| self->dec_seq = 0; |
| |
| SGRelease (self->seq_grab); |
| break; |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| static GstFlowReturn |
| gst_osx_video_src_create (GstPushSrc * src, GstBuffer ** buf) |
| { |
| GstOSXVideoSrc *self = GST_OSX_VIDEO_SRC (src); |
| ComponentResult err; |
| GstCaps *caps; |
| //GstClock * clock; |
| |
| // ###: we need to sleep between calls to SGIdle. originally, the sleeping |
| // was done using gst_clock_id_wait(), but it turns out that approach |
| // doesn't work well. it has two issues: |
| // 1) every so often, gst_clock_id_wait() will block for a much longer |
| // period of time than requested (upwards of a minute) causing video |
| // to freeze until it finally returns. this seems to happen once |
| // every few minutes, which probably means something like 1 in every |
| // several hundred calls gst_clock_id_wait() does the wrong thing. |
| // 2) even when the gst_clock approach is working properly, it uses |
| // quite a bit of cpu in comparison to a simple usleep(). on one |
| // test machine, using gst_clock_id_wait() caused osxvideosrc to use |
| // nearly 100% cpu, while using usleep() brough the usage to less |
| // than 10%. |
| // |
| // so, for now, we comment out the gst_clock stuff and use usleep. |
| |
| //clock = gst_system_clock_obtain (); |
| do { |
| err = SGIdle (self->seq_grab); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGIdle returned %d", (int) err); |
| gst_object_unref (clock); |
| return GST_FLOW_UNEXPECTED; |
| } |
| |
| if (self->buffer == NULL) { |
| /*GstClockID clock_id; |
| |
| clock_id = gst_clock_new_single_shot_id (clock, |
| (GstClockTime) (gst_clock_get_time(clock) + |
| (GST_SECOND / ((float)FRAMERATE * 2)))); |
| gst_clock_id_wait (clock_id, NULL); |
| gst_clock_id_unref (clock_id); */ |
| |
| usleep (1000000 / (FRAMERATE * 2)); |
| } |
| } while (self->buffer == NULL); |
| //gst_object_unref (clock); |
| |
| *buf = self->buffer; |
| self->buffer = NULL; |
| |
| caps = gst_pad_get_caps (GST_BASE_SRC_PAD (src)); |
| gst_buffer_set_caps (*buf, caps); |
| gst_caps_unref (caps); |
| |
| return GST_FLOW_OK; |
| } |
| |
| static OSErr |
| data_proc (SGChannel c, Ptr p, long len, long *offset, long chRefCon, |
| TimeValue time, short writeType, long refCon) |
| { |
| GstOSXVideoSrc *self; |
| gint fps_n, fps_d; |
| GstClockTime duration, timestamp, latency; |
| CodecFlags flags; |
| ComponentResult err; |
| PixMapHandle hPixMap; |
| Rect portRect; |
| int pix_rowBytes; |
| void *pix_ptr; |
| int pix_height; |
| int pix_size; |
| |
| self = GST_OSX_VIDEO_SRC (refCon); |
| |
| if (self->buffer != NULL) { |
| gst_buffer_unref (self->buffer); |
| self->buffer = NULL; |
| } |
| |
| err = DecompressSequenceFrameS (self->dec_seq, p, len, 0, &flags, NULL); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "DecompressSequenceFrameS returned %d", (int) err); |
| return err; |
| } |
| |
| hPixMap = GetGWorldPixMap (self->world); |
| LockPixels (hPixMap); |
| GetPortBounds (self->world, &portRect); |
| pix_rowBytes = (int) GetPixRowBytes (hPixMap); |
| pix_ptr = GetPixBaseAddr (hPixMap); |
| pix_height = (portRect.bottom - portRect.top); |
| pix_size = pix_rowBytes * pix_height; |
| |
| GST_DEBUG_OBJECT (self, "num=%5d, height=%d, rowBytes=%d, size=%d", |
| self->seq_num, pix_height, pix_rowBytes, pix_size); |
| |
| fps_n = FRAMERATE; |
| fps_d = 1; |
| |
| duration = gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); |
| latency = duration; |
| |
| timestamp = gst_clock_get_time (GST_ELEMENT_CAST (self)->clock); |
| timestamp -= gst_element_get_base_time (GST_ELEMENT_CAST (self)); |
| if (timestamp > latency) |
| timestamp -= latency; |
| else |
| timestamp = 0; |
| |
| self->buffer = gst_buffer_new_and_alloc (pix_size); |
| GST_BUFFER_OFFSET (self->buffer) = self->seq_num; |
| GST_BUFFER_TIMESTAMP (self->buffer) = timestamp; |
| memcpy (GST_BUFFER_DATA (self->buffer), pix_ptr, pix_size); |
| |
| self->seq_num++; |
| |
| UnlockPixels (hPixMap); |
| |
| return noErr; |
| } |
| |
| static gboolean |
| prepare_capture (GstOSXVideoSrc * self) |
| { |
| ComponentResult err; |
| |
| err = SGSetChannelUsage (self->video_chan, seqGrabRecord); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGSetChannelUsage returned %d", (int) err); |
| return FALSE; |
| } |
| |
| err = SGSetDataProc (self->seq_grab, NewSGDataUPP (data_proc), (long) self); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGSetDataProc returned %d", (int) err); |
| return FALSE; |
| } |
| |
| err = SGPrepare (self->seq_grab, false, true); |
| if (err != noErr) { |
| GST_ERROR_OBJECT (self, "SGPrepare returnd %d", (int) err); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static const GList * |
| probe_get_properties (GstPropertyProbe * probe) |
| { |
| GObjectClass *klass = G_OBJECT_GET_CLASS (probe); |
| static GList *list = NULL; |
| |
| // ###: from gstalsadeviceprobe.c |
| /* well, not perfect, but better than no locking at all. |
| * In the worst case we leak a list node, so who cares? */ |
| GST_CLASS_LOCK (GST_OBJECT_CLASS (klass)); |
| |
| if (!list) { |
| GParamSpec *pspec; |
| |
| pspec = g_object_class_find_property (klass, "device"); |
| list = g_list_append (NULL, pspec); |
| } |
| |
| GST_CLASS_UNLOCK (GST_OBJECT_CLASS (klass)); |
| |
| return list; |
| } |
| |
| static void |
| probe_probe_property (GstPropertyProbe * probe, guint prop_id, |
| const GParamSpec * pspec) |
| { |
| /* we do nothing in here. the actual "probe" occurs in get_values(), |
| * which is a common practice when not caching responses. |
| */ |
| |
| if (!g_str_equal (pspec->name, "device")) { |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); |
| } |
| } |
| |
| static gboolean |
| probe_needs_probe (GstPropertyProbe * probe, guint prop_id, |
| const GParamSpec * pspec) |
| { |
| /* don't cache probed data */ |
| return TRUE; |
| } |
| |
| static GValueArray * |
| probe_get_values (GstPropertyProbe * probe, guint prop_id, |
| const GParamSpec * pspec) |
| { |
| GstOSXVideoSrc *src; |
| GValueArray *array; |
| GValue value = { 0, }; |
| GList *l, *list; |
| video_device *dev; |
| |
| if (!g_str_equal (pspec->name, "device")) { |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); |
| return NULL; |
| } |
| |
| src = GST_OSX_VIDEO_SRC (probe); |
| |
| list = device_list (src); |
| |
| if (list == NULL) { |
| GST_LOG_OBJECT (probe, "No devices found"); |
| return NULL; |
| } |
| |
| array = g_value_array_new (g_list_length (list)); |
| g_value_init (&value, G_TYPE_STRING); |
| for (l = list; l != NULL; l = l->next) { |
| dev = (video_device *) l->data; |
| GST_LOG_OBJECT (probe, "Found device: %s", dev->id); |
| g_value_take_string (&value, dev->id); |
| dev->id = NULL; |
| video_device_free (dev); |
| l->data = NULL; |
| g_value_array_append (array, &value); |
| } |
| g_value_unset (&value); |
| g_list_free (list); |
| |
| return array; |
| } |
| |
| static void |
| gst_osx_video_src_property_probe_interface_init (GstPropertyProbeInterface * |
| iface) |
| { |
| iface->get_properties = probe_get_properties; |
| iface->probe_property = probe_probe_property; |
| iface->needs_probe = probe_needs_probe; |
| iface->get_values = probe_get_values; |
| } |
| |
| void |
| gst_osx_video_src_type_add_device_property_probe_interface (GType type) |
| { |
| static const GInterfaceInfo probe_iface_info = { |
| (GInterfaceInitFunc) gst_osx_video_src_property_probe_interface_init, |
| NULL, |
| NULL, |
| }; |
| |
| g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE, |
| &probe_iface_info); |
| } |