add osxvideosrc
authorAndy Wingo <wingo@oblong.net>
Mon, 20 Apr 2009 11:09:46 +0000 (13:09 +0200)
committerAndy Wingo <wingo@wingomac.bcn.oblong.net>
Mon, 20 Apr 2009 14:18:59 +0000 (16:18 +0200)
* configure.ac:
* sys/Makefile.am:
* sys/osxvideo/Makefile.am: Autoconfiscation.

* sys/osxvideo/osxvideoplugin.m:
* sys/osxvideo/osxvideosrc.h:
* sys/osxvideo/osxvideosrc.c: Add osxvideosrc. Should fix #153684.

Patch-by: Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
Patch-by: Ali Sabil <ali.sabil@tandberg.com>
Patch-by: Barracuda Networks <justin@affinix.com>
configure.ac
sys/Makefile.am
sys/osxvideo/Makefile.am [new file with mode: 0644]
sys/osxvideo/osxvideoplugin.m [new file with mode: 0644]
sys/osxvideo/osxvideosrc.c [new file with mode: 0644]
sys/osxvideo/osxvideosrc.h [new file with mode: 0644]

index 05051b263bc78c853ed756490c72fa72c1b5128e..58cacad4b9716820c1ba357f7c53321138e71ed1 100644 (file)
@@ -359,6 +359,22 @@ fi
 
 dnl *** sys plug-ins ***
 
+dnl *** OS X videosrc ***
+translit(dnm, m, l) AM_CONDITIONAL(USE_OSX_VIDEO, true)
+HAVE_OSX_VIDEO="no"
+AG_GST_CHECK_FEATURE(OSX_VIDEO, [OSX video], osxvideosrc, [
+  AC_CHECK_HEADER(Quicktime/Quicktime.h, HAVE_OSX_VIDEO="yes", HAVE_OSX_VIDEO="no")
+])
+dnl in case header Quicktime/Quicktime.h is found on other platforms
+case "$host" in
+  *-*darwin*)
+    dnl do nothing
+    ;;
+  *)
+    HAVE_OSX_VIDEO="no"
+    ;;
+esac
+
 dnl check for QuickTime
 translit(dnm, m, l) AM_CONDITIONAL(USE_QUICKTIME, true)
 AG_GST_CHECK_FEATURE(QUICKTIME, [QuickTime wrapper], qtwrapper, [
@@ -1565,6 +1581,7 @@ sys/dshowvideosink/Makefile
 sys/dvb/Makefile
 sys/fbdev/Makefile
 sys/oss4/Makefile
+sys/osxvideo/Makefile
 sys/qtwrapper/Makefile
 sys/vcd/Makefile
 sys/wasapi/Makefile
index eb1ea7261b2f36e6f6056229801c4b459129d54a..e6212c659bf9be87fc4c8b8672e2fb8646d21016 100644 (file)
@@ -40,6 +40,12 @@ else
 OSS4_DIR=
 endif
 
+if USE_OSX_VIDEO
+OSX_VIDEO_DIR=osxvideo
+else
+OSX_VIDEO_DIR=
+endif
+
 if USE_QUICKTIME
 QT_DIR=qtwrapper
 else
@@ -58,8 +64,8 @@ else
 ACM_DIR=
 endif
 
-SUBDIRS = $(ACM_DIR) $(DVB_DIR) $(FBDEV_DIR) $(OSS4_DIR) $(QT_DIR) $(VCD_DIR) $(WININET_DIR)
+SUBDIRS = $(ACM_DIR) $(DVB_DIR) $(FBDEV_DIR) $(OSS4_DIR) $(OSX_VIDEO_DIR) $(QT_DIR) $(VCD_DIR) $(WININET_DIR)
 
 DIST_SUBDIRS = acmenc acmmp3dec dvb fbdev dshowdecwrapper dshowsrcwrapper dshowvideosink \
-                          oss4 qtwrapper vcd wasapi wininet winks winscreencap
+                          oss4 osxvideo qtwrapper vcd wasapi wininet winks winscreencap
 
diff --git a/sys/osxvideo/Makefile.am b/sys/osxvideo/Makefile.am
new file mode 100644 (file)
index 0000000..dd46e99
--- /dev/null
@@ -0,0 +1,18 @@
+
+plugin_LTLIBRARIES = libgstosxvideosrc.la
+
+libgstosxvideosrc_la_SOURCES = osxvideoplugin.m osxvideosrc.c
+libgstosxvideosrc_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \
+       $(GST_PLUGINS_BASE_CFLAGS) -Wno-deprecated-declarations
+libgstosxvideosrc_la_LIBADD =  \
+       $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) -lgstvideo-$(GST_MAJORMINOR) \
+       -lgstinterfaces-$(GST_MAJORMINOR)
+
+libgstosxvideosrc_la_LIBTOOLFLAGS = --tag=disable-static
+
+libgstosxvideosrc_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) \
+       -Wl,-framework -Wl,Cocoa -Wl,-framework -Wl,QuickTime
+
+AM_OBJCFLAGS=$(CFLAGS) $(GST_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS)
+
+noinst_HEADERS = osxvideosrc.h
diff --git a/sys/osxvideo/osxvideoplugin.m b/sys/osxvideo/osxvideoplugin.m
new file mode 100644 (file)
index 0000000..0199dc9
--- /dev/null
@@ -0,0 +1,50 @@
+/* GStreamer
+ * OSX video src
+ * Copyright (C) 2004-6 Zaheer Abbas Merali <zaheerabbas at merali dot org>
+ * Copyright (C) 2007 Pioneers of the Inevitable <songbird@songbirdnest.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.
+ *
+ * The development of this code was made possible due to the involvement of
+ * Pioneers of the Inevitable, the creators of the Songbird Music player.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Object header */
+#include "osxvideosrc.h"
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  if (!gst_element_register (plugin, "osxvideosrc",
+          GST_RANK_PRIMARY, GST_TYPE_OSX_VIDEO_SRC))
+    return FALSE;
+
+  GST_DEBUG_CATEGORY_INIT (gst_debug_osx_video_src, "osxvideosrc", 0,
+                           "osxvideosrc element");
+
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "osxvideosrc",
+    "OSX native video input plugin",
+    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/sys/osxvideo/osxvideosrc.c b/sys/osxvideo/osxvideosrc.c
new file mode 100644 (file)
index 0000000..c054cca
--- /dev/null
@@ -0,0 +1,1394 @@
+/*
+ * 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;
+
+  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 ()
+{
+  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;
+  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) {
+    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)
+{
+  static GstElementDetails element_details = {
+      "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>"
+  };
+
+  GstElementClass * element_class = GST_ELEMENT_CLASS (gclass);
+
+  GST_DEBUG (G_STRFUNC);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&src_template));
+
+  gst_element_class_set_details (element_class, &element_details);
+}
+
+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 (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, 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, 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, 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, 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);
+}
diff --git a/sys/osxvideo/osxvideosrc.h b/sys/osxvideo/osxvideosrc.h
new file mode 100644 (file)
index 0000000..6fef353
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * GStreamer
+ * Copyright 2007 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ * Copyright 2007 Ali Sabil <ali.sabil@tandberg.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.
+ */
+
+#ifndef __GST_OSX_VIDEO_SRC_H__
+#define __GST_OSX_VIDEO_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstpushsrc.h>
+#include <Quicktime/Quicktime.h>
+
+GST_DEBUG_CATEGORY_EXTERN (gst_debug_osx_video_src);
+
+G_BEGIN_DECLS
+
+/* #defines don't like whitespacey bits */
+#define GST_TYPE_OSX_VIDEO_SRC \
+  (gst_osx_video_src_get_type())
+#define GST_OSX_VIDEO_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OSX_VIDEO_SRC,GstOSXVideoSrc))
+#define GST_OSX_VIDEO_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OSX_VIDEO_SRC,GstOSXVideoSrcClass))
+#define GST_IS_OSX_VIDEO_SRC(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OSX_VIDEO_SRC))
+#define GST_IS_OSX_VIDEO_SRC_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OSX_VIDEO_SRC))
+
+typedef struct _GstOSXVideoSrc GstOSXVideoSrc;
+typedef struct _GstOSXVideoSrcClass GstOSXVideoSrcClass;
+
+struct _GstOSXVideoSrc
+{
+  GstPushSrc pushsrc;
+
+  gchar * device_id;
+  gchar * device_name;
+  SeqGrabComponent seq_grab;
+  SGChannel video_chan;
+  GWorldPtr world;
+  Rect rect;
+  ImageSequence dec_seq;
+
+  GstBuffer * buffer;
+  guint seq_num;
+};
+
+struct _GstOSXVideoSrcClass
+{
+  GstPushSrcClass parent_class;
+  gboolean movies_enabled;
+};
+
+GType gst_osx_video_src_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_OSX_VIDEO_SRC_H__ */