mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 11:10:37 +00:00
8efe6108c4
Don't use REPLACE_ALL merge mode when that's not really what we want, as now that REPLACE_ALL actually does what it's supposed to do in core, we drop tags we wanted to keep, such as the various disc id tags. Add unit test for this as well. Fixes #579463.
1594 lines
46 KiB
C
1594 lines
46 KiB
C
/* GStreamer
|
|
* Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
|
|
* Copyright (C) 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/* TODO:
|
|
*
|
|
* - in ::start(), we want to post a tags message with an array or a list
|
|
* of tagslists of all tracks, so that applications know at least the
|
|
* number of tracks and all track durations immediately without having
|
|
* to do any querying. We have to decide what type and name to use for
|
|
* this array of track taglists.
|
|
*
|
|
* - FIX cddb discid calculation algorithm for mixed mode CDs - do we use
|
|
* offsets and duration of ALL tracks (data + audio) for the CDDB ID
|
|
* calculation, or only audio tracks?
|
|
*
|
|
* - Do we really need properties for the TOC bias/offset stuff? Wouldn't
|
|
* environment variables make much more sense? Do we need this at all
|
|
* (does it only affect ancient hardware?)
|
|
*/
|
|
|
|
/**
|
|
* SECTION:gstcddabasesrc
|
|
* @short_description: Base class for CD digital audio (CDDA) sources
|
|
*
|
|
* <refsect2>
|
|
* <para>
|
|
* Provides a base class for CDDA sources, which handles things like seeking,
|
|
* querying, discid calculation, tags, and buffer timestamping.
|
|
* </para>
|
|
* <title>Using GstCddaBaseSrc-based elements in applications</title>
|
|
* <para>
|
|
* GstCddaBaseSrc registers two #GstFormat<!-- -->s of its own, namely
|
|
* the "track" format and the "sector" format. Applications will usually
|
|
* only find the "track" format interesting. You can retrieve that #GstFormat
|
|
* for use in seek events or queries with gst_format_get_by_nick("track").
|
|
* </para>
|
|
* <para>
|
|
* In order to query the number of tracks, for example, an application would
|
|
* set the CDDA source element to READY or PAUSED state and then query the
|
|
* the number of tracks via gst_element_query_duration() using the track
|
|
* format acquired above. Applications can query the currently playing track
|
|
* in the same way.
|
|
* </para>
|
|
* <para>
|
|
* Alternatively, applications may retrieve the currently playing track and
|
|
* the total number of tracks from the taglist that will posted on the bus
|
|
* whenever the CD is opened or the currently playing track changes. The
|
|
* taglist will contain GST_TAG_TRACK_NUMBER and GST_TAG_TRACK_COUNT tags.
|
|
* </para>
|
|
* <para>
|
|
* Applications playing back CD audio using playbin and cdda://n URIs should
|
|
* issue a seek command in track format to change between tracks, rather than
|
|
* setting a new cdda://n+1 URI on playbin (as setting a new URI on playbin
|
|
* involves closing and re-opening the CD device, which is much much slower).
|
|
* </para>
|
|
* <title>Tags and meta-information</title>
|
|
* <para>
|
|
* CDDA sources will automatically emit a number of tags, details about which
|
|
* can be found in the libgsttag documentation. Those tags are:
|
|
* #GST_TAG_CDDA_CDDB_DISCID, #GST_TAG_CDDA_CDDB_DISCID_FULL,
|
|
* #GST_TAG_CDDA_MUSICBRAINZ_DISCID, #GST_TAG_CDDA_MUSICBRAINZ_DISCID_FULL,
|
|
* among others.
|
|
* </para>
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h> /* for strtol */
|
|
|
|
#include "gstcddabasesrc.h"
|
|
#include "gst/gst-i18n-plugin.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_cdda_base_src_debug);
|
|
#define GST_CAT_DEFAULT gst_cdda_base_src_debug
|
|
|
|
#define DEFAULT_DEVICE "/dev/cdrom"
|
|
|
|
#define CD_FRAMESIZE_RAW (2352)
|
|
|
|
#define SECTORS_PER_SECOND (75)
|
|
#define SECTORS_PER_MINUTE (75*60)
|
|
#define SAMPLES_PER_SECTOR (CD_FRAMESIZE_RAW >> 2)
|
|
#define TIME_INTERVAL_FROM_SECTORS(sectors) ((SAMPLES_PER_SECTOR * sectors * GST_SECOND) / 44100)
|
|
#define SECTORS_FROM_TIME_INTERVAL(dtime) (dtime * 44100 / (SAMPLES_PER_SECTOR * GST_SECOND))
|
|
|
|
enum
|
|
{
|
|
ARG_0,
|
|
ARG_MODE,
|
|
ARG_DEVICE,
|
|
ARG_TRACK,
|
|
ARG_TOC_OFFSET,
|
|
ARG_TOC_BIAS
|
|
};
|
|
|
|
static void gst_cdda_base_src_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static void gst_cdda_base_src_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_cdda_base_src_finalize (GObject * obj);
|
|
static const GstQueryType *gst_cdda_base_src_get_query_types (GstPad * pad);
|
|
static gboolean gst_cdda_base_src_query (GstBaseSrc * src, GstQuery * query);
|
|
static gboolean gst_cdda_base_src_handle_event (GstBaseSrc * basesrc,
|
|
GstEvent * event);
|
|
static gboolean gst_cdda_base_src_do_seek (GstBaseSrc * basesrc,
|
|
GstSegment * segment);
|
|
static void gst_cdda_base_src_setup_interfaces (GType type);
|
|
static gboolean gst_cdda_base_src_start (GstBaseSrc * basesrc);
|
|
static gboolean gst_cdda_base_src_stop (GstBaseSrc * basesrc);
|
|
static GstFlowReturn gst_cdda_base_src_create (GstPushSrc * pushsrc,
|
|
GstBuffer ** buf);
|
|
static gboolean gst_cdda_base_src_is_seekable (GstBaseSrc * basesrc);
|
|
static void gst_cdda_base_src_update_duration (GstCddaBaseSrc * src);
|
|
static void gst_cdda_base_src_set_index (GstElement * src, GstIndex * index);
|
|
static GstIndex *gst_cdda_base_src_get_index (GstElement * src);
|
|
|
|
GST_BOILERPLATE_FULL (GstCddaBaseSrc, gst_cdda_base_src, GstPushSrc,
|
|
GST_TYPE_PUSH_SRC, gst_cdda_base_src_setup_interfaces);
|
|
|
|
#define SRC_CAPS \
|
|
"audio/x-raw-int, " \
|
|
"endianness = (int) BYTE_ORDER, " \
|
|
"signed = (boolean) true, " \
|
|
"width = (int) 16, " \
|
|
"depth = (int) 16, " \
|
|
"rate = (int) 44100, " \
|
|
"channels = (int) 2" \
|
|
|
|
static GstStaticPadTemplate gst_cdda_base_src_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS (SRC_CAPS)
|
|
);
|
|
|
|
/* our two formats */
|
|
static GstFormat track_format;
|
|
static GstFormat sector_format;
|
|
|
|
GType
|
|
gst_cdda_base_src_mode_get_type (void)
|
|
{
|
|
static GType mode_type; /* 0 */
|
|
static const GEnumValue modes[] = {
|
|
{GST_CDDA_BASE_SRC_MODE_NORMAL, "Stream consists of a single track",
|
|
"normal"},
|
|
{GST_CDDA_BASE_SRC_MODE_CONTINUOUS, "Stream consists of the whole disc",
|
|
"continuous"},
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
if (mode_type == 0)
|
|
mode_type = g_enum_register_static ("GstCddaBaseSrcMode", modes);
|
|
|
|
return mode_type;
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_base_init (gpointer g_class)
|
|
{
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_cdda_base_src_src_template));
|
|
|
|
/* our very own formats */
|
|
track_format = gst_format_register ("track", "CD track");
|
|
sector_format = gst_format_register ("sector", "CD sector");
|
|
|
|
/* register CDDA tags */
|
|
gst_tag_register_musicbrainz_tags ();
|
|
|
|
#if 0
|
|
///// FIXME: what type to use here? ///////
|
|
gst_tag_register (GST_TAG_CDDA_TRACK_TAGS, GST_TAG_FLAG_META, GST_TYPE_TAG_LIST, "track-tags", "CDDA taglist for one track", gst_tag_merge_use_first); ///////////// FIXME: right function??? ///////
|
|
#endif
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_cdda_base_src_debug, "cddabasesrc", 0,
|
|
"CDDA Base Source");
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_class_init (GstCddaBaseSrcClass * klass)
|
|
{
|
|
GstElementClass *element_class;
|
|
GstPushSrcClass *pushsrc_class;
|
|
GstBaseSrcClass *basesrc_class;
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
element_class = (GstElementClass *) klass;
|
|
basesrc_class = (GstBaseSrcClass *) klass;
|
|
pushsrc_class = (GstPushSrcClass *) klass;
|
|
|
|
gobject_class->set_property = gst_cdda_base_src_set_property;
|
|
gobject_class->get_property = gst_cdda_base_src_get_property;
|
|
gobject_class->finalize = gst_cdda_base_src_finalize;
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
|
|
g_param_spec_string ("device", "Device", "CD device location",
|
|
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MODE,
|
|
g_param_spec_enum ("mode", "Mode", "Mode", GST_TYPE_CDDA_BASE_SRC_MODE,
|
|
GST_CDDA_BASE_SRC_MODE_NORMAL,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TRACK,
|
|
g_param_spec_uint ("track", "Track", "Track", 1, 99, 1,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
#if 0
|
|
/* Do we really need this toc adjustment stuff as properties? does the user
|
|
* have a chance to set it in practice, e.g. when using sound-juicer, rb,
|
|
* totem, whatever? Shouldn't we rather use environment variables
|
|
* for this? (tpm) */
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOC_OFFSET,
|
|
g_param_spec_int ("toc-offset", "Table of contents offset",
|
|
"Add <n> sectors to the values reported", G_MININT, G_MAXINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOC_BIAS,
|
|
g_param_spec_boolean ("toc-bias", "Table of contents bias",
|
|
"Assume that the beginning offset of track 1 as reported in the TOC "
|
|
"will be addressed as LBA 0. Necessary for some Toshiba drives to "
|
|
"get track boundaries", FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
#endif
|
|
|
|
element_class->set_index = GST_DEBUG_FUNCPTR (gst_cdda_base_src_set_index);
|
|
element_class->get_index = GST_DEBUG_FUNCPTR (gst_cdda_base_src_get_index);
|
|
|
|
basesrc_class->start = GST_DEBUG_FUNCPTR (gst_cdda_base_src_start);
|
|
basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_cdda_base_src_stop);
|
|
basesrc_class->query = GST_DEBUG_FUNCPTR (gst_cdda_base_src_query);
|
|
basesrc_class->event = GST_DEBUG_FUNCPTR (gst_cdda_base_src_handle_event);
|
|
basesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_cdda_base_src_do_seek);
|
|
basesrc_class->is_seekable =
|
|
GST_DEBUG_FUNCPTR (gst_cdda_base_src_is_seekable);
|
|
|
|
pushsrc_class->create = GST_DEBUG_FUNCPTR (gst_cdda_base_src_create);
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_init (GstCddaBaseSrc * src, GstCddaBaseSrcClass * klass)
|
|
{
|
|
gst_pad_set_query_type_function (GST_BASE_SRC_PAD (src),
|
|
GST_DEBUG_FUNCPTR (gst_cdda_base_src_get_query_types));
|
|
|
|
/* we're not live and we operate in time */
|
|
gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
|
|
gst_base_src_set_live (GST_BASE_SRC (src), FALSE);
|
|
|
|
src->device = NULL;
|
|
src->mode = GST_CDDA_BASE_SRC_MODE_NORMAL;
|
|
src->uri_track = -1;
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_finalize (GObject * obj)
|
|
{
|
|
GstCddaBaseSrc *cddasrc = GST_CDDA_BASE_SRC (obj);
|
|
|
|
g_free (cddasrc->uri);
|
|
g_free (cddasrc->device);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (obj);
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (object);
|
|
|
|
GST_OBJECT_LOCK (src);
|
|
|
|
switch (prop_id) {
|
|
case ARG_MODE:{
|
|
src->mode = g_value_get_enum (value);
|
|
break;
|
|
}
|
|
case ARG_DEVICE:{
|
|
const gchar *dev = g_value_get_string (value);
|
|
|
|
g_free (src->device);
|
|
if (dev && *dev) {
|
|
src->device = g_strdup (dev);
|
|
} else {
|
|
src->device = NULL;
|
|
}
|
|
break;
|
|
}
|
|
case ARG_TRACK:{
|
|
guint track = g_value_get_uint (value);
|
|
|
|
if (src->num_tracks > 0 && track > src->num_tracks) {
|
|
g_warning ("Invalid track %u", track);
|
|
} else if (track > 0 && src->tracks != NULL) {
|
|
src->cur_sector = src->tracks[track - 1].start;
|
|
src->uri_track = track;
|
|
} else {
|
|
src->uri_track = track; /* seek will be done in start() */
|
|
}
|
|
break;
|
|
}
|
|
case ARG_TOC_OFFSET:{
|
|
src->toc_offset = g_value_get_int (value);
|
|
break;
|
|
}
|
|
case ARG_TOC_BIAS:{
|
|
src->toc_bias = g_value_get_boolean (value);
|
|
break;
|
|
}
|
|
default:{
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (src);
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (object);
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (object);
|
|
|
|
GST_OBJECT_LOCK (src);
|
|
|
|
switch (prop_id) {
|
|
case ARG_MODE:
|
|
g_value_set_enum (value, src->mode);
|
|
break;
|
|
case ARG_DEVICE:{
|
|
if (src->device == NULL && klass->get_default_device != NULL) {
|
|
gchar *d = klass->get_default_device (src);
|
|
|
|
if (d != NULL) {
|
|
g_value_set_string (value, DEFAULT_DEVICE);
|
|
g_free (d);
|
|
break;
|
|
}
|
|
}
|
|
if (src->device == NULL)
|
|
g_value_set_string (value, DEFAULT_DEVICE);
|
|
else
|
|
g_value_set_string (value, src->device);
|
|
break;
|
|
}
|
|
case ARG_TRACK:{
|
|
if (src->num_tracks <= 0 && src->uri_track > 0) {
|
|
g_value_set_uint (value, src->uri_track);
|
|
} else {
|
|
g_value_set_uint (value, src->cur_track + 1);
|
|
}
|
|
break;
|
|
}
|
|
case ARG_TOC_OFFSET:
|
|
g_value_set_int (value, src->toc_offset);
|
|
break;
|
|
case ARG_TOC_BIAS:
|
|
g_value_set_boolean (value, src->toc_bias);
|
|
break;
|
|
default:{
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (src);
|
|
}
|
|
|
|
static gint
|
|
gst_cdda_base_src_get_track_from_sector (GstCddaBaseSrc * src, gint sector)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < src->num_tracks; ++i) {
|
|
if (sector >= src->tracks[i].start && sector <= src->tracks[i].end)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static const GstQueryType *
|
|
gst_cdda_base_src_get_query_types (GstPad * pad)
|
|
{
|
|
static const GstQueryType src_query_types[] = {
|
|
GST_QUERY_DURATION,
|
|
GST_QUERY_POSITION,
|
|
GST_QUERY_CONVERT,
|
|
0
|
|
};
|
|
|
|
return src_query_types;
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_convert (GstCddaBaseSrc * src, GstFormat src_format,
|
|
gint64 src_val, GstFormat dest_format, gint64 * dest_val)
|
|
{
|
|
gboolean started;
|
|
|
|
GST_LOG_OBJECT (src, "converting value %" G_GINT64_FORMAT " from %s into %s",
|
|
src_val, gst_format_get_name (src_format),
|
|
gst_format_get_name (dest_format));
|
|
|
|
if (src_format == dest_format) {
|
|
*dest_val = src_val;
|
|
return TRUE;
|
|
}
|
|
|
|
started = GST_OBJECT_FLAG_IS_SET (GST_BASE_SRC (src), GST_BASE_SRC_STARTED);
|
|
|
|
if (src_format == track_format) {
|
|
if (!started)
|
|
goto not_started;
|
|
if (src_val < 0 || src_val >= src->num_tracks) {
|
|
GST_DEBUG_OBJECT (src, "track number %d out of bounds", (gint) src_val);
|
|
goto wrong_value;
|
|
}
|
|
src_format = GST_FORMAT_DEFAULT;
|
|
src_val = src->tracks[src_val].start * SAMPLES_PER_SECTOR;
|
|
} else if (src_format == sector_format) {
|
|
src_format = GST_FORMAT_DEFAULT;
|
|
src_val = src_val * SAMPLES_PER_SECTOR;
|
|
}
|
|
|
|
if (src_format == dest_format) {
|
|
*dest_val = src_val;
|
|
goto done;
|
|
}
|
|
|
|
switch (src_format) {
|
|
case GST_FORMAT_BYTES:
|
|
/* convert to samples (4 bytes per sample) */
|
|
src_val = src_val >> 2;
|
|
/* fallthrough */
|
|
case GST_FORMAT_DEFAULT:{
|
|
switch (dest_format) {
|
|
case GST_FORMAT_BYTES:{
|
|
if (src_val < 0) {
|
|
GST_DEBUG_OBJECT (src, "sample source value negative");
|
|
goto wrong_value;
|
|
}
|
|
*dest_val = src_val << 2; /* 4 bytes per sample */
|
|
break;
|
|
}
|
|
case GST_FORMAT_TIME:{
|
|
*dest_val = gst_util_uint64_scale_int (src_val, GST_SECOND, 44100);
|
|
break;
|
|
}
|
|
default:{
|
|
gint64 sector = src_val / SAMPLES_PER_SECTOR;
|
|
|
|
if (dest_format == sector_format) {
|
|
*dest_val = sector;
|
|
} else if (dest_format == track_format) {
|
|
if (!started)
|
|
goto not_started;
|
|
*dest_val = gst_cdda_base_src_get_track_from_sector (src, sector);
|
|
} else {
|
|
goto unknown_format;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case GST_FORMAT_TIME:{
|
|
gint64 sample_offset;
|
|
|
|
if (src_val == GST_CLOCK_TIME_NONE) {
|
|
GST_DEBUG_OBJECT (src, "source time value invalid");
|
|
goto wrong_value;
|
|
}
|
|
|
|
sample_offset = gst_util_uint64_scale_int (src_val, 44100, GST_SECOND);
|
|
switch (dest_format) {
|
|
case GST_FORMAT_BYTES:{
|
|
*dest_val = sample_offset << 2; /* 4 bytes per sample */
|
|
break;
|
|
}
|
|
case GST_FORMAT_DEFAULT:{
|
|
*dest_val = sample_offset;
|
|
break;
|
|
}
|
|
default:{
|
|
gint64 sector = sample_offset / SAMPLES_PER_SECTOR;
|
|
|
|
if (dest_format == sector_format) {
|
|
*dest_val = sector;
|
|
} else if (dest_format == track_format) {
|
|
if (!started)
|
|
goto not_started;
|
|
*dest_val = gst_cdda_base_src_get_track_from_sector (src, sector);
|
|
} else {
|
|
goto unknown_format;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:{
|
|
goto unknown_format;
|
|
}
|
|
}
|
|
|
|
done:
|
|
{
|
|
GST_LOG_OBJECT (src, "returning %" G_GINT64_FORMAT, *dest_val);
|
|
return TRUE;
|
|
}
|
|
|
|
unknown_format:
|
|
{
|
|
GST_DEBUG_OBJECT (src, "conversion failed: %s", "unsupported format");
|
|
return FALSE;
|
|
}
|
|
|
|
wrong_value:
|
|
{
|
|
GST_DEBUG_OBJECT (src, "conversion failed: %s",
|
|
"source value not within allowed range");
|
|
return FALSE;
|
|
}
|
|
|
|
not_started:
|
|
{
|
|
GST_DEBUG_OBJECT (src, "conversion failed: %s",
|
|
"cannot do this conversion, device not open");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_query (GstBaseSrc * basesrc, GstQuery * query)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
|
|
gboolean started;
|
|
|
|
started = GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_STARTED);
|
|
|
|
GST_LOG_OBJECT (src, "handling %s query",
|
|
gst_query_type_get_name (GST_QUERY_TYPE (query)));
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_DURATION:{
|
|
GstFormat dest_format;
|
|
gint64 dest_val;
|
|
guint sectors;
|
|
|
|
gst_query_parse_duration (query, &dest_format, NULL);
|
|
|
|
if (!started)
|
|
return FALSE;
|
|
|
|
g_assert (src->tracks != NULL);
|
|
|
|
if (dest_format == track_format) {
|
|
GST_LOG_OBJECT (src, "duration: %d tracks", src->num_tracks);
|
|
gst_query_set_duration (query, track_format, src->num_tracks);
|
|
return TRUE;
|
|
}
|
|
|
|
if (src->cur_track < 0 || src->cur_track >= src->num_tracks)
|
|
return FALSE;
|
|
|
|
if (src->mode == GST_CDDA_BASE_SRC_MODE_NORMAL) {
|
|
sectors = src->tracks[src->cur_track].end -
|
|
src->tracks[src->cur_track].start + 1;
|
|
} else {
|
|
sectors = src->tracks[src->num_tracks - 1].end -
|
|
src->tracks[0].start + 1;
|
|
}
|
|
|
|
/* ... and convert into final format */
|
|
if (!gst_cdda_base_src_convert (src, sector_format, sectors,
|
|
dest_format, &dest_val)) {
|
|
return FALSE;
|
|
}
|
|
|
|
gst_query_set_duration (query, dest_format, dest_val);
|
|
|
|
GST_LOG ("duration: %u sectors, %" G_GINT64_FORMAT " in format %s",
|
|
sectors, dest_val, gst_format_get_name (dest_format));
|
|
break;
|
|
}
|
|
case GST_QUERY_POSITION:{
|
|
GstFormat dest_format;
|
|
gint64 pos_sector;
|
|
gint64 dest_val;
|
|
|
|
gst_query_parse_position (query, &dest_format, NULL);
|
|
|
|
if (!started)
|
|
return FALSE;
|
|
|
|
g_assert (src->tracks != NULL);
|
|
|
|
if (dest_format == track_format) {
|
|
GST_LOG_OBJECT (src, "position: track %d", src->cur_track);
|
|
gst_query_set_position (query, track_format, src->cur_track);
|
|
return TRUE;
|
|
}
|
|
|
|
if (src->cur_track < 0 || src->cur_track >= src->num_tracks)
|
|
return FALSE;
|
|
|
|
if (src->mode == GST_CDDA_BASE_SRC_MODE_NORMAL) {
|
|
pos_sector = src->cur_sector - src->tracks[src->cur_track].start;
|
|
} else {
|
|
pos_sector = src->cur_sector - src->tracks[0].start;
|
|
}
|
|
|
|
if (!gst_cdda_base_src_convert (src, sector_format, pos_sector,
|
|
dest_format, &dest_val)) {
|
|
return FALSE;
|
|
}
|
|
|
|
gst_query_set_position (query, dest_format, dest_val);
|
|
|
|
GST_LOG ("position: sector %u, %" G_GINT64_FORMAT " in format %s",
|
|
(guint) pos_sector, dest_val, gst_format_get_name (dest_format));
|
|
break;
|
|
}
|
|
case GST_QUERY_CONVERT:{
|
|
GstFormat src_format, dest_format;
|
|
gint64 src_val, dest_val;
|
|
|
|
gst_query_parse_convert (query, &src_format, &src_val, &dest_format,
|
|
NULL);
|
|
|
|
if (!gst_cdda_base_src_convert (src, src_format, src_val, dest_format,
|
|
&dest_val)) {
|
|
return FALSE;
|
|
}
|
|
|
|
gst_query_set_convert (query, src_format, src_val, dest_format, dest_val);
|
|
break;
|
|
}
|
|
default:{
|
|
GST_DEBUG_OBJECT (src, "unhandled query, chaining up to parent class");
|
|
return GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_is_seekable (GstBaseSrc * basesrc)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_do_seek (GstBaseSrc * basesrc, GstSegment * segment)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
|
|
gint64 seek_sector;
|
|
|
|
GST_DEBUG_OBJECT (src, "segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop));
|
|
|
|
if (!gst_cdda_base_src_convert (src, GST_FORMAT_TIME, segment->start,
|
|
sector_format, &seek_sector)) {
|
|
GST_WARNING_OBJECT (src, "conversion failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* we should only really be called when open */
|
|
g_assert (src->cur_track >= 0 && src->cur_track < src->num_tracks);
|
|
|
|
switch (src->mode) {
|
|
case GST_CDDA_BASE_SRC_MODE_NORMAL:
|
|
seek_sector += src->tracks[src->cur_track].start;
|
|
break;
|
|
case GST_CDDA_BASE_SRC_MODE_CONTINUOUS:
|
|
seek_sector += src->tracks[0].start;
|
|
break;
|
|
default:
|
|
g_return_val_if_reached (FALSE);
|
|
}
|
|
|
|
src->cur_sector = (gint) seek_sector;
|
|
|
|
GST_DEBUG_OBJECT (src, "seek'd to sector %d", src->cur_sector);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_handle_track_seek (GstCddaBaseSrc * src, gdouble rate,
|
|
GstSeekFlags flags, GstSeekType start_type, gint64 start,
|
|
GstSeekType stop_type, gint64 stop)
|
|
{
|
|
GstBaseSrc *basesrc = GST_BASE_SRC (src);
|
|
GstEvent *event;
|
|
|
|
if ((flags & GST_SEEK_FLAG_SEGMENT) == GST_SEEK_FLAG_SEGMENT) {
|
|
gint64 start_time = -1;
|
|
gint64 stop_time = -1;
|
|
|
|
if (src->mode != GST_CDDA_BASE_SRC_MODE_CONTINUOUS) {
|
|
GST_DEBUG_OBJECT (src, "segment seek in track format is only "
|
|
"supported in CONTINUOUS mode, not in mode %d", src->mode);
|
|
return FALSE;
|
|
}
|
|
|
|
switch (start_type) {
|
|
case GST_SEEK_TYPE_SET:
|
|
if (!gst_cdda_base_src_convert (src, track_format, start,
|
|
GST_FORMAT_TIME, &start_time)) {
|
|
GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
|
|
(gint) start);
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case GST_SEEK_TYPE_END:
|
|
if (!gst_cdda_base_src_convert (src, track_format,
|
|
src->num_tracks - start - 1, GST_FORMAT_TIME, &start_time)) {
|
|
GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
|
|
(gint) start);
|
|
return FALSE;
|
|
}
|
|
start_type = GST_SEEK_TYPE_SET;
|
|
break;
|
|
case GST_SEEK_TYPE_NONE:
|
|
start_time = -1;
|
|
break;
|
|
default:
|
|
g_return_val_if_reached (FALSE);
|
|
}
|
|
|
|
switch (stop_type) {
|
|
case GST_SEEK_TYPE_SET:
|
|
if (!gst_cdda_base_src_convert (src, track_format, stop,
|
|
GST_FORMAT_TIME, &stop_time)) {
|
|
GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
|
|
(gint) stop);
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case GST_SEEK_TYPE_END:
|
|
if (!gst_cdda_base_src_convert (src, track_format,
|
|
src->num_tracks - stop - 1, GST_FORMAT_TIME, &stop_time)) {
|
|
GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
|
|
(gint) stop);
|
|
return FALSE;
|
|
}
|
|
stop_type = GST_SEEK_TYPE_SET;
|
|
break;
|
|
case GST_SEEK_TYPE_NONE:
|
|
stop_time = -1;
|
|
break;
|
|
default:
|
|
g_return_val_if_reached (FALSE);
|
|
}
|
|
|
|
GST_LOG_OBJECT (src, "seek segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (start_time), GST_TIME_ARGS (stop_time));
|
|
|
|
/* send fake segment seek event in TIME format to
|
|
* base class, which will hopefully handle the rest */
|
|
|
|
event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type,
|
|
start_time, stop_type, stop_time);
|
|
|
|
return GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
|
|
}
|
|
|
|
/* not a segment seek */
|
|
|
|
if (start_type == GST_SEEK_TYPE_NONE) {
|
|
GST_LOG_OBJECT (src, "start seek type is NONE, nothing to do");
|
|
return TRUE;
|
|
}
|
|
|
|
if (stop_type != GST_SEEK_TYPE_NONE) {
|
|
GST_WARNING_OBJECT (src, "ignoring stop seek type (expected NONE)");
|
|
}
|
|
|
|
if (start < 0 || start >= src->num_tracks) {
|
|
GST_DEBUG_OBJECT (src, "invalid track %" G_GINT64_FORMAT, start);
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (src, "seeking to track %" G_GINT64_FORMAT, start + 1);
|
|
|
|
src->cur_sector = src->tracks[start].start;
|
|
GST_DEBUG_OBJECT (src, "starting at sector %d", src->cur_sector);
|
|
|
|
if (src->cur_track != start) {
|
|
src->cur_track = (gint) start;
|
|
src->uri_track = -1;
|
|
src->prev_track = -1;
|
|
|
|
gst_cdda_base_src_update_duration (src);
|
|
} else {
|
|
GST_DEBUG_OBJECT (src, "is current track, just seeking back to start");
|
|
}
|
|
|
|
/* send fake segment seek event in TIME format to
|
|
* base class (so we get a newsegment etc.) */
|
|
event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
|
|
GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1);
|
|
|
|
return GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_handle_event (GstBaseSrc * basesrc, GstEvent * event)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
|
|
gboolean ret = FALSE;
|
|
|
|
GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event));
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEEK:{
|
|
GstSeekType start_type, stop_type;
|
|
GstSeekFlags flags;
|
|
GstFormat format;
|
|
gdouble rate;
|
|
gint64 start, stop;
|
|
|
|
if (!GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_STARTED)) {
|
|
GST_DEBUG_OBJECT (src, "seek failed: device not open");
|
|
break;
|
|
}
|
|
|
|
gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
|
|
&stop_type, &stop);
|
|
|
|
if (format == sector_format) {
|
|
GST_DEBUG_OBJECT (src, "seek in sector format not supported");
|
|
break;
|
|
}
|
|
|
|
if (format == track_format) {
|
|
ret = gst_cdda_base_src_handle_track_seek (src, rate, flags,
|
|
start_type, start, stop_type, stop);
|
|
} else {
|
|
GST_LOG_OBJECT (src, "let base class handle seek in %s format",
|
|
gst_format_get_name (format));
|
|
event = gst_event_ref (event);
|
|
ret = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
|
|
}
|
|
break;
|
|
}
|
|
default:{
|
|
GST_LOG_OBJECT (src, "let base class handle event");
|
|
ret = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstURIType
|
|
gst_cdda_base_src_uri_get_type (void)
|
|
{
|
|
return GST_URI_SRC;
|
|
}
|
|
|
|
static gchar **
|
|
gst_cdda_base_src_uri_get_protocols (void)
|
|
{
|
|
static gchar *protocols[] = { "cdda", NULL };
|
|
|
|
return protocols;
|
|
}
|
|
|
|
static const gchar *
|
|
gst_cdda_base_src_uri_get_uri (GstURIHandler * handler)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (handler);
|
|
|
|
GST_OBJECT_LOCK (src);
|
|
|
|
g_free (src->uri);
|
|
|
|
if (GST_OBJECT_FLAG_IS_SET (GST_BASE_SRC (src), GST_BASE_SRC_STARTED)) {
|
|
src->uri = g_strdup_printf ("cdda://%d", src->uri_track);
|
|
} else {
|
|
src->uri = g_strdup ("cdda://1");
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (src);
|
|
|
|
return src->uri;
|
|
}
|
|
|
|
/* Note: gst_element_make_from_uri() might call us with just 'cdda://' as
|
|
* URI and expects us to return TRUE then (and this might be in any state) */
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (handler);
|
|
gchar *protocol, *location;
|
|
|
|
GST_OBJECT_LOCK (src);
|
|
|
|
protocol = gst_uri_get_protocol (uri);
|
|
if (!protocol || strcmp (protocol, "cdda") != 0) {
|
|
g_free (protocol);
|
|
goto failed;
|
|
}
|
|
g_free (protocol);
|
|
|
|
location = gst_uri_get_location (uri);
|
|
if (location == NULL || *location == '\0') {
|
|
g_free (location);
|
|
location = g_strdup ("1");
|
|
}
|
|
|
|
src->uri_track = strtol (location, NULL, 10);
|
|
g_free (location);
|
|
|
|
if (src->uri_track == 0)
|
|
goto failed;
|
|
|
|
if (src->num_tracks > 0
|
|
&& src->tracks != NULL && src->uri_track > src->num_tracks)
|
|
goto failed;
|
|
|
|
if (src->uri_track > 0 && src->tracks != NULL) {
|
|
GST_OBJECT_UNLOCK (src);
|
|
|
|
gst_pad_send_event (GST_BASE_SRC_PAD (src),
|
|
gst_event_new_seek (1.0, track_format, GST_SEEK_FLAG_FLUSH,
|
|
GST_SEEK_TYPE_SET, src->uri_track - 1, GST_SEEK_TYPE_NONE, -1));
|
|
} else {
|
|
/* seek will be done in start() */
|
|
GST_OBJECT_UNLOCK (src);
|
|
}
|
|
|
|
GST_LOG_OBJECT (handler, "successfully handled uri '%s'", uri);
|
|
|
|
return TRUE;
|
|
|
|
failed:
|
|
{
|
|
GST_OBJECT_UNLOCK (src);
|
|
GST_DEBUG_OBJECT (src, "cannot handle URI '%s'", uri);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
|
|
{
|
|
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
|
|
|
|
iface->get_type = gst_cdda_base_src_uri_get_type;
|
|
iface->get_uri = gst_cdda_base_src_uri_get_uri;
|
|
iface->set_uri = gst_cdda_base_src_uri_set_uri;
|
|
iface->get_protocols = gst_cdda_base_src_uri_get_protocols;
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_setup_interfaces (GType type)
|
|
{
|
|
static const GInterfaceInfo urihandler_info = {
|
|
gst_cdda_base_src_uri_handler_init,
|
|
NULL,
|
|
NULL,
|
|
};
|
|
|
|
g_type_add_interface_static (type, GST_TYPE_URI_HANDLER, &urihandler_info);
|
|
}
|
|
|
|
/**
|
|
* gst_cdda_base_src_add_track:
|
|
* @src: a #GstCddaBaseSrc
|
|
* @track: address of #GstCddaBaseSrcTrack to add
|
|
*
|
|
* CDDA sources use this function from their start vfunc to announce the
|
|
* available data and audio tracks to the base source class. The caller
|
|
* should allocate @track on the stack, the base source will do a shallow
|
|
* copy of the structure (and take ownership of the taglist if there is one).
|
|
*
|
|
* Returns: FALSE on error, otherwise TRUE.
|
|
*/
|
|
|
|
gboolean
|
|
gst_cdda_base_src_add_track (GstCddaBaseSrc * src, GstCddaBaseSrcTrack * track)
|
|
{
|
|
g_return_val_if_fail (GST_IS_CDDA_BASE_SRC (src), FALSE);
|
|
g_return_val_if_fail (track != NULL, FALSE);
|
|
g_return_val_if_fail (track->num > 0, FALSE);
|
|
|
|
GST_DEBUG_OBJECT (src, "adding track %2u (%2u) [%6u-%6u] [%5s], tags: %"
|
|
GST_PTR_FORMAT, src->num_tracks + 1, track->num, track->start,
|
|
track->end, (track->is_audio) ? "AUDIO" : "DATA ", track->tags);
|
|
|
|
if (src->num_tracks > 0) {
|
|
guint end_of_previous_track = src->tracks[src->num_tracks - 1].end;
|
|
|
|
if (track->start <= end_of_previous_track) {
|
|
GST_WARNING ("track %2u overlaps with previous tracks", track->num);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
GST_OBJECT_LOCK (src);
|
|
|
|
++src->num_tracks;
|
|
src->tracks = g_renew (GstCddaBaseSrcTrack, src->tracks, src->num_tracks);
|
|
src->tracks[src->num_tracks - 1] = *track;
|
|
|
|
GST_OBJECT_UNLOCK (src);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_update_duration (GstCddaBaseSrc * src)
|
|
{
|
|
GstBaseSrc *basesrc;
|
|
GstFormat format;
|
|
gint64 duration;
|
|
|
|
basesrc = GST_BASE_SRC (src);
|
|
|
|
format = GST_FORMAT_TIME;
|
|
if (gst_pad_query_duration (GST_BASE_SRC_PAD (src), &format, &duration)) {
|
|
gst_segment_set_duration (&basesrc->segment, GST_FORMAT_TIME, duration);
|
|
} else {
|
|
gst_segment_set_duration (&basesrc->segment, GST_FORMAT_TIME, -1);
|
|
duration = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
gst_element_post_message (GST_ELEMENT (src),
|
|
gst_message_new_duration (GST_OBJECT (src), GST_FORMAT_TIME, -1));
|
|
|
|
GST_LOG_OBJECT (src, "duration updated to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (duration));
|
|
}
|
|
|
|
#define CD_MSF_OFFSET 150
|
|
|
|
/* the cddb hash function */
|
|
static guint
|
|
cddb_sum (gint n)
|
|
{
|
|
guint ret;
|
|
|
|
ret = 0;
|
|
while (n > 0) {
|
|
ret += (n % 10);
|
|
n /= 10;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#include "sha1.h"
|
|
|
|
static void
|
|
gst_cddabasesrc_calculate_musicbrainz_discid (GstCddaBaseSrc * src)
|
|
{
|
|
GString *s;
|
|
SHA_INFO sha;
|
|
guchar digest[20];
|
|
gchar *ptr;
|
|
gchar tmp[9];
|
|
gulong i;
|
|
guint leadout_sector;
|
|
|
|
s = g_string_new (NULL);
|
|
|
|
leadout_sector = src->tracks[src->num_tracks - 1].end + 1 + CD_MSF_OFFSET;
|
|
|
|
/* generate SHA digest */
|
|
sha_init (&sha);
|
|
g_snprintf (tmp, sizeof (tmp), "%02X", src->tracks[0].num);
|
|
g_string_append_printf (s, "%02X", src->tracks[0].num);
|
|
sha_update (&sha, (SHA_BYTE *) tmp, 2);
|
|
|
|
g_snprintf (tmp, sizeof (tmp), "%02X", src->tracks[src->num_tracks - 1].num);
|
|
g_string_append_printf (s, " %02X", src->tracks[src->num_tracks - 1].num);
|
|
sha_update (&sha, (SHA_BYTE *) tmp, 2);
|
|
|
|
g_snprintf (tmp, sizeof (tmp), "%08X", leadout_sector);
|
|
g_string_append_printf (s, " %08X", leadout_sector);
|
|
sha_update (&sha, (SHA_BYTE *) tmp, 8);
|
|
|
|
for (i = 0; i < 99; i++) {
|
|
if (i < src->num_tracks) {
|
|
guint frame_offset = src->tracks[i].start + CD_MSF_OFFSET;
|
|
|
|
g_snprintf (tmp, sizeof (tmp), "%08X", frame_offset);
|
|
g_string_append_printf (s, " %08X", frame_offset);
|
|
sha_update (&sha, (SHA_BYTE *) tmp, 8);
|
|
} else {
|
|
sha_update (&sha, (SHA_BYTE *) "00000000", 8);
|
|
}
|
|
}
|
|
sha_final (digest, &sha);
|
|
|
|
/* re-encode to base64 */
|
|
ptr = g_base64_encode (digest, 20);
|
|
i = strlen (ptr);
|
|
|
|
g_assert (i < sizeof (src->mb_discid) + 1);
|
|
memcpy (src->mb_discid, ptr, i);
|
|
src->mb_discid[i] = '\0';
|
|
free (ptr);
|
|
|
|
/* Replace '/', '+' and '=' by '_', '.' and '-' as specified on
|
|
* http://musicbrainz.org/doc/DiscIDCalculation
|
|
*/
|
|
for (ptr = src->mb_discid; *ptr != '\0'; ptr++) {
|
|
if (*ptr == '/')
|
|
*ptr = '_';
|
|
else if (*ptr == '+')
|
|
*ptr = '.';
|
|
else if (*ptr == '=')
|
|
*ptr = '-';
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (src, "musicbrainz-discid = %s", src->mb_discid);
|
|
GST_DEBUG_OBJECT (src, "musicbrainz-discid-full = %s", s->str);
|
|
|
|
gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_CDDA_MUSICBRAINZ_DISCID, src->mb_discid,
|
|
GST_TAG_CDDA_MUSICBRAINZ_DISCID_FULL, s->str, NULL);
|
|
|
|
g_string_free (s, TRUE);
|
|
}
|
|
|
|
static void
|
|
lba_to_msf (guint sector, guint * p_m, guint * p_s, guint * p_f, guint * p_secs)
|
|
{
|
|
guint m, s, f;
|
|
|
|
m = sector / SECTORS_PER_MINUTE;
|
|
sector = sector % SECTORS_PER_MINUTE;
|
|
s = sector / SECTORS_PER_SECOND;
|
|
f = sector % SECTORS_PER_SECOND;
|
|
|
|
if (p_m)
|
|
*p_m = m;
|
|
if (p_s)
|
|
*p_s = s;
|
|
if (p_f)
|
|
*p_f = f;
|
|
if (p_secs)
|
|
*p_secs = s + (m * 60);
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_calculate_cddb_id (GstCddaBaseSrc * src)
|
|
{
|
|
GString *s;
|
|
guint first_sector = 0, last_sector = 0;
|
|
guint start_secs, end_secs, secs, len_secs;
|
|
guint total_secs, num_audio_tracks;
|
|
guint id, t, i;
|
|
|
|
id = 0;
|
|
total_secs = 0;
|
|
num_audio_tracks = 0;
|
|
|
|
/* FIXME: do we use offsets and duration of ALL tracks (data + audio)
|
|
* for the CDDB ID calculation, or only audio tracks? */
|
|
for (i = 0; i < src->num_tracks; ++i) {
|
|
if (1) { /* src->tracks[i].is_audio) { */
|
|
if (num_audio_tracks == 0) {
|
|
first_sector = src->tracks[i].start + CD_MSF_OFFSET;
|
|
}
|
|
last_sector = src->tracks[i].end + CD_MSF_OFFSET + 1;
|
|
++num_audio_tracks;
|
|
|
|
lba_to_msf (src->tracks[i].start + CD_MSF_OFFSET, NULL, NULL, NULL,
|
|
&secs);
|
|
|
|
len_secs = (src->tracks[i].end - src->tracks[i].start + 1) / 75;
|
|
|
|
GST_DEBUG_OBJECT (src, "track %02u: lsn %6u (%02u:%02u), "
|
|
"length: %u seconds (%02u:%02u)",
|
|
num_audio_tracks, src->tracks[i].start + CD_MSF_OFFSET,
|
|
secs / 60, secs % 60, len_secs, len_secs / 60, len_secs % 60);
|
|
|
|
id += cddb_sum (secs);
|
|
total_secs += len_secs;
|
|
}
|
|
}
|
|
|
|
/* first_sector = src->tracks[0].start + CD_MSF_OFFSET; */
|
|
lba_to_msf (first_sector, NULL, NULL, NULL, &start_secs);
|
|
|
|
/* last_sector = src->tracks[src->num_tracks-1].end + CD_MSF_OFFSET; */
|
|
lba_to_msf (last_sector, NULL, NULL, NULL, &end_secs);
|
|
|
|
GST_DEBUG_OBJECT (src, "first_sector = %u = %u secs (%02u:%02u)",
|
|
first_sector, start_secs, start_secs / 60, start_secs % 60);
|
|
GST_DEBUG_OBJECT (src, "last_sector = %u = %u secs (%02u:%02u)",
|
|
last_sector, end_secs, end_secs / 60, end_secs % 60);
|
|
|
|
t = end_secs - start_secs;
|
|
|
|
GST_DEBUG_OBJECT (src, "total length = %u secs (%02u:%02u), added title "
|
|
"lengths = %u seconds (%02u:%02u)", t, t / 60, t % 60, total_secs,
|
|
total_secs / 60, total_secs % 60);
|
|
|
|
src->discid = ((id % 0xff) << 24 | t << 8 | num_audio_tracks);
|
|
|
|
s = g_string_new (NULL);
|
|
g_string_append_printf (s, "%08x", src->discid);
|
|
|
|
gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_CDDA_CDDB_DISCID, s->str, NULL);
|
|
|
|
g_string_append_printf (s, " %u", src->num_tracks);
|
|
for (i = 0; i < src->num_tracks; ++i) {
|
|
g_string_append_printf (s, " %u", src->tracks[i].start + CD_MSF_OFFSET);
|
|
}
|
|
g_string_append_printf (s, " %u", t);
|
|
|
|
gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_CDDA_CDDB_DISCID_FULL, s->str, NULL);
|
|
|
|
GST_DEBUG_OBJECT (src, "cddb discid = %s", s->str);
|
|
|
|
g_string_free (s, TRUE);
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_add_tags (GstCddaBaseSrc * src)
|
|
{
|
|
gint i;
|
|
|
|
/* fill in details for each track */
|
|
for (i = 0; i < src->num_tracks; ++i) {
|
|
gint64 duration;
|
|
guint num_sectors;
|
|
|
|
if (src->tracks[i].tags == NULL)
|
|
src->tracks[i].tags = gst_tag_list_new ();
|
|
|
|
num_sectors = src->tracks[i].end - src->tracks[i].start + 1;
|
|
gst_cdda_base_src_convert (src, sector_format, num_sectors,
|
|
GST_FORMAT_TIME, &duration);
|
|
|
|
gst_tag_list_add (src->tracks[i].tags,
|
|
GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_TRACK_NUMBER, i + 1,
|
|
GST_TAG_TRACK_COUNT, src->num_tracks, GST_TAG_DURATION, duration, NULL);
|
|
}
|
|
|
|
/* now fill in per-album tags and include each track's tags
|
|
* in the album tags, so that interested parties can retrieve
|
|
* the relevant details for each track in one go */
|
|
|
|
/* /////////////////////////////// FIXME should we rather insert num_tracks
|
|
* tags by the name of 'track-tags' and have the caller use
|
|
* gst_tag_list_get_value_index() rather than use tag names incl.
|
|
* the track number ?? *////////////////////////////////////////
|
|
|
|
gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
|
|
GST_TAG_TRACK_COUNT, src->num_tracks, NULL);
|
|
#if 0
|
|
for (i = 0; i < src->num_tracks; ++i) {
|
|
gst_tag_list_add (src->tags, GST_TAG_MERGE_APPEND,
|
|
GST_TAG_CDDA_TRACK_TAGS, src->tracks[i].tags, NULL);
|
|
}
|
|
#endif
|
|
|
|
GST_DEBUG ("src->tags = %" GST_PTR_FORMAT, src->tags);
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_add_index_associations (GstCddaBaseSrc * src)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < src->num_tracks; i++) {
|
|
gint64 sector;
|
|
|
|
sector = src->tracks[i].start;
|
|
gst_index_add_association (src->index, src->index_id, GST_ASSOCIATION_FLAG_KEY_UNIT, track_format, i, /* here we count from 0 */
|
|
sector_format, sector,
|
|
GST_FORMAT_TIME,
|
|
(gint64) (((CD_FRAMESIZE_RAW >> 2) * sector * GST_SECOND) / 44100),
|
|
GST_FORMAT_BYTES, (gint64) (sector << 2), GST_FORMAT_DEFAULT,
|
|
(gint64) ((CD_FRAMESIZE_RAW >> 2) * sector), NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_set_index (GstElement * element, GstIndex * index)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (element);
|
|
|
|
src->index = index;
|
|
|
|
gst_index_get_writer_id (index, GST_OBJECT (src), &src->index_id);
|
|
gst_index_add_format (index, src->index_id, track_format);
|
|
gst_index_add_format (index, src->index_id, sector_format);
|
|
}
|
|
|
|
|
|
static GstIndex *
|
|
gst_cdda_base_src_get_index (GstElement * element)
|
|
{
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (element);
|
|
|
|
return src->index;
|
|
}
|
|
|
|
static gint
|
|
gst_cdda_base_src_track_sort_func (gconstpointer a, gconstpointer b,
|
|
gpointer foo)
|
|
{
|
|
GstCddaBaseSrcTrack *track_a = ((GstCddaBaseSrcTrack *) a);
|
|
GstCddaBaseSrcTrack *track_b = ((GstCddaBaseSrcTrack *) b);
|
|
|
|
/* sort data tracks to the end, and audio tracks by track number */
|
|
if (track_a->is_audio == track_b->is_audio)
|
|
return (gint) track_a->num - (gint) track_b->num;
|
|
|
|
if (track_a->is_audio) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_start (GstBaseSrc * basesrc)
|
|
{
|
|
GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (basesrc);
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
|
|
gboolean ret;
|
|
gchar *device = NULL;
|
|
|
|
src->discid = 0;
|
|
src->mb_discid[0] = '\0';
|
|
|
|
g_assert (klass->open != NULL);
|
|
|
|
if (src->device != NULL) {
|
|
device = g_strdup (src->device);
|
|
} else if (klass->get_default_device != NULL) {
|
|
device = klass->get_default_device (src);
|
|
}
|
|
|
|
if (device == NULL)
|
|
device = g_strdup (DEFAULT_DEVICE);
|
|
|
|
GST_LOG_OBJECT (basesrc, "opening device %s", device);
|
|
|
|
src->tags = gst_tag_list_new ();
|
|
|
|
ret = klass->open (src, device);
|
|
g_free (device);
|
|
device = NULL;
|
|
|
|
if (!ret) {
|
|
GST_DEBUG_OBJECT (basesrc, "failed to open device");
|
|
/* subclass (should have) posted an error message with the details */
|
|
gst_cdda_base_src_stop (basesrc);
|
|
return FALSE;
|
|
}
|
|
|
|
if (src->num_tracks == 0 || src->tracks == NULL) {
|
|
GST_DEBUG_OBJECT (src, "no tracks");
|
|
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
|
|
(_("This CD has no audio tracks")), (NULL));
|
|
gst_cdda_base_src_stop (basesrc);
|
|
return FALSE;
|
|
}
|
|
|
|
/* need to calculate disc IDs before we ditch the data tracks */
|
|
gst_cdda_base_src_calculate_cddb_id (src);
|
|
gst_cddabasesrc_calculate_musicbrainz_discid (src);
|
|
|
|
#if 0
|
|
/* adjust sector offsets if necessary */
|
|
if (src->toc_bias) {
|
|
src->toc_offset -= src->tracks[0].start;
|
|
}
|
|
for (i = 0; i < src->num_tracks; ++i) {
|
|
src->tracks[i].start += src->toc_offset;
|
|
src->tracks[i].end += src->toc_offset;
|
|
}
|
|
#endif
|
|
|
|
/* now that we calculated the various disc IDs,
|
|
* sort the data tracks to end and ignore them */
|
|
src->num_all_tracks = src->num_tracks;
|
|
|
|
g_qsort_with_data (src->tracks, src->num_tracks,
|
|
sizeof (GstCddaBaseSrcTrack), gst_cdda_base_src_track_sort_func, NULL);
|
|
|
|
while (src->num_tracks > 0 && !src->tracks[src->num_tracks - 1].is_audio)
|
|
--src->num_tracks;
|
|
|
|
if (src->num_tracks == 0) {
|
|
GST_DEBUG_OBJECT (src, "no audio tracks");
|
|
GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
|
|
(_("This CD has no audio tracks")), (NULL));
|
|
gst_cdda_base_src_stop (basesrc);
|
|
return FALSE;
|
|
}
|
|
|
|
gst_cdda_base_src_add_tags (src);
|
|
|
|
if (src->index && GST_INDEX_IS_WRITABLE (src->index))
|
|
gst_cdda_base_src_add_index_associations (src);
|
|
|
|
src->cur_track = 0;
|
|
src->prev_track = -1;
|
|
|
|
if (src->uri_track > 0 && src->uri_track <= src->num_tracks) {
|
|
GST_LOG_OBJECT (src, "seek to track %d", src->uri_track);
|
|
src->cur_track = src->uri_track - 1;
|
|
src->uri_track = -1;
|
|
src->mode = GST_CDDA_BASE_SRC_MODE_NORMAL;
|
|
}
|
|
|
|
src->cur_sector = src->tracks[src->cur_track].start;
|
|
GST_LOG_OBJECT (src, "starting at sector %d", src->cur_sector);
|
|
|
|
gst_cdda_base_src_update_duration (src);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_cdda_base_src_clear_tracks (GstCddaBaseSrc * src)
|
|
{
|
|
if (src->tracks != NULL) {
|
|
gint i;
|
|
|
|
for (i = 0; i < src->num_all_tracks; ++i) {
|
|
if (src->tracks[i].tags)
|
|
gst_tag_list_free (src->tracks[i].tags);
|
|
}
|
|
|
|
g_free (src->tracks);
|
|
src->tracks = NULL;
|
|
}
|
|
src->num_tracks = 0;
|
|
src->num_all_tracks = 0;
|
|
}
|
|
|
|
static gboolean
|
|
gst_cdda_base_src_stop (GstBaseSrc * basesrc)
|
|
{
|
|
GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (basesrc);
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
|
|
|
|
g_assert (klass->close != NULL);
|
|
|
|
klass->close (src);
|
|
|
|
gst_cdda_base_src_clear_tracks (src);
|
|
|
|
if (src->tags) {
|
|
gst_tag_list_free (src->tags);
|
|
src->tags = NULL;
|
|
}
|
|
|
|
src->prev_track = -1;
|
|
src->cur_track = -1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static GstFlowReturn
|
|
gst_cdda_base_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
|
|
{
|
|
GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (pushsrc);
|
|
GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (pushsrc);
|
|
GstBuffer *buf;
|
|
GstFormat format;
|
|
gboolean eos;
|
|
|
|
GstClockTime position = GST_CLOCK_TIME_NONE;
|
|
GstClockTime duration = GST_CLOCK_TIME_NONE;
|
|
gint64 qry_position;
|
|
|
|
g_assert (klass->read_sector != NULL);
|
|
|
|
switch (src->mode) {
|
|
case GST_CDDA_BASE_SRC_MODE_NORMAL:
|
|
eos = (src->cur_sector > src->tracks[src->cur_track].end);
|
|
break;
|
|
case GST_CDDA_BASE_SRC_MODE_CONTINUOUS:
|
|
eos = (src->cur_sector > src->tracks[src->num_tracks - 1].end);
|
|
src->cur_track = gst_cdda_base_src_get_track_from_sector (src,
|
|
src->cur_sector);
|
|
break;
|
|
default:
|
|
g_return_val_if_reached (GST_FLOW_ERROR);
|
|
}
|
|
|
|
if (eos) {
|
|
src->prev_track = -1;
|
|
GST_DEBUG_OBJECT (src, "EOS at sector %d, cur_track=%d, mode=%d",
|
|
src->cur_sector, src->cur_track, src->mode);
|
|
/* base class will send EOS for us */
|
|
return GST_FLOW_UNEXPECTED;
|
|
}
|
|
|
|
if (src->prev_track != src->cur_track) {
|
|
GstTagList *tags;
|
|
|
|
tags = gst_tag_list_merge (src->tags, src->tracks[src->cur_track].tags,
|
|
GST_TAG_MERGE_REPLACE);
|
|
GST_LOG_OBJECT (src, "announcing tags: %" GST_PTR_FORMAT, tags);
|
|
gst_element_found_tags_for_pad (GST_ELEMENT (src),
|
|
GST_BASE_SRC_PAD (src), tags);
|
|
src->prev_track = src->cur_track;
|
|
|
|
gst_cdda_base_src_update_duration (src);
|
|
|
|
g_object_notify (G_OBJECT (src), "track");
|
|
}
|
|
|
|
GST_LOG_OBJECT (src, "asking for sector %u", src->cur_sector);
|
|
|
|
buf = klass->read_sector (src, src->cur_sector);
|
|
|
|
if (buf == NULL) {
|
|
GST_WARNING_OBJECT (src, "failed to read sector %u", src->cur_sector);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
if (GST_BUFFER_CAPS (buf) == NULL) {
|
|
gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (src)));
|
|
}
|
|
|
|
format = GST_FORMAT_TIME;
|
|
if (gst_pad_query_position (GST_BASE_SRC_PAD (src), &format, &qry_position)) {
|
|
gint64 next_ts = 0;
|
|
|
|
position = (GstClockTime) qry_position;
|
|
|
|
++src->cur_sector;
|
|
if (gst_pad_query_position (GST_BASE_SRC_PAD (src), &format, &next_ts)) {
|
|
duration = (GstClockTime) (next_ts - qry_position);
|
|
}
|
|
--src->cur_sector;
|
|
}
|
|
|
|
/* fallback duration: 4 bytes per sample, 44100 samples per second */
|
|
if (duration == GST_CLOCK_TIME_NONE) {
|
|
duration = gst_util_uint64_scale_int (GST_BUFFER_SIZE (buf) >> 2,
|
|
GST_SECOND, 44100);
|
|
}
|
|
|
|
GST_BUFFER_TIMESTAMP (buf) = position;
|
|
GST_BUFFER_DURATION (buf) = duration;
|
|
|
|
GST_LOG_OBJECT (src, "pushing sector %d with timestamp %" GST_TIME_FORMAT,
|
|
src->cur_sector, GST_TIME_ARGS (position));
|
|
|
|
++src->cur_sector;
|
|
|
|
*buffer = buf;
|
|
|
|
return GST_FLOW_OK;
|
|
}
|