| /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ |
| /* GStreamer |
| * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> |
| * <2005> Wim Taymans <wim@fluendo.com> |
| * <2005> Tim-Philipp Müller <tim centricular net> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include <errno.h> |
| #include <stdio.h> |
| |
| #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, |
| PROP_CACHE_SIZE |
| }; |
| |
| #define DEFAULT_READ_SPEED -1 |
| #define DEFAULT_SEARCH_OVERLAP -1 |
| #define DEFAULT_PARANOIA_MODE PARANOIA_MODE_FRAGMENT |
| #define DEFAULT_GENERIC_DEVICE NULL |
| #define DEFAULT_CACHE_SIZE -1 |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_cd_paranoia_src_debug); |
| #define GST_CAT_DEFAULT gst_cd_paranoia_src_debug |
| |
| #define gst_cd_paranoia_src_parent_class parent_class |
| G_DEFINE_TYPE (GstCdParanoiaSrc, gst_cd_paranoia_src, GST_TYPE_AUDIO_CD_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 (GstAudioCdSrc * src, |
| gint sector); |
| static gboolean gst_cd_paranoia_src_open (GstAudioCdSrc * src, |
| const gchar * device); |
| static void gst_cd_paranoia_src_close (GstAudioCdSrc * src); |
| |
| /* 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 GMutex cur_cb_mutex; |
| |
| 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_init (GstCdParanoiaSrc * src) |
| { |
| 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); |
| src->cache_size = DEFAULT_CACHE_SIZE; |
| } |
| |
| static void |
| gst_cd_paranoia_src_class_init (GstCdParanoiaSrcClass * klass) |
| { |
| GstAudioCdSrcClass *audiocdsrc_class = GST_AUDIO_CD_SRC_CLASS (klass); |
| GstElementClass *element_class = GST_ELEMENT_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; |
| |
| gst_element_class_set_static_metadata (element_class, |
| "CD Audio (cdda) Source, Paranoia IV", "Source/File", |
| "Read audio from CD in paranoid mode", |
| "Erik Walthinsen <omega@cse.ogi.edu>, Wim Taymans <wim@fluendo.com>"); |
| |
| audiocdsrc_class->open = gst_cd_paranoia_src_open; |
| audiocdsrc_class->close = gst_cd_paranoia_src_close; |
| audiocdsrc_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_PARAM_STATIC_STRINGS)); |
| 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 and 0 = full speed)", |
| -1, G_MAXINT, DEFAULT_READ_SPEED, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| 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_PARAM_STATIC_STRINGS)); |
| 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 | G_PARAM_STATIC_STRINGS)); |
| /** |
| * GstCdParanoiaSrc:cache-size: |
| * |
| * Set CD cache size to n sectors (-1 = auto) |
| */ |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CACHE_SIZE, |
| g_param_spec_int ("cache-size", "Cache size", |
| "Set CD cache size to n sectors (-1 = auto)", -1, |
| G_MAXINT, DEFAULT_CACHE_SIZE, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /* 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 */ |
| /** |
| * GstCdParanoiaSrc::transport-error: |
| * @cdparanoia: The CdParanoia instance |
| * @sector: The sector number at which the error was encountered. |
| * |
| * This signal is emitted whenever an error occurs while reading. |
| * CdParanoia will attempt to recover the data. |
| */ |
| 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); |
| /** |
| * GstCdParanoiaSrc::uncorrected-error: |
| * @cdparanoia: The CdParanoia instance |
| * @sector: The sector number at which the error was encountered. |
| * |
| * This signal is emitted whenever an uncorrectable error occurs while |
| * reading. The data could not be read. |
| */ |
| 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 (GstAudioCdSrc * audiocdsrc, const gchar * device) |
| { |
| GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (audiocdsrc); |
| gint i, cache_size; |
| |
| 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; |
| |
| GST_INFO_OBJECT (src, "set read speed to %d", src->read_speed); |
| cdda_speed_set (src->d, src->read_speed); |
| |
| for (i = 1; i < src->d->tracks + 1; i++) { |
| GstAudioCdSrcTrack 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_audio_cd_src_add_track (GST_AUDIO_CD_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); |
| GST_INFO_OBJECT (src, "set paranoia mode to 0x%02x", src->paranoia_mode); |
| |
| if (src->search_overlap != -1) { |
| paranoia_overlapset (src->p, src->search_overlap); |
| GST_INFO_OBJECT (src, "search overlap set to %u", src->search_overlap); |
| } |
| |
| cache_size = src->cache_size; |
| if (cache_size == -1) { |
| /* if paranoia mode is low (the default), assume we're doing playback */ |
| if (src->paranoia_mode <= PARANOIA_MODE_FRAGMENT) |
| cache_size = 150; |
| else |
| cache_size = paranoia_cachemodel_size (src->p, -1); |
| } |
| paranoia_cachemodel_size (src->p, cache_size); |
| GST_INFO_OBJECT (src, "set cachemodel size to %u", cache_size); |
| |
| 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 (GstAudioCdSrc * audiocdsrc) |
| { |
| GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (audiocdsrc); |
| |
| 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 (GstAudioCdSrc * audiocdsrc, gint sector) |
| { |
| GstCdParanoiaSrc *src = GST_CD_PARANOIA_SRC (audiocdsrc); |
| 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_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_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); |
| gst_buffer_fill (buf, 0, 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; |
| } |
| case PROP_CACHE_SIZE:{ |
| src->cache_size = 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; |
| case PROP_CACHE_SIZE: |
| g_value_set_int (value, src->cache_size); |
| 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); |
| bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
| #endif |
| |
| return TRUE; |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| cdparanoia, |
| "Read audio from CD in paranoid mode", |
| plugin_init, GST_PLUGINS_BASE_VERSION, "LGPL", GST_PACKAGE_NAME, |
| GST_PACKAGE_ORIGIN) |