mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 11:10:37 +00:00
af97600798
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
597 lines
15 KiB
C
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)
|