mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 19:20:35 +00:00
1ab41f83b7
If drive produces samples in other endianness than the host, we need to byte swap them before pushing them out, or we produce nothing but noise. cdparanoia detects this automatically, but libcdio does not, so we have to do it ourselves. This is needed on e.g. the PowerBook G4 with Matshita UJ-816 drive. https://bugzilla.gnome.org/show_bug.cgi?id=377280
520 lines
15 KiB
C
520 lines
15 KiB
C
/* 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 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
|
|
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
|
|
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
|
|
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;
|
|
}
|
|
}
|