gstreamer/sys/osxvideo/osxvideosrc.c

1393 lines
36 KiB
C

/*
* 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 ()
{
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 (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 (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);
}