gstreamer/sys/vcd/vcdsrc.c
Bastien Nocera af97600798 vcdsrc: change VCD URI handler to the DVD scheme
The current URI parsing code doesn't allow setting the "device"
from which the VCD should be read.

Use the same structure as the DVD URI handling instead, so that
devices can be passed in the URI, as well as track number.

Up the rank of the VCD plugin so that it can be auto-plugged and
used by Totem.

https://bugzilla.gnome.org/show_bug.cgi?id=340986
2010-05-22 10:30:44 +01:00

597 lines
15 KiB
C

/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include "vcdsrc.h"
#define DEFAULT_DEVICE "/dev/cdrom"
/* VCDSrc signals and args */
enum
{
/* FILL ME */
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_DEVICE,
PROP_TRACK,
PROP_MAX_ERRORS
};
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
GST_DEBUG_CATEGORY_STATIC (gst_vcdsrc_debug);
#define GST_CAT_DEFAULT gst_vcdsrc_debug
static void gst_vcdsrc_uri_handler_init (gpointer g_iface, gpointer iface_data);
static void
gst_vcdsrc_setup_interfaces (GType type)
{
static const GInterfaceInfo urihandler_info = {
gst_vcdsrc_uri_handler_init,
NULL,
NULL,
};
g_type_add_interface_static (type, GST_TYPE_URI_HANDLER, &urihandler_info);
}
GST_BOILERPLATE_FULL (GstVCDSrc, gst_vcdsrc, GstPushSrc, GST_TYPE_PUSH_SRC,
gst_vcdsrc_setup_interfaces);
static void gst_vcdsrc_finalize (GObject * object);
static void gst_vcdsrc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_vcdsrc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gboolean gst_vcdsrc_start (GstBaseSrc * src);
static gboolean gst_vcdsrc_stop (GstBaseSrc * src);
static GstFlowReturn gst_vcdsrc_create (GstPushSrc * src, GstBuffer ** out);
static void
gst_vcdsrc_base_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
gst_element_class_set_details_simple (element_class, "VCD Source",
"Source/File",
"Asynchronous read from VCD disk", "Erik Walthinsen <omega@cse.ogi.edu>");
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&srctemplate));
}
static void
gst_vcdsrc_class_init (GstVCDSrcClass * klass)
{
GObjectClass *gobject_class;
GstBaseSrcClass *basesrc_class;
GstPushSrcClass *pushsrc_class;
gobject_class = (GObjectClass *) klass;
basesrc_class = (GstBaseSrcClass *) klass;
pushsrc_class = (GstPushSrcClass *) klass;
gobject_class->set_property = gst_vcdsrc_set_property;
gobject_class->get_property = gst_vcdsrc_get_property;
gobject_class->finalize = gst_vcdsrc_finalize;
g_object_class_install_property (gobject_class, PROP_DEVICE,
g_param_spec_string ("device", "Device",
"CD device location", NULL, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_TRACK,
g_param_spec_int ("track", "Track",
"Track number to play", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE));
g_object_class_install_property (gobject_class, PROP_MAX_ERRORS,
g_param_spec_int ("max-errors", "Max. errors",
"Maximum number of errors before bailing out",
0, G_MAXINT, 16, G_PARAM_READWRITE));
basesrc_class->start = gst_vcdsrc_start;
basesrc_class->stop = gst_vcdsrc_stop;
pushsrc_class->create = gst_vcdsrc_create;
GST_DEBUG_CATEGORY_INIT (gst_vcdsrc_debug, "vcdsrc", 0,
"VideoCD Source element");
}
static void
gst_vcdsrc_init (GstVCDSrc * vcdsrc, GstVCDSrcClass * klass)
{
vcdsrc->device = g_strdup (DEFAULT_DEVICE);
vcdsrc->track = 1;
vcdsrc->fd = 0;
vcdsrc->trackoffset = 0;
vcdsrc->curoffset = 0;
vcdsrc->bytes_per_read = VCD_BYTES_PER_SECTOR;
vcdsrc->max_errors = 16;
}
static void
gst_vcdsrc_finalize (GObject * object)
{
GstVCDSrc *vcdsrc = GST_VCDSRC (object);
g_free (vcdsrc->device);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static inline guint64
gst_vcdsrc_msf (GstVCDSrc * vcdsrc, gint track)
{
return (vcdsrc->tracks[track].cdte_addr.msf.minute * 60 +
vcdsrc->tracks[track].cdte_addr.msf.second) * 75 +
vcdsrc->tracks[track].cdte_addr.msf.frame;
}
static void
gst_vcdsrc_recalculate (GstVCDSrc * vcdsrc)
{
/* calculate track offset (beginning of track) */
vcdsrc->trackoffset = gst_vcdsrc_msf (vcdsrc, vcdsrc->track);
GST_DEBUG ("track offset is %ld", vcdsrc->trackoffset);
}
static void
gst_vcdsrc_set_property (GObject * object, guint prop_id, const GValue * value,
GParamSpec * pspec)
{
GstVCDSrc *src;
src = GST_VCDSRC (object);
switch (prop_id) {
case PROP_DEVICE:
g_free (src->device);
src->device = g_value_dup_string (value);
break;
case PROP_TRACK:
if (g_value_get_int (value) >= 1 &&
g_value_get_int (value) < src->numtracks) {
src->track = g_value_get_int (value);
gst_vcdsrc_recalculate (src);
}
break;
case PROP_MAX_ERRORS:
src->max_errors = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_vcdsrc_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstVCDSrc *src;
g_return_if_fail (GST_IS_VCDSRC (object));
src = GST_VCDSRC (object);
switch (prop_id) {
case PROP_DEVICE:
g_value_set_string (value, src->device);
break;
case PROP_TRACK:
g_value_set_int (value, src->track);
break;
case PROP_MAX_ERRORS:
g_value_set_int (value, src->max_errors);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
#if 0
static gboolean
gst_vcdsrc_srcpad_event (GstPad * pad, GstEvent * event)
{
GstVCDSrc *vcdsrc = GST_VCDSRC (gst_pad_get_parent (pad));
gboolean res = TRUE;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:{
gint64 new_off;
if (GST_EVENT_SEEK_FORMAT (event) != GST_FORMAT_BYTES)
return FALSE;
new_off = GST_EVENT_SEEK_OFFSET (event);
switch (GST_EVENT_SEEK_METHOD (event)) {
case GST_SEEK_METHOD_SET:
/* no-op */
break;
case GST_SEEK_METHOD_CUR:
new_off += vcdsrc->curoffset * vcdsrc->bytes_per_read;
break;
case GST_SEEK_METHOD_END:
new_off = (gst_vcdsrc_msf (vcdsrc, vcdsrc->track + 1) -
vcdsrc->trackoffset) * vcdsrc->bytes_per_read - new_off;
break;
default:
return FALSE;
}
if (new_off < 0 ||
new_off > (gst_vcdsrc_msf (vcdsrc, vcdsrc->track + 1) -
vcdsrc->trackoffset) * vcdsrc->bytes_per_read)
return FALSE;
vcdsrc->curoffset = new_off / vcdsrc->bytes_per_read;
vcdsrc->tempoffset = new_off % vcdsrc->bytes_per_read;
vcdsrc->discont = TRUE;
if (GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_FLUSH)
vcdsrc->flush = TRUE;
break;
}
default:
res = FALSE;
break;
}
gst_event_unref (event);
return res;
}
#endif
#if 0
static gboolean
gst_vcdsrc_srcpad_query (GstPad * pad, GstQueryType type,
GstFormat * format, gint64 * value)
{
GstVCDSrc *vcdsrc = GST_VCDSRC (gst_pad_get_parent (pad));
gboolean res = TRUE;
if (*format != GST_FORMAT_BYTES)
return FALSE;
switch (type) {
case GST_QUERY_TOTAL:
*value = (gst_vcdsrc_msf (vcdsrc, vcdsrc->track + 1) -
vcdsrc->trackoffset) * vcdsrc->bytes_per_read;
break;
case GST_QUERY_POSITION:
*value = vcdsrc->curoffset * vcdsrc->bytes_per_read;
break;
default:
res = FALSE;
break;
}
return res;
}
#endif
static GstFlowReturn
gst_vcdsrc_create (GstPushSrc * src, GstBuffer ** buf)
{
GstVCDSrc *vcdsrc;
GstBuffer *outbuf;
gulong offset;
struct cdrom_msf *msf;
gint error_count = 0;
vcdsrc = GST_VCDSRC (src);
offset = vcdsrc->trackoffset + vcdsrc->curoffset;
if (offset >= gst_vcdsrc_msf (vcdsrc, vcdsrc->track + 1))
goto eos;
/* create the buffer */
outbuf = gst_buffer_new_and_alloc (vcdsrc->bytes_per_read);
msf = (struct cdrom_msf *) GST_BUFFER_DATA (outbuf);
read:
/* read it in from the device */
msf->cdmsf_frame0 = offset % 75;
msf->cdmsf_sec0 = (offset / 75) % 60;
msf->cdmsf_min0 = (offset / (75 * 60));
GST_LOG ("msf is %d:%d:%d", msf->cdmsf_min0, msf->cdmsf_sec0,
msf->cdmsf_frame0);
if (ioctl (vcdsrc->fd, CDROMREADRAW, msf) < 0) {
if (++error_count <= vcdsrc->max_errors) {
vcdsrc->curoffset++;
offset++;
goto read;
}
GST_ELEMENT_ERROR (vcdsrc, RESOURCE, READ, (NULL),
("Read from cdrom at %d:%d:%d failed: %s",
msf->cdmsf_min0, msf->cdmsf_sec0, msf->cdmsf_frame0,
strerror (errno)));
return GST_FLOW_ERROR;
}
GST_BUFFER_SIZE (outbuf) = vcdsrc->bytes_per_read;
vcdsrc->curoffset += 1;
*buf = outbuf;
return GST_FLOW_OK;
/* ERRORS */
eos:
{
GST_DEBUG_OBJECT (vcdsrc, "got eos");
return GST_FLOW_UNEXPECTED;
}
}
/* open the file, necessary to go to RUNNING state */
static gboolean
gst_vcdsrc_start (GstBaseSrc * bsrc)
{
int i;
GstVCDSrc *src = GST_VCDSRC (bsrc);
struct stat buf;
/* open the device */
src->fd = open (src->device, O_RDONLY);
if (src->fd < 0)
goto open_failed;
if (fstat (src->fd, &buf) < 0)
goto toc_failed;
/* If it's not a block device, then we need to try and
* parse the cue file if there is one
* FIXME implement */
if (!S_ISBLK (buf.st_mode)) {
GST_DEBUG ("Reading CUE files not handled yet, cannot process %s",
GST_STR_NULL (src->device));
goto toc_failed;
}
/* read the table of contents */
if (ioctl (src->fd, CDROMREADTOCHDR, &src->tochdr))
goto toc_failed;
/* allocate enough track structs for disk */
src->numtracks = (src->tochdr.cdth_trk1 - src->tochdr.cdth_trk0) + 1;
src->tracks = g_new (struct cdrom_tocentry, src->numtracks + 1);
/* read each track entry */
for (i = 0; i <= src->numtracks; i++) {
src->tracks[i].cdte_track = i == src->numtracks ? CDROM_LEADOUT : i + 1;
src->tracks[i].cdte_format = CDROM_MSF;
if (ioctl (src->fd, CDROMREADTOCENTRY, &src->tracks[i]))
goto toc_entry_failed;
GST_DEBUG ("track %d begins at %d:%02d.%02d", i,
src->tracks[i].cdte_addr.msf.minute,
src->tracks[i].cdte_addr.msf.second,
src->tracks[i].cdte_addr.msf.frame);
}
src->curoffset = 0;
gst_vcdsrc_recalculate (src);
return TRUE;
/* ERRORS */
open_failed:
{
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), GST_ERROR_SYSTEM);
return FALSE;
}
toc_failed:
{
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), GST_ERROR_SYSTEM);
close (src->fd);
return FALSE;
}
toc_entry_failed:
{
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), GST_ERROR_SYSTEM);
g_free (src->tracks);
close (src->fd);
return FALSE;
}
}
/* close the file */
static gboolean
gst_vcdsrc_stop (GstBaseSrc * bsrc)
{
GstVCDSrc *src = GST_VCDSRC (bsrc);
/* close the file */
close (src->fd);
/* zero out a lot of our state */
src->fd = 0;
src->curoffset = 0;
g_free (src->tracks);
return TRUE;
}
/*
* URI interface.
*/
static guint
gst_vcdsrc_uri_get_type (void)
{
return GST_URI_SRC;
}
static gchar **
gst_vcdsrc_uri_get_protocols (void)
{
static gchar *protocols[] = { (char *) "vcd", NULL };
return protocols;
}
static const gchar *
gst_vcdsrc_uri_get_uri (GstURIHandler * handler)
{
GstVCDSrc *src = GST_VCDSRC (handler);
gchar *result;
GST_OBJECT_LOCK (src);
result = g_strdup_printf ("vcd://%d", src->track);
GST_OBJECT_UNLOCK (src);
return result;
}
static gboolean
gst_vcdsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri)
{
GstVCDSrc *src = GST_VCDSRC (handler);
gchar *protocol;
gchar *location = NULL;
gint tracknr;
GST_DEBUG_OBJECT (src, "setting uri '%s'", uri);
protocol = gst_uri_get_protocol (uri);
if (protocol == NULL || strcmp (protocol, "vcd"))
goto wrong_protocol;
GST_DEBUG_OBJECT (src, "have protocol '%s'", protocol);
g_free (protocol);
/* parse out the track in the location */
if (!(location = gst_uri_get_location (uri)))
goto no_location;
GST_DEBUG_OBJECT (src, "have location '%s'", location);
/*
* URI structure: vcd:///path/to/device,track-num
*/
if (g_str_has_prefix (uri, "vcd://")) {
GST_OBJECT_LOCK (src);
g_free (src->device);
if (strlen (uri) > 6)
src->device = g_strdup (uri + 6);
else
src->device = g_strdup (DEFAULT_DEVICE);
GST_DEBUG_OBJECT (src, "configured device %s", src->device);
GST_OBJECT_UNLOCK (src);
}
/* Parse the track number */
{
char **split;
split = g_strsplit (location, ",", 2);
if (split == NULL || *split == NULL || split[1] == NULL) {
tracknr = 1;
} else if (sscanf (split[1], "%d", &tracknr) != 1 || tracknr < 1) {
g_strfreev (split);
goto invalid_location;
}
g_strfreev (split);
}
GST_OBJECT_LOCK (src);
src->track = tracknr;
GST_DEBUG_OBJECT (src, "configured track %d", src->track);
GST_OBJECT_UNLOCK (src);
g_free (location);
return TRUE;
/* ERRORS */
wrong_protocol:
{
GST_ERROR_OBJECT (src, "wrong protocol %s specified",
GST_STR_NULL (protocol));
g_free (protocol);
return FALSE;
}
no_location:
{
GST_ERROR_OBJECT (src, "no location specified");
return FALSE;
}
invalid_location:
{
GST_ERROR_OBJECT (src, "Invalid location %s in URI '%s'", location, uri);
g_free (location);
return FALSE;
}
}
static void
gst_vcdsrc_uri_handler_init (gpointer g_iface, gpointer iface_data)
{
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
iface->get_type = gst_vcdsrc_uri_get_type;
iface->get_protocols = gst_vcdsrc_uri_get_protocols;
iface->get_uri = gst_vcdsrc_uri_get_uri;
iface->set_uri = gst_vcdsrc_uri_set_uri;
}
static gboolean
plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "vcdsrc", GST_RANK_SECONDARY,
GST_TYPE_VCDSRC);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
"vcdsrc",
"Asynchronous read from VCD disk",
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)