diff -r 000000000000 -r 0e761a78d257 gst_plugins_base/ext/cdparanoia/gstcdparanoiasrc.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gst_plugins_base/ext/cdparanoia/gstcdparanoiasrc.c Thu Dec 17 08:53:32 2009 +0200 @@ -0,0 +1,490 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * <2005> Wim Taymans + * <2005> Tim-Philipp Müller + * + * 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 +#include + +#include "gstcdparanoiasrc.h" +#include "gst/gst-i18n-plugin.h" + +enum +{ + TRANSPORT_ERROR, + UNCORRECTED_ERROR, + NUM_SIGNALS +}; + +enum +{ + PROP_0, + PROP_READ_SPEED, + PROP_PARANOIA_MODE, + PROP_SEARCH_OVERLAP, + PROP_GENERIC_DEVICE +}; + +#define DEFAULT_READ_SPEED -1 +#define DEFAULT_SEARCH_OVERLAP -1 +#define DEFAULT_PARANOIA_MODE PARANOIA_MODE_FRAGMENT +#define DEFAULT_GENERIC_DEVICE NULL + +GST_DEBUG_CATEGORY_STATIC (gst_cd_paranoia_src_debug); +#define GST_CAT_DEFAULT gst_cd_paranoia_src_debug + +GST_BOILERPLATE (GstCdParanoiaSrc, gst_cd_paranoia_src, GstCddaBaseSrc, + GST_TYPE_CDDA_BASE_SRC); + +static void gst_cd_paranoia_src_finalize (GObject * obj); +static void gst_cd_paranoia_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_cd_paranoia_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static GstBuffer *gst_cd_paranoia_src_read_sector (GstCddaBaseSrc * src, + gint sector); +static gboolean gst_cd_paranoia_src_open (GstCddaBaseSrc * src, + const gchar * device); +static void gst_cd_paranoia_src_close (GstCddaBaseSrc * src); + +static const GstElementDetails cdparanoia_details = +GST_ELEMENT_DETAILS ("CD Audio (cdda) Source, Paranoia IV", + "Source/File", + "Read audio from CD in paranoid mode", + "Erik Walthinsen , " "Wim Taymans "); + +/* We use these to serialize calls to paranoia_read() among several + * cdparanoiasrc instances. We do this because it's the only reasonably + * easy way to find out the calling object from within the paranoia + * callback, and we need the object instance in there to emit our signals */ +static GstCdParanoiaSrc *cur_cb_source; +static GStaticMutex cur_cb_mutex = G_STATIC_MUTEX_INIT; + +static gint cdpsrc_signals[NUM_SIGNALS]; /* all 0 */ + +#define GST_TYPE_CD_PARANOIA_MODE (gst_cd_paranoia_mode_get_type()) +static GType +gst_cd_paranoia_mode_get_type (void) +{ + static const GFlagsValue paranoia_modes[] = { + {PARANOIA_MODE_DISABLE, "PARANOIA_MODE_DISABLE", "disable"}, + {PARANOIA_MODE_FRAGMENT, "PARANOIA_MODE_FRAGMENT", "fragment"}, + {PARANOIA_MODE_OVERLAP, "PARANOIA_MODE_OVERLAP", "overlap"}, + {PARANOIA_MODE_SCRATCH, "PARANOIA_MODE_SCRATCH", "scratch"}, + {PARANOIA_MODE_REPAIR, "PARANOIA_MODE_REPAIR", "repair"}, + {PARANOIA_MODE_FULL, "PARANOIA_MODE_FULL", "full"}, + {0, NULL, NULL}, + }; + + static GType type; /* 0 */ + + if (!type) { + type = g_flags_register_static ("GstCdParanoiaMode", paranoia_modes); + } + + return type; +} + +static void +gst_cd_paranoia_src_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &cdparanoia_details); +} + +static void +gst_cd_paranoia_src_init (GstCdParanoiaSrc * src, GstCdParanoiaSrcClass * klass) +{ + src->d = NULL; + src->p = NULL; + src->next_sector = -1; + + src->search_overlap = DEFAULT_SEARCH_OVERLAP; + src->paranoia_mode = DEFAULT_PARANOIA_MODE; + src->read_speed = DEFAULT_READ_SPEED; + src->generic_device = g_strdup (DEFAULT_GENERIC_DEVICE); +} + +static void +gst_cd_paranoia_src_class_init (GstCdParanoiaSrcClass * klass) +{ + GstCddaBaseSrcClass *cddabasesrc_class = GST_CDDA_BASE_SRC_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gst_cd_paranoia_src_set_property; + gobject_class->get_property = gst_cd_paranoia_src_get_property; + gobject_class->finalize = gst_cd_paranoia_src_finalize; + + cddabasesrc_class->open = gst_cd_paranoia_src_open; + cddabasesrc_class->close = gst_cd_paranoia_src_close; + cddabasesrc_class->read_sector = gst_cd_paranoia_src_read_sector; + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GENERIC_DEVICE, + g_param_spec_string ("generic-device", "Generic device", + "Use specified generic scsi device", DEFAULT_GENERIC_DEVICE, + G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_READ_SPEED, + g_param_spec_int ("read-speed", "Read speed", + "Read from device at specified speed", -1, G_MAXINT, + DEFAULT_READ_SPEED, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PARANOIA_MODE, + g_param_spec_flags ("paranoia-mode", "Paranoia mode", + "Type of checking to perform", GST_TYPE_CD_PARANOIA_MODE, + DEFAULT_PARANOIA_MODE, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEARCH_OVERLAP, + g_param_spec_int ("search-overlap", "Search overlap", + "Force minimum overlap search during verification to n sectors", -1, + 75, DEFAULT_SEARCH_OVERLAP, G_PARAM_READWRITE)); + + /* FIXME: we don't really want signals for this, but messages on the bus, + * but then we can't check any longer whether anyone is interested in them */ + cdpsrc_signals[TRANSPORT_ERROR] = + g_signal_new ("transport-error", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstCdParanoiaSrcClass, transport_error), + NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); + cdpsrc_signals[UNCORRECTED_ERROR] = + g_signal_new ("uncorrected-error", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstCdParanoiaSrcClass, uncorrected_error), + NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); +} + +static gboolean +gst_cd_paranoia_src_open (GstCddaBaseSrc * cddabasesrc, const gchar * device) +{ + GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (cddabasesrc); + gint i; + + GST_DEBUG_OBJECT (src, "trying to open device %s (generic-device=%s) ...", + device, GST_STR_NULL (src->generic_device)); + + /* find the device */ + if (src->generic_device != NULL) { + src->d = cdda_identify_scsi (src->generic_device, device, FALSE, NULL); + } else { + if (device != NULL) { + src->d = cdda_identify (device, FALSE, NULL); + } else { + src->d = cdda_identify ("/dev/cdrom", FALSE, NULL); + } + } + + /* fail if the device couldn't be found */ + if (src->d == NULL) + goto no_device; + + /* set verbosity mode */ + cdda_verbose_set (src->d, CDDA_MESSAGE_FORGETIT, CDDA_MESSAGE_FORGETIT); + + /* open the disc */ + if (cdda_open (src->d)) + goto open_failed; + + if (src->read_speed != -1) { + cdda_speed_set (src->d, src->read_speed); + } + + for (i = 1; i < src->d->tracks + 1; i++) { + GstCddaBaseSrcTrack track = { 0, }; + + track.num = i; + track.is_audio = IS_AUDIO (src->d, i - 1); + track.start = cdda_track_firstsector (src->d, i); + track.end = cdda_track_lastsector (src->d, i); + track.tags = NULL; + + gst_cdda_base_src_add_track (GST_CDDA_BASE_SRC (src), &track); + } + + /* create the paranoia struct and set it up */ + src->p = paranoia_init (src->d); + if (src->p == NULL) + goto init_failed; + + paranoia_modeset (src->p, src->paranoia_mode); + + if (src->search_overlap != -1) + paranoia_overlapset (src->p, src->search_overlap); + + src->next_sector = -1; + + return TRUE; + + /* ERRORS */ +no_device: + { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + (_("Could not open CD device for reading.")), ("cdda_identify failed")); + return FALSE; + } +open_failed: + { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + (_("Could not open CD device for reading.")), ("cdda_open failed")); + cdda_close (src->d); + src->d = NULL; + return FALSE; + } +init_failed: + { + GST_ELEMENT_ERROR (src, LIBRARY, INIT, + ("failed to initialize paranoia"), ("failed to initialize paranoia")); + return FALSE; + } +} + +static void +gst_cd_paranoia_src_close (GstCddaBaseSrc * cddabasesrc) +{ + GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (cddabasesrc); + + if (src->p) { + paranoia_free (src->p); + src->p = NULL; + } + + if (src->d) { + cdda_close (src->d); + src->d = NULL; + } + + src->next_sector = -1; +} + +static void +gst_cd_paranoia_dummy_callback (long inpos, int function) +{ + /* Used by instanced where no one is interested what's happening here */ +} + +static void +gst_cd_paranoia_paranoia_callback (long inpos, int function) +{ + GstCdParanoiaSrc *src = cur_cb_source; + gint sector = (gint) (inpos / CD_FRAMEWORDS); + + switch (function) { + case PARANOIA_CB_SKIP: + GST_INFO_OBJECT (src, "Skip at sector %d", sector); + g_signal_emit (src, cdpsrc_signals[UNCORRECTED_ERROR], 0, sector); + break; + case PARANOIA_CB_READERR: + GST_INFO_OBJECT (src, "Transport error at sector %d", sector); + g_signal_emit (src, cdpsrc_signals[TRANSPORT_ERROR], 0, sector); + break; + default: + break; + } +} + +static gboolean +gst_cd_paranoia_src_signal_is_being_watched (GstCdParanoiaSrc * src, gint sig) +{ + return g_signal_has_handler_pending (src, cdpsrc_signals[sig], 0, FALSE); +} + +static GstBuffer * +gst_cd_paranoia_src_read_sector (GstCddaBaseSrc * cddabasesrc, gint sector) +{ + GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (cddabasesrc); + GstBuffer *buf; + gboolean do_serialize; + gint16 *cdda_buf; + +#if 0 + /* Do we really need to output this? (tpm) */ + /* Due to possible autocorrections of start sectors of audio tracks on + * multisession cds, we can maybe not compute the correct discid. + * So issue a warning. + * See cdparanoia/interface/common-interface.c:FixupTOC */ + if (src->d && src->d->cd_extra) { + g_message + ("DiscID on multisession discs might be broken. Use at own risk."); + } +#endif + + if (src->next_sector == -1 || src->next_sector != sector) { + if (paranoia_seek (src->p, sector, SEEK_SET) == -1) + goto seek_failed; + + GST_DEBUG_OBJECT (src, "successfully seeked to sector %d", sector); + src->next_sector = sector; + } + + do_serialize = + gst_cd_paranoia_src_signal_is_being_watched (src, TRANSPORT_ERROR) || + gst_cd_paranoia_src_signal_is_being_watched (src, UNCORRECTED_ERROR); + + if (do_serialize) { + GST_LOG_OBJECT (src, "Signal handlers connected, serialising access"); + g_static_mutex_lock (&cur_cb_mutex); + GST_LOG_OBJECT (src, "Got lock"); + cur_cb_source = src; + + cdda_buf = paranoia_read (src->p, gst_cd_paranoia_paranoia_callback); + + cur_cb_source = NULL; + GST_LOG_OBJECT (src, "Releasing lock"); + g_static_mutex_unlock (&cur_cb_mutex); + } else { + cdda_buf = paranoia_read (src->p, gst_cd_paranoia_dummy_callback); + } + + if (cdda_buf == NULL) + goto read_failed; + + buf = gst_buffer_new_and_alloc (CD_FRAMESIZE_RAW); + memcpy (GST_BUFFER_DATA (buf), cdda_buf, CD_FRAMESIZE_RAW); + + /* cdda base class will take care of timestamping etc. */ + ++src->next_sector; + + return buf; + + /* ERRORS */ +seek_failed: + { + GST_WARNING_OBJECT (src, "seek to sector %d failed!", sector); + GST_ELEMENT_ERROR (src, RESOURCE, SEEK, + (_("Could not seek CD.")), + ("paranoia_seek to %d failed: %s", sector, g_strerror (errno))); + return NULL; + } +read_failed: + { + GST_WARNING_OBJECT (src, "read at sector %d failed!", sector); + GST_ELEMENT_ERROR (src, RESOURCE, READ, + (_("Could not read CD.")), + ("paranoia_read at %d failed: %s", sector, g_strerror (errno))); + return NULL; + } +} + +static void +gst_cd_paranoia_src_finalize (GObject * obj) +{ + GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (obj); + + g_free (src->generic_device); + + G_OBJECT_CLASS (parent_class)->finalize (obj); +} + +static void +gst_cd_paranoia_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (object); + + GST_OBJECT_LOCK (src); + + switch (prop_id) { + case PROP_GENERIC_DEVICE:{ + g_free (src->generic_device); + src->generic_device = g_value_dup_string (value); + if (src->generic_device && src->generic_device[0] == '\0') { + g_free (src->generic_device); + src->generic_device = NULL; + } + break; + } + case PROP_READ_SPEED:{ + src->read_speed = g_value_get_int (value); + if (src->read_speed == 0) + src->read_speed = -1; + break; + } + case PROP_PARANOIA_MODE:{ + src->paranoia_mode = g_value_get_flags (value) & PARANOIA_MODE_FULL; + break; + } + case PROP_SEARCH_OVERLAP:{ + src->search_overlap = g_value_get_int (value); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_OBJECT_UNLOCK (src); +} + +static void +gst_cd_paranoia_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (object); + + GST_OBJECT_LOCK (src); + + switch (prop_id) { + case PROP_READ_SPEED: + g_value_set_int (value, src->read_speed); + break; + case PROP_PARANOIA_MODE: + g_value_set_flags (value, src->paranoia_mode); + break; + case PROP_GENERIC_DEVICE: + g_value_set_string (value, src->generic_device); + break; + case PROP_SEARCH_OVERLAP: + g_value_set_int (value, src->search_overlap); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + GST_OBJECT_UNLOCK (src); +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_cd_paranoia_src_debug, "cdparanoiasrc", 0, + "CD Paranoia Source"); + + if (!gst_element_register (plugin, "cdparanoiasrc", GST_RANK_SECONDARY, + GST_TYPE_CD_PARANOIA_SRC)) + return FALSE; + +#ifdef ENABLE_NLS + GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, + LOCALEDIR); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); +#endif + + + return TRUE; +} + + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "cdparanoia", + "Read audio from CD in paranoid mode", + plugin_init, VERSION, "GPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)