| /* GStreamer |
| * Copyright (C) 2006 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. |
| */ |
| |
| /** |
| * SECTION:element-cdiocddasrc |
| * @see_also: GstCdParanoiaSrc, GstAudioCdSrc |
| * |
| * <refsect2> |
| * <para> |
| * cdiocddasrc reads and extracts raw audio from Audio CDs. It can operate |
| * in one of two modes: |
| * <itemizedlist> |
| * <listitem><para> |
| * treat each track as a separate stream, counting time from the start |
| * of the track to the end of the track and posting EOS at the end of |
| * a track, or |
| * </para></listitem> |
| * <listitem><para> |
| * treat the entire disc as one stream, counting time from the start of |
| * the first track to the end of the last track, posting EOS only at |
| * the end of the last track. |
| * </para></listitem> |
| * </itemizedlist> |
| * </para> |
| * <para> |
| * With a recent-enough version of libcdio, the element will extract |
| * CD-TEXT if this is supported by the CD-drive and CD-TEXT information |
| * is available on the CD. The information will be posted on the bus in |
| * form of a tag message. |
| * </para> |
| * <para> |
| * When opened, the element will also calculate a CDDB disc ID and a |
| * MusicBrainz disc ID, which applications can use to query online |
| * databases for artist/title information. These disc IDs will also be |
| * posted on the bus as part of the tag messages. |
| * </para> |
| * <para> |
| * cdiocddasrc supports the GstUriHandler interface, so applications can use |
| * playbin with cdda://<track-number> URIs for playback (they will have |
| * to connect to playbin's notify::source signal and set the device on the |
| * cd source in the notify callback if they want to set the device property). |
| * Applications should use seeks in "track" format to switch between different |
| * tracks of the same CD (passing a new cdda:// URI to playbin involves opening |
| * and closing the CD device, which is much slower). |
| * </para> |
| * <title>Example launch line</title> |
| * <para> |
| * <programlisting> |
| * gst-launch-1.0 cdiocddasrc track=5 device=/dev/cdrom ! audioconvert ! vorbisenc ! oggmux ! filesink location=track5.ogg |
| * </programlisting> |
| * This pipeline extracts track 5 of the audio CD and encodes it into an |
| * Ogg/Vorbis file. |
| * </para> |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstcdio.h" |
| #include "gstcdiocddasrc.h" |
| |
| #include <gst/gst.h> |
| #include "gst/gst-i18n-plugin.h" |
| |
| #include <sys/types.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| |
| #define SAMPLES_PER_SECTOR (CDIO_CD_FRAMESIZE_RAW / sizeof (gint16)) |
| |
| #define DEFAULT_READ_SPEED -1 |
| |
| enum |
| { |
| PROP_0 = 0, |
| PROP_READ_SPEED |
| }; |
| |
| G_DEFINE_TYPE (GstCdioCddaSrc, gst_cdio_cdda_src, GST_TYPE_AUDIO_CD_SRC); |
| |
| static void gst_cdio_cdda_src_finalize (GObject * obj); |
| static void gst_cdio_cdda_src_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_cdio_cdda_src_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| static GstBuffer *gst_cdio_cdda_src_read_sector (GstAudioCdSrc * src, |
| gint sector); |
| static gboolean gst_cdio_cdda_src_open (GstAudioCdSrc * src, |
| const gchar * device); |
| static void gst_cdio_cdda_src_close (GstAudioCdSrc * src); |
| |
| #if 0 |
| static gchar * |
| gst_cdio_cdda_src_get_default_device (GstAudioCdSrc * audiocdsrc) |
| { |
| GstCdioCddaSrc *src; |
| gchar *default_device, *ret; |
| |
| src = GST_CDIO_CDDA_SRC (audiocdsrc); |
| |
| /* src->cdio may be NULL here */ |
| default_device = cdio_get_default_device (src->cdio); |
| |
| ret = g_strdup (default_device); |
| free (default_device); |
| |
| GST_LOG_OBJECT (src, "returning default device: %s", GST_STR_NULL (ret)); |
| |
| return ret; |
| } |
| |
| static gchar ** |
| gst_cdio_cdda_src_probe_devices (GstAudioCdSrc * audiocdsrc) |
| { |
| char **devices, **ret, **d; |
| |
| /* FIXME: might return the same hardware device twice, e.g. |
| * as /dev/cdrom and /dev/dvd - gotta do something more sophisticated */ |
| devices = cdio_get_devices (DRIVER_DEVICE); |
| |
| if (devices == NULL) |
| goto no_devices; |
| |
| if (*devices == NULL) |
| goto empty_devices; |
| |
| ret = g_strdupv (devices); |
| for (d = devices; *d != NULL; ++d) { |
| GST_DEBUG_OBJECT (audiocdsrc, "device: %s", GST_STR_NULL (*d)); |
| free (*d); |
| } |
| free (devices); |
| |
| return ret; |
| |
| /* ERRORS */ |
| no_devices: |
| { |
| GST_DEBUG_OBJECT (audiocdsrc, "no devices found"); |
| return NULL; |
| } |
| empty_devices: |
| { |
| GST_DEBUG_OBJECT (audiocdsrc, "empty device list found"); |
| free (devices); |
| return NULL; |
| } |
| } |
| #endif |
| |
| static GstBuffer * |
| gst_cdio_cdda_src_read_sector (GstAudioCdSrc * audiocdsrc, gint sector) |
| { |
| GstCdioCddaSrc *src; |
| guint8 *data; |
| |
| src = GST_CDIO_CDDA_SRC (audiocdsrc); |
| |
| data = g_malloc (CDIO_CD_FRAMESIZE_RAW); |
| |
| /* can't use pad_alloc because we can't return the GstFlowReturn (FIXME 0.11) */ |
| if (cdio_read_audio_sector (src->cdio, data, sector) != 0) |
| goto read_failed; |
| |
| if (src->swap_le_be) { |
| gint16 *pcm_data = (gint16 *) data; |
| gint i; |
| |
| for (i = 0; i < SAMPLES_PER_SECTOR; ++i) |
| pcm_data[i] = GUINT16_SWAP_LE_BE (pcm_data[i]); |
| } |
| |
| return gst_buffer_new_wrapped (data, CDIO_CD_FRAMESIZE_RAW); |
| |
| /* ERRORS */ |
| read_failed: |
| { |
| GST_WARNING_OBJECT (src, "read at sector %d failed!", sector); |
| GST_ELEMENT_ERROR (src, RESOURCE, READ, |
| (_("Could not read from CD.")), |
| ("cdio_read_audio_sector at %d failed: %s", sector, |
| g_strerror (errno))); |
| g_free (data); |
| return NULL; |
| } |
| } |
| |
| static gboolean |
| gst_cdio_cdda_src_do_detect_drive_endianness (GstCdioCddaSrc * src, gint from, |
| gint to) |
| { |
| gint16 pcm_data[SAMPLES_PER_SECTOR], last_pcm_ne, last_pcm_oe; |
| gdouble ne_sumd0, ne_sumd1, ne_factor; |
| gdouble oe_sumd0, oe_sumd1, oe_factor; |
| gdouble diff; |
| gint sector; |
| gint i; |
| |
| ne_sumd0 = ne_sumd1 = 0.0; |
| oe_sumd0 = oe_sumd1 = 0.0; |
| last_pcm_ne = 0; |
| last_pcm_oe = 0; |
| |
| GST_LOG_OBJECT (src, "checking sector %d to %d", from, to); |
| |
| for (sector = from; sector < to; ++sector) { |
| if (cdio_read_audio_sector (src->cdio, pcm_data, sector) != 0) |
| goto read_failed; |
| |
| /* only evaluate samples for left channel */ |
| for (i = 0; i < SAMPLES_PER_SECTOR; i += 2) { |
| gint16 pcm; |
| |
| /* Native endianness first */ |
| pcm = pcm_data[i]; |
| ne_sumd0 += abs (pcm); |
| ne_sumd1 += abs (pcm - last_pcm_ne); |
| last_pcm_ne = pcm; |
| |
| /* other endianness next */ |
| pcm = GUINT16_SWAP_LE_BE (pcm); |
| oe_sumd0 += abs (pcm); |
| oe_sumd1 += abs (pcm - last_pcm_oe); |
| last_pcm_oe = pcm; |
| } |
| |
| } |
| |
| ne_factor = (ne_sumd1 / ne_sumd0); |
| oe_factor = (oe_sumd1 / oe_sumd0); |
| diff = ne_factor - oe_factor; |
| |
| GST_DEBUG_OBJECT (src, "Native: %.2f, Other: %.2f, diff: %.2f", |
| ne_factor, oe_factor, diff); |
| |
| if (diff > 0.5) { |
| GST_INFO_OBJECT (src, "Drive produces samples in other endianness"); |
| src->swap_le_be = TRUE; |
| return TRUE; |
| } else if (diff < -0.5) { |
| GST_INFO_OBJECT (src, "Drive produces samples in host endianness"); |
| src->swap_le_be = FALSE; |
| return TRUE; |
| } else { |
| GST_INFO_OBJECT (src, "Inconclusive, assuming host endianness"); |
| src->swap_le_be = FALSE; |
| return FALSE; |
| } |
| |
| /* ERRORS */ |
| read_failed: |
| { |
| GST_WARNING_OBJECT (src, "could not read sector %d", sector); |
| src->swap_le_be = FALSE; |
| return FALSE; |
| } |
| } |
| |
| static void |
| gst_cdio_cdda_src_detect_drive_endianness (GstCdioCddaSrc * src, gint first, |
| gint last) |
| { |
| gint from, to; |
| |
| GST_INFO ("Detecting drive endianness"); |
| |
| /* try middle of disc first */ |
| from = (first + last) / 2; |
| to = MIN (from + 10, last); |
| if (gst_cdio_cdda_src_do_detect_drive_endianness (src, from, to)) |
| return; |
| |
| /* if that was inconclusive, try other places */ |
| from = (first + last) / 4; |
| to = MIN (from + 10, last); |
| if (gst_cdio_cdda_src_do_detect_drive_endianness (src, from, to)) |
| return; |
| |
| from = (first + last) * 3 / 4; |
| to = MIN (from + 10, last); |
| if (gst_cdio_cdda_src_do_detect_drive_endianness (src, from, to)) |
| return; |
| |
| /* if that's still inconclusive, we give up and assume host endianness */ |
| return; |
| } |
| |
| static gboolean |
| notcdio_track_is_audio_track (const CdIo * p_cdio, track_t i_track) |
| { |
| return (cdio_get_track_format (p_cdio, i_track) == TRACK_FORMAT_AUDIO); |
| } |
| |
| static gboolean |
| gst_cdio_cdda_src_open (GstAudioCdSrc * audiocdsrc, const gchar * device) |
| { |
| GstCdioCddaSrc *src; |
| discmode_t discmode; |
| gint first_track, num_tracks, i; |
| gint first_audio_sector = 0, last_audio_sector = 0; |
| #if LIBCDIO_VERSION_NUM > 83 || LIBCDIO_VERSION_NUM < 76 |
| cdtext_t *cdtext; |
| #endif |
| |
| src = GST_CDIO_CDDA_SRC (audiocdsrc); |
| |
| g_assert (device != NULL); |
| g_assert (src->cdio == NULL); |
| |
| GST_LOG_OBJECT (src, "trying to open device %s", device); |
| |
| if (!(src->cdio = cdio_open (device, DRIVER_UNKNOWN))) |
| goto open_failed; |
| |
| discmode = cdio_get_discmode (src->cdio); |
| GST_LOG_OBJECT (src, "discmode: %d", (gint) discmode); |
| |
| if (discmode != CDIO_DISC_MODE_CD_DA && discmode != CDIO_DISC_MODE_CD_MIXED) |
| goto not_audio; |
| |
| first_track = cdio_get_first_track_num (src->cdio); |
| num_tracks = cdio_get_num_tracks (src->cdio); |
| |
| if (num_tracks <= 0 || first_track < 0) |
| return TRUE; /* base class will generate 'has no tracks' error */ |
| |
| if (src->read_speed != -1) |
| cdio_set_speed (src->cdio, src->read_speed); |
| |
| #if LIBCDIO_VERSION_NUM > 83 || LIBCDIO_VERSION_NUM < 76 |
| cdtext = cdio_get_cdtext (src->cdio); |
| |
| if (NULL == cdtext) |
| GST_DEBUG_OBJECT (src, "no CD-TEXT on disc"); |
| else |
| gst_cdio_add_cdtext_album_tags (GST_OBJECT_CAST (src), cdtext, |
| audiocdsrc->tags); |
| #else |
| gst_cdio_add_cdtext_album_tags (GST_OBJECT_CAST (src), src->cdio, |
| audiocdsrc->tags); |
| #endif |
| |
| GST_LOG_OBJECT (src, "%u tracks, first track: %d", num_tracks, first_track); |
| |
| for (i = 0; i < num_tracks; ++i) { |
| GstAudioCdSrcTrack track = { 0, }; |
| gint len_sectors; |
| |
| len_sectors = cdio_get_track_sec_count (src->cdio, i + first_track); |
| |
| track.num = i + first_track; |
| track.is_audio = notcdio_track_is_audio_track (src->cdio, i + first_track); |
| |
| /* Note: LSN/LBA confusion all around us; in any case, this does |
| * the right thing here (for cddb id calculations etc. as well) */ |
| track.start = cdio_get_track_lsn (src->cdio, i + first_track); |
| track.end = track.start + len_sectors - 1; /* -1? */ |
| |
| if (track.is_audio) { |
| first_audio_sector = MIN (first_audio_sector, track.start); |
| last_audio_sector = MAX (last_audio_sector, track.end); |
| } |
| #if LIBCDIO_VERSION_NUM > 83 || LIBCDIO_VERSION_NUM < 76 |
| if (NULL != cdtext) |
| track.tags = gst_cdio_get_cdtext (GST_OBJECT (src), cdtext, |
| i + first_track); |
| #else |
| track.tags = gst_cdio_get_cdtext (GST_OBJECT (src), src->cdio, |
| i + first_track); |
| #endif |
| |
| gst_audio_cd_src_add_track (GST_AUDIO_CD_SRC (src), &track); |
| } |
| |
| /* Try to detect if we need to byte-order swap the samples coming from the |
| * drive, which might be the case if the CD drive operates in a different |
| * endianness than the host CPU's endianness (happens on e.g. Powerbook G4) */ |
| gst_cdio_cdda_src_detect_drive_endianness (src, first_audio_sector, |
| last_audio_sector); |
| |
| return TRUE; |
| |
| /* ERRORS */ |
| open_failed: |
| { |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, |
| (_("Could not open CD device for reading.")), |
| ("cdio_open() failed: %s", g_strerror (errno))); |
| return FALSE; |
| } |
| not_audio: |
| { |
| GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, |
| (_("Disc is not an Audio CD.")), ("discmode: %d", (gint) discmode)); |
| |
| cdio_destroy (src->cdio); |
| src->cdio = NULL; |
| return FALSE; |
| } |
| } |
| |
| static void |
| gst_cdio_cdda_src_close (GstAudioCdSrc * audiocdsrc) |
| { |
| GstCdioCddaSrc *src = GST_CDIO_CDDA_SRC (audiocdsrc); |
| |
| if (src->cdio) { |
| cdio_destroy (src->cdio); |
| src->cdio = NULL; |
| } |
| } |
| |
| static void |
| gst_cdio_cdda_src_init (GstCdioCddaSrc * src) |
| { |
| src->read_speed = DEFAULT_READ_SPEED; /* don't need atomic access here */ |
| src->cdio = NULL; |
| } |
| |
| static void |
| gst_cdio_cdda_src_finalize (GObject * obj) |
| { |
| GstCdioCddaSrc *src = GST_CDIO_CDDA_SRC (obj); |
| |
| if (src->cdio) { |
| cdio_destroy (src->cdio); |
| src->cdio = NULL; |
| } |
| |
| G_OBJECT_CLASS (gst_cdio_cdda_src_parent_class)->finalize (obj); |
| } |
| |
| static void |
| gst_cdio_cdda_src_class_init (GstCdioCddaSrcClass * 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_cdio_cdda_src_set_property; |
| gobject_class->get_property = gst_cdio_cdda_src_get_property; |
| gobject_class->finalize = gst_cdio_cdda_src_finalize; |
| |
| audiocdsrc_class->open = gst_cdio_cdda_src_open; |
| audiocdsrc_class->close = gst_cdio_cdda_src_close; |
| audiocdsrc_class->read_sector = gst_cdio_cdda_src_read_sector; |
| #if 0 |
| audiocdsrc_class->probe_devices = gst_cdio_cdda_src_probe_devices; |
| audiocdsrc_class->get_default_device = gst_cdio_cdda_src_get_default_device; |
| #endif |
| |
| g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_READ_SPEED, |
| g_param_spec_int ("read-speed", "Read speed", |
| "Read from device at the specified speed (-1 = default)", -1, 100, |
| DEFAULT_READ_SPEED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| gst_element_class_set_static_metadata (element_class, |
| "CD audio source (CDDA)", "Source/File", |
| "Read audio from CD using libcdio", |
| "Tim-Philipp Müller <tim centricular net>"); |
| } |
| |
| static void |
| gst_cdio_cdda_src_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstCdioCddaSrc *src = GST_CDIO_CDDA_SRC (object); |
| |
| switch (prop_id) { |
| case PROP_READ_SPEED:{ |
| gint speed; |
| |
| speed = g_value_get_int (value); |
| g_atomic_int_set (&src->read_speed, speed); |
| break; |
| } |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_cdio_cdda_src_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstCdioCddaSrc *src = GST_CDIO_CDDA_SRC (object); |
| |
| switch (prop_id) { |
| case PROP_READ_SPEED:{ |
| gint speed; |
| |
| speed = g_atomic_int_get (&src->read_speed); |
| g_value_set_int (value, speed); |
| break; |
| } |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |