• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer Audio CD Source Base Class
2  * Copyright (C) 2005 Tim-Philipp Müller <tim centricular net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /* TODO:
21  *
22  *  - in ::start(), we want to post a tags message with an array or a list
23  *    of tagslists of all tracks, so that applications know at least the
24  *    number of tracks and all track durations immediately without having
25  *    to do any querying. We have to decide what type and name to use for
26  *    this array of track taglists.
27  *
28  *  - FIX cddb discid calculation algorithm for mixed mode CDs - do we use
29  *    offsets and duration of ALL tracks (data + audio) for the CDDB ID
30  *    calculation, or only audio tracks?
31  *
32  *  - Do we really need properties for the TOC bias/offset stuff? Wouldn't
33  *    environment variables make much more sense? Do we need this at all
34  *    (does it only affect ancient hardware?)
35  */
36 
37 /**
38  * SECTION:gstaudiocdsrc
39  * @title: GstAudioCdSrc
40  * @short_description: Base class for Audio CD sources
41  *
42  * Provides a base class for CD digital audio (CDDA) sources, which handles
43  * things like seeking, querying, discid calculation, tags, and buffer
44  * timestamping.
45  *
46  * ## Using GstAudioCdSrc-based elements in applications
47  *
48  * GstAudioCdSrc registers two #GstFormat<!-- -->s of its own, namely
49  * the "track" format and the "sector" format. Applications will usually
50  * only find the "track" format interesting. You can retrieve that #GstFormat
51  * for use in seek events or queries with gst_format_get_by_nick("track").
52  *
53  * In order to query the number of tracks, for example, an application would
54  * set the CDDA source element to READY or PAUSED state and then query the
55  * the number of tracks via gst_element_query_duration() using the track
56  * format acquired above. Applications can query the currently playing track
57  * in the same way.
58  *
59  * Alternatively, applications may retrieve the currently playing track and
60  * the total number of tracks from the taglist that will posted on the bus
61  * whenever the CD is opened or the currently playing track changes. The
62  * taglist will contain GST_TAG_TRACK_NUMBER and GST_TAG_TRACK_COUNT tags.
63  *
64  * Applications playing back CD audio using playbin and cdda://n URIs should
65  * issue a seek command in track format to change between tracks, rather than
66  * setting a new cdda://n+1 URI on playbin (as setting a new URI on playbin
67  * involves closing and re-opening the CD device, which is much much slower).
68  *
69  * ## Tags and meta-information
70  *
71  * CDDA sources will automatically emit a number of tags, details about which
72  * can be found in the libgsttag documentation. Those tags are:
73  * #GST_TAG_CDDA_CDDB_DISCID, #GST_TAG_CDDA_CDDB_DISCID_FULL,
74  * #GST_TAG_CDDA_MUSICBRAINZ_DISCID, #GST_TAG_CDDA_MUSICBRAINZ_DISCID_FULL,
75  * among others.
76  *
77  * ## Tracks and Table of Contents (TOC)
78  *
79  * Applications will be informed of the available tracks via a TOC message
80  * on the pipeline's #GstBus. The #GstToc will contain a #GstTocEntry for
81  * each track, with information about each track. The duration for each
82  * track can be retrieved via the #GST_TAG_DURATION tag from each entry's
83  * tag list, or calculated via gst_toc_entry_get_start_stop_times().
84  * The track entries in the TOC will be sorted by track number.
85  *
86  */
87 
88 #ifdef HAVE_CONFIG_H
89 #include "config.h"
90 #endif
91 
92 #include <string.h>
93 #include <stdlib.h>             /* for strtol */
94 #include <stdio.h>
95 
96 #include <gst/tag/tag.h>
97 #include <gst/audio/audio.h>
98 #include "gstaudiocdsrc.h"
99 #include "gst/gst-i18n-plugin.h"
100 
101 GST_DEBUG_CATEGORY_STATIC (gst_audio_cd_src_debug);
102 #define GST_CAT_DEFAULT gst_audio_cd_src_debug
103 
104 #define DEFAULT_DEVICE                       "/dev/cdrom"
105 
106 #define CD_FRAMESIZE_RAW                     (2352)
107 
108 #define SECTORS_PER_SECOND                   (75)
109 #define SECTORS_PER_MINUTE                   (75*60)
110 #define SAMPLES_PER_SECTOR                   (CD_FRAMESIZE_RAW >> 2)
111 #define TIME_INTERVAL_FROM_SECTORS(sectors)  ((SAMPLES_PER_SECTOR * sectors * GST_SECOND) / 44100)
112 #define SECTORS_FROM_TIME_INTERVAL(dtime)    (dtime * 44100 / (SAMPLES_PER_SECTOR * GST_SECOND))
113 
114 enum
115 {
116   ARG_0,
117   ARG_MODE,
118   ARG_DEVICE,
119   ARG_TRACK,
120   ARG_TOC_OFFSET,
121   ARG_TOC_BIAS
122 };
123 
124 struct _GstAudioCdSrcPrivate
125 {
126   GstAudioCdSrcMode mode;
127 
128   gchar *device;
129 
130   guint num_tracks;
131   guint num_all_tracks;
132   GstAudioCdSrcTrack *tracks;
133 
134   gint cur_track;               /* current track (starting from 0) */
135   gint prev_track;              /* current track last time         */
136   gint cur_sector;              /* current sector                  */
137   gint seek_sector;             /* -1 or sector to seek to         */
138 
139   gint uri_track;
140   gchar *uri;
141 
142   guint32 discid;               /* cddb disc id (for unit test)    */
143   gchar mb_discid[32];          /* musicbrainz discid              */
144 
145 #if 0
146   GstIndex *index;
147   gint index_id;
148 #endif
149 
150   gint toc_offset;
151   gboolean toc_bias;
152 
153   GstEvent *toc_event;          /* pending TOC event */
154   GstToc *toc;
155 };
156 
157 static void gst_audio_cd_src_uri_handler_init (gpointer g_iface,
158     gpointer iface_data);
159 static void gst_audio_cd_src_get_property (GObject * object, guint prop_id,
160     GValue * value, GParamSpec * pspec);
161 static void gst_audio_cd_src_set_property (GObject * object, guint prop_id,
162     const GValue * value, GParamSpec * pspec);
163 static void gst_audio_cd_src_finalize (GObject * obj);
164 static gboolean gst_audio_cd_src_query (GstBaseSrc * src, GstQuery * query);
165 static gboolean gst_audio_cd_src_handle_event (GstBaseSrc * basesrc,
166     GstEvent * event);
167 static gboolean gst_audio_cd_src_do_seek (GstBaseSrc * basesrc,
168     GstSegment * segment);
169 static gboolean gst_audio_cd_src_start (GstBaseSrc * basesrc);
170 static gboolean gst_audio_cd_src_stop (GstBaseSrc * basesrc);
171 static GstFlowReturn gst_audio_cd_src_create (GstPushSrc * pushsrc,
172     GstBuffer ** buf);
173 static gboolean gst_audio_cd_src_is_seekable (GstBaseSrc * basesrc);
174 static void gst_audio_cd_src_update_duration (GstAudioCdSrc * src);
175 #if 0
176 static void gst_audio_cd_src_set_index (GstElement * src, GstIndex * index);
177 static GstIndex *gst_audio_cd_src_get_index (GstElement * src);
178 #endif
179 
180 #define gst_audio_cd_src_parent_class parent_class
181 G_DEFINE_TYPE_WITH_CODE (GstAudioCdSrc, gst_audio_cd_src, GST_TYPE_PUSH_SRC,
182     G_ADD_PRIVATE (GstAudioCdSrc)
183     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
184         gst_audio_cd_src_uri_handler_init));
185 
186 #define SRC_CAPS \
187   "audio/x-raw, "               \
188   "format = (string) " GST_AUDIO_NE(S16) ", " \
189   "layout = (string) interleaved, " \
190   "rate = (int) 44100, "            \
191   "channels = (int) 2"              \
192 
193 static GstStaticPadTemplate gst_audio_cd_src_src_template =
194 GST_STATIC_PAD_TEMPLATE ("src",
195     GST_PAD_SRC,
196     GST_PAD_ALWAYS,
197     GST_STATIC_CAPS (SRC_CAPS)
198     );
199 
200 /* our two formats */
201 static GstFormat track_format;
202 static GstFormat sector_format;
203 
204 static void
gst_audio_cd_src_class_init(GstAudioCdSrcClass * klass)205 gst_audio_cd_src_class_init (GstAudioCdSrcClass * klass)
206 {
207   GstElementClass *element_class;
208   GstPushSrcClass *pushsrc_class;
209   GstBaseSrcClass *basesrc_class;
210   GObjectClass *gobject_class;
211 
212   gobject_class = (GObjectClass *) klass;
213   element_class = (GstElementClass *) klass;
214   basesrc_class = (GstBaseSrcClass *) klass;
215   pushsrc_class = (GstPushSrcClass *) klass;
216 
217   GST_DEBUG_CATEGORY_INIT (gst_audio_cd_src_debug, "audiocdsrc", 0,
218       "Audio CD source base class");
219 
220   /* our very own formats */
221   track_format = gst_format_register ("track", "CD track");
222   sector_format = gst_format_register ("sector", "CD sector");
223 
224   /* register CDDA tags */
225   gst_tag_register_musicbrainz_tags ();
226 
227 #if 0
228   ///// FIXME: what type to use here? ///////
229   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??? ///////
230 #endif
231 
232   gobject_class->set_property = gst_audio_cd_src_set_property;
233   gobject_class->get_property = gst_audio_cd_src_get_property;
234   gobject_class->finalize = gst_audio_cd_src_finalize;
235 
236   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
237       g_param_spec_string ("device", "Device", "CD device location",
238           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
239   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MODE,
240       g_param_spec_enum ("mode", "Mode", "Mode", GST_TYPE_AUDIO_CD_SRC_MODE,
241           GST_AUDIO_CD_SRC_MODE_NORMAL,
242           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
243 
244   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TRACK,
245       g_param_spec_uint ("track", "Track", "Track", 1, 99, 1,
246           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
247 
248 #if 0
249   /* Do we really need this toc adjustment stuff as properties? does the user
250    * have a chance to set it in practice, e.g. when using sound-juicer, rb,
251    * totem, whatever? Shouldn't we rather use environment variables
252    * for this? (tpm) */
253 
254   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOC_OFFSET,
255       g_param_spec_int ("toc-offset", "Table of contents offset",
256           "Add <n> sectors to the values reported", G_MININT, G_MAXINT, 0,
257           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
258   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOC_BIAS,
259       g_param_spec_boolean ("toc-bias", "Table of contents bias",
260           "Assume that the beginning offset of track 1 as reported in the TOC "
261           "will be addressed as LBA 0.  Necessary for some Toshiba drives to "
262           "get track boundaries", FALSE,
263           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
264 #endif
265 
266   gst_element_class_add_static_pad_template (element_class,
267       &gst_audio_cd_src_src_template);
268 
269 #if 0
270   element_class->set_index = GST_DEBUG_FUNCPTR (gst_audio_cd_src_set_index);
271   element_class->get_index = GST_DEBUG_FUNCPTR (gst_audio_cd_src_get_index);
272 #endif
273 
274   basesrc_class->start = GST_DEBUG_FUNCPTR (gst_audio_cd_src_start);
275   basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_audio_cd_src_stop);
276   basesrc_class->query = GST_DEBUG_FUNCPTR (gst_audio_cd_src_query);
277   basesrc_class->event = GST_DEBUG_FUNCPTR (gst_audio_cd_src_handle_event);
278   basesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_audio_cd_src_do_seek);
279   basesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_audio_cd_src_is_seekable);
280 
281   pushsrc_class->create = GST_DEBUG_FUNCPTR (gst_audio_cd_src_create);
282 }
283 
284 static void
gst_audio_cd_src_init(GstAudioCdSrc * src)285 gst_audio_cd_src_init (GstAudioCdSrc * src)
286 {
287   src->priv = gst_audio_cd_src_get_instance_private (src);
288 
289   /* we're not live and we operate in time */
290   gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
291   gst_base_src_set_live (GST_BASE_SRC (src), FALSE);
292 
293   GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_INDEXABLE);
294 
295   src->priv->device = NULL;
296   src->priv->mode = GST_AUDIO_CD_SRC_MODE_NORMAL;
297   src->priv->uri_track = -1;
298 }
299 
300 static void
gst_audio_cd_src_finalize(GObject * obj)301 gst_audio_cd_src_finalize (GObject * obj)
302 {
303   GstAudioCdSrc *cddasrc = GST_AUDIO_CD_SRC (obj);
304 
305   g_free (cddasrc->priv->uri);
306   g_free (cddasrc->priv->device);
307 
308 #if 0
309   if (cddasrc->priv->index)
310     gst_object_unref (cddasrc->priv->index);
311 #endif
312 
313   G_OBJECT_CLASS (parent_class)->finalize (obj);
314 }
315 
316 static void
gst_audio_cd_src_set_device(GstAudioCdSrc * src,const gchar * device)317 gst_audio_cd_src_set_device (GstAudioCdSrc * src, const gchar * device)
318 {
319   if (src->priv->device)
320     g_free (src->priv->device);
321   src->priv->device = NULL;
322 
323   if (!device)
324     return;
325 
326   /* skip multiple slashes */
327   while (*device == '/' && *(device + 1) == '/')
328     device++;
329 
330 #ifdef __sun
331   /*
332    * On Solaris, /dev/rdsk is used for accessing the CD device, but some
333    * applications pass in /dev/dsk, so correct.
334    */
335   if (strncmp (device, "/dev/dsk", 8) == 0) {
336     gchar *rdsk_value;
337     rdsk_value = g_strdup_printf ("/dev/rdsk%s", device + 8);
338     src->priv->device = g_strdup (rdsk_value);
339     g_free (rdsk_value);
340   } else {
341 #endif
342     src->priv->device = g_strdup (device);
343 #ifdef __sun
344   }
345 #endif
346 }
347 
348 static void
gst_audio_cd_src_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)349 gst_audio_cd_src_set_property (GObject * object, guint prop_id,
350     const GValue * value, GParamSpec * pspec)
351 {
352   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (object);
353 
354   GST_OBJECT_LOCK (src);
355 
356   switch (prop_id) {
357     case ARG_MODE:{
358       src->priv->mode = g_value_get_enum (value);
359       break;
360     }
361     case ARG_DEVICE:{
362       const gchar *dev = g_value_get_string (value);
363 
364       gst_audio_cd_src_set_device (src, dev);
365       break;
366     }
367     case ARG_TRACK:{
368       guint track = g_value_get_uint (value);
369 
370       if (src->priv->num_tracks > 0 && track > src->priv->num_tracks) {
371         g_warning ("Invalid track %u", track);
372       } else if (track > 0 && src->priv->tracks != NULL) {
373         src->priv->cur_sector = src->priv->tracks[track - 1].start;
374         src->priv->uri_track = track;
375       } else {
376         src->priv->uri_track = track;   /* seek will be done in start() */
377       }
378       break;
379     }
380     case ARG_TOC_OFFSET:{
381       src->priv->toc_offset = g_value_get_int (value);
382       break;
383     }
384     case ARG_TOC_BIAS:{
385       src->priv->toc_bias = g_value_get_boolean (value);
386       break;
387     }
388     default:{
389       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
390       break;
391     }
392   }
393 
394   GST_OBJECT_UNLOCK (src);
395 }
396 
397 static void
gst_audio_cd_src_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)398 gst_audio_cd_src_get_property (GObject * object, guint prop_id,
399     GValue * value, GParamSpec * pspec)
400 {
401 #if 0
402   GstAudioCdSrcClass *klass = GST_AUDIO_CD_SRC_GET_CLASS (object);
403 #endif
404   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (object);
405 
406   GST_OBJECT_LOCK (src);
407 
408   switch (prop_id) {
409     case ARG_MODE:
410       g_value_set_enum (value, src->priv->mode);
411       break;
412     case ARG_DEVICE:{
413 #if 0
414       if (src->priv->device == NULL && klass->get_default_device != NULL) {
415         gchar *d = klass->get_default_device (src);
416 
417         if (d != NULL) {
418           g_value_set_string (value, DEFAULT_DEVICE);
419           g_free (d);
420           break;
421         }
422       }
423 #endif
424       if (src->priv->device == NULL)
425         g_value_set_string (value, DEFAULT_DEVICE);
426       else
427         g_value_set_string (value, src->priv->device);
428       break;
429     }
430     case ARG_TRACK:{
431       if (src->priv->num_tracks <= 0 && src->priv->uri_track > 0) {
432         g_value_set_uint (value, src->priv->uri_track);
433       } else {
434         g_value_set_uint (value, src->priv->cur_track + 1);
435       }
436       break;
437     }
438     case ARG_TOC_OFFSET:
439       g_value_set_int (value, src->priv->toc_offset);
440       break;
441     case ARG_TOC_BIAS:
442       g_value_set_boolean (value, src->priv->toc_bias);
443       break;
444     default:{
445       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
446       break;
447     }
448   }
449 
450   GST_OBJECT_UNLOCK (src);
451 }
452 
453 static gint
gst_audio_cd_src_get_track_from_sector(GstAudioCdSrc * src,gint sector)454 gst_audio_cd_src_get_track_from_sector (GstAudioCdSrc * src, gint sector)
455 {
456   gint i;
457 
458   for (i = 0; i < src->priv->num_tracks; ++i) {
459     if (sector >= src->priv->tracks[i].start
460         && sector <= src->priv->tracks[i].end)
461       return i;
462   }
463   return -1;
464 }
465 
466 static gboolean
gst_audio_cd_src_convert(GstAudioCdSrc * src,GstFormat src_format,gint64 src_val,GstFormat dest_format,gint64 * dest_val)467 gst_audio_cd_src_convert (GstAudioCdSrc * src, GstFormat src_format,
468     gint64 src_val, GstFormat dest_format, gint64 * dest_val)
469 {
470   gboolean started;
471 
472   GST_LOG_OBJECT (src, "converting value %" G_GINT64_FORMAT " from %s into %s",
473       src_val, gst_format_get_name (src_format),
474       gst_format_get_name (dest_format));
475 
476   if (src_format == dest_format) {
477     *dest_val = src_val;
478     return TRUE;
479   }
480 
481   started =
482       GST_OBJECT_FLAG_IS_SET (GST_BASE_SRC (src), GST_BASE_SRC_FLAG_STARTED);
483 
484   if (src_format == track_format) {
485     if (!started)
486       goto not_started;
487     if (src_val < 0 || src_val >= src->priv->num_tracks) {
488       GST_DEBUG_OBJECT (src, "track number %d out of bounds", (gint) src_val);
489       goto wrong_value;
490     }
491     src_format = GST_FORMAT_DEFAULT;
492     src_val = src->priv->tracks[src_val].start * (gint64) SAMPLES_PER_SECTOR;
493   } else if (src_format == sector_format) {
494     src_format = GST_FORMAT_DEFAULT;
495     src_val = src_val * SAMPLES_PER_SECTOR;
496   }
497 
498   if (src_format == dest_format) {
499     *dest_val = src_val;
500     goto done;
501   }
502 
503   switch (src_format) {
504     case GST_FORMAT_BYTES:
505       /* convert to samples (4 bytes per sample) */
506       src_val = src_val >> 2;
507       /* fallthrough */
508     case GST_FORMAT_DEFAULT:{
509       switch (dest_format) {
510         case GST_FORMAT_BYTES:{
511           if (src_val < 0) {
512             GST_DEBUG_OBJECT (src, "sample source value negative");
513             goto wrong_value;
514           }
515           *dest_val = src_val << 2;     /* 4 bytes per sample */
516           break;
517         }
518         case GST_FORMAT_TIME:{
519           *dest_val = gst_util_uint64_scale_int (src_val, GST_SECOND, 44100);
520           break;
521         }
522         default:{
523           gint64 sector = src_val / SAMPLES_PER_SECTOR;
524 
525           if (dest_format == sector_format) {
526             *dest_val = sector;
527           } else if (dest_format == track_format) {
528             if (!started)
529               goto not_started;
530             *dest_val = gst_audio_cd_src_get_track_from_sector (src, sector);
531           } else {
532             goto unknown_format;
533           }
534           break;
535         }
536       }
537       break;
538     }
539     case GST_FORMAT_TIME:{
540       gint64 sample_offset;
541 
542       if (src_val == GST_CLOCK_TIME_NONE) {
543         GST_DEBUG_OBJECT (src, "source time value invalid");
544         goto wrong_value;
545       }
546 
547       sample_offset = gst_util_uint64_scale_int (src_val, 44100, GST_SECOND);
548       switch (dest_format) {
549         case GST_FORMAT_BYTES:{
550           *dest_val = sample_offset << 2;       /* 4 bytes per sample */
551           break;
552         }
553         case GST_FORMAT_DEFAULT:{
554           *dest_val = sample_offset;
555           break;
556         }
557         default:{
558           gint64 sector = sample_offset / SAMPLES_PER_SECTOR;
559 
560           if (dest_format == sector_format) {
561             *dest_val = sector;
562           } else if (dest_format == track_format) {
563             if (!started)
564               goto not_started;
565             *dest_val = gst_audio_cd_src_get_track_from_sector (src, sector);
566           } else {
567             goto unknown_format;
568           }
569           break;
570         }
571       }
572       break;
573     }
574     default:{
575       goto unknown_format;
576     }
577   }
578 
579 done:
580   {
581     GST_LOG_OBJECT (src, "returning %" G_GINT64_FORMAT, *dest_val);
582     return TRUE;
583   }
584 
585 unknown_format:
586   {
587     GST_DEBUG_OBJECT (src, "conversion failed: %s", "unsupported format");
588     return FALSE;
589   }
590 
591 wrong_value:
592   {
593     GST_DEBUG_OBJECT (src, "conversion failed: %s",
594         "source value not within allowed range");
595     return FALSE;
596   }
597 
598 not_started:
599   {
600     GST_DEBUG_OBJECT (src, "conversion failed: %s",
601         "cannot do this conversion, device not open");
602     return FALSE;
603   }
604 }
605 
606 static gboolean
gst_audio_cd_src_query(GstBaseSrc * basesrc,GstQuery * query)607 gst_audio_cd_src_query (GstBaseSrc * basesrc, GstQuery * query)
608 {
609   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (basesrc);
610   gboolean started;
611 
612   started = GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_FLAG_STARTED);
613 
614   GST_LOG_OBJECT (src, "handling %s query",
615       gst_query_type_get_name (GST_QUERY_TYPE (query)));
616 
617   switch (GST_QUERY_TYPE (query)) {
618     case GST_QUERY_DURATION:{
619       GstFormat dest_format;
620       gint64 dest_val;
621       guint sectors;
622 
623       gst_query_parse_duration (query, &dest_format, NULL);
624 
625       if (!started)
626         return FALSE;
627 
628       g_assert (src->priv->tracks != NULL);
629 
630       if (dest_format == track_format) {
631         GST_LOG_OBJECT (src, "duration: %d tracks", src->priv->num_tracks);
632         gst_query_set_duration (query, track_format, src->priv->num_tracks);
633         return TRUE;
634       }
635 
636       if (src->priv->cur_track < 0
637           || src->priv->cur_track >= src->priv->num_tracks)
638         return FALSE;
639 
640       if (src->priv->mode == GST_AUDIO_CD_SRC_MODE_NORMAL) {
641         sectors = src->priv->tracks[src->priv->cur_track].end -
642             src->priv->tracks[src->priv->cur_track].start + 1;
643       } else {
644         sectors = src->priv->tracks[src->priv->num_tracks - 1].end -
645             src->priv->tracks[0].start + 1;
646       }
647 
648       /* ... and convert into final format */
649       if (!gst_audio_cd_src_convert (src, sector_format, sectors,
650               dest_format, &dest_val)) {
651         return FALSE;
652       }
653 
654       gst_query_set_duration (query, dest_format, dest_val);
655 
656       GST_LOG ("duration: %u sectors, %" G_GINT64_FORMAT " in format %s",
657           sectors, dest_val, gst_format_get_name (dest_format));
658       break;
659     }
660     case GST_QUERY_POSITION:{
661       GstFormat dest_format;
662       gint64 pos_sector;
663       gint64 dest_val;
664 
665       gst_query_parse_position (query, &dest_format, NULL);
666 
667       if (!started)
668         return FALSE;
669 
670       g_assert (src->priv->tracks != NULL);
671 
672       if (dest_format == track_format) {
673         GST_LOG_OBJECT (src, "position: track %d", src->priv->cur_track);
674         gst_query_set_position (query, track_format, src->priv->cur_track);
675         return TRUE;
676       }
677 
678       if (src->priv->cur_track < 0
679           || src->priv->cur_track >= src->priv->num_tracks)
680         return FALSE;
681 
682       if (src->priv->mode == GST_AUDIO_CD_SRC_MODE_NORMAL) {
683         pos_sector =
684             src->priv->cur_sector -
685             src->priv->tracks[src->priv->cur_track].start;
686       } else {
687         pos_sector = src->priv->cur_sector - src->priv->tracks[0].start;
688       }
689 
690       if (!gst_audio_cd_src_convert (src, sector_format, pos_sector,
691               dest_format, &dest_val)) {
692         return FALSE;
693       }
694 
695       gst_query_set_position (query, dest_format, dest_val);
696 
697       GST_LOG ("position: sector %u, %" G_GINT64_FORMAT " in format %s",
698           (guint) pos_sector, dest_val, gst_format_get_name (dest_format));
699       break;
700     }
701     case GST_QUERY_CONVERT:{
702       GstFormat src_format, dest_format;
703       gint64 src_val, dest_val;
704 
705       gst_query_parse_convert (query, &src_format, &src_val, &dest_format,
706           NULL);
707 
708       if (!gst_audio_cd_src_convert (src, src_format, src_val, dest_format,
709               &dest_val)) {
710         return FALSE;
711       }
712 
713       gst_query_set_convert (query, src_format, src_val, dest_format, dest_val);
714       break;
715     }
716     default:{
717       GST_DEBUG_OBJECT (src, "unhandled query, chaining up to parent class");
718       return GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
719     }
720   }
721 
722   return TRUE;
723 }
724 
725 static gboolean
gst_audio_cd_src_is_seekable(GstBaseSrc * basesrc)726 gst_audio_cd_src_is_seekable (GstBaseSrc * basesrc)
727 {
728   return TRUE;
729 }
730 
731 static gboolean
gst_audio_cd_src_do_seek(GstBaseSrc * basesrc,GstSegment * segment)732 gst_audio_cd_src_do_seek (GstBaseSrc * basesrc, GstSegment * segment)
733 {
734   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (basesrc);
735   gint64 seek_sector;
736 
737   GST_DEBUG_OBJECT (src, "segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT,
738       GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop));
739 
740   if (!gst_audio_cd_src_convert (src, GST_FORMAT_TIME, segment->start,
741           sector_format, &seek_sector)) {
742     GST_WARNING_OBJECT (src, "conversion failed");
743     return FALSE;
744   }
745 
746   /* we should only really be called when open */
747   g_assert (src->priv->cur_track >= 0
748       && src->priv->cur_track < src->priv->num_tracks);
749 
750   switch (src->priv->mode) {
751     case GST_AUDIO_CD_SRC_MODE_NORMAL:
752       seek_sector += src->priv->tracks[src->priv->cur_track].start;
753       break;
754     case GST_AUDIO_CD_SRC_MODE_CONTINUOUS:
755       seek_sector += src->priv->tracks[0].start;
756       break;
757     default:
758       g_return_val_if_reached (FALSE);
759   }
760 
761   src->priv->cur_sector = (gint) seek_sector;
762 
763   GST_DEBUG_OBJECT (src, "seek'd to sector %d", src->priv->cur_sector);
764 
765   return TRUE;
766 }
767 
768 static gboolean
gst_audio_cd_src_handle_track_seek(GstAudioCdSrc * src,gdouble rate,GstSeekFlags flags,GstSeekType start_type,gint64 start,GstSeekType stop_type,gint64 stop)769 gst_audio_cd_src_handle_track_seek (GstAudioCdSrc * src, gdouble rate,
770     GstSeekFlags flags, GstSeekType start_type, gint64 start,
771     GstSeekType stop_type, gint64 stop)
772 {
773   GstBaseSrc *basesrc = GST_BASE_SRC (src);
774   GstEvent *event;
775 
776   if ((flags & GST_SEEK_FLAG_SEGMENT) == GST_SEEK_FLAG_SEGMENT) {
777     gint64 start_time = -1;
778     gint64 stop_time = -1;
779 
780     if (src->priv->mode != GST_AUDIO_CD_SRC_MODE_CONTINUOUS) {
781       GST_DEBUG_OBJECT (src, "segment seek in track format is only "
782           "supported in CONTINUOUS mode, not in mode %d", src->priv->mode);
783       return FALSE;
784     }
785 
786     switch (start_type) {
787       case GST_SEEK_TYPE_SET:
788         if (!gst_audio_cd_src_convert (src, track_format, start,
789                 GST_FORMAT_TIME, &start_time)) {
790           GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
791               (gint) start);
792           return FALSE;
793         }
794         break;
795       case GST_SEEK_TYPE_END:
796         if (!gst_audio_cd_src_convert (src, track_format,
797                 src->priv->num_tracks - start - 1, GST_FORMAT_TIME,
798                 &start_time)) {
799           GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
800               (gint) start);
801           return FALSE;
802         }
803         start_type = GST_SEEK_TYPE_SET;
804         break;
805       case GST_SEEK_TYPE_NONE:
806         start_time = -1;
807         break;
808       default:
809         g_return_val_if_reached (FALSE);
810     }
811 
812     switch (stop_type) {
813       case GST_SEEK_TYPE_SET:
814         if (!gst_audio_cd_src_convert (src, track_format, stop,
815                 GST_FORMAT_TIME, &stop_time)) {
816           GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
817               (gint) stop);
818           return FALSE;
819         }
820         break;
821       case GST_SEEK_TYPE_END:
822         if (!gst_audio_cd_src_convert (src, track_format,
823                 src->priv->num_tracks - stop - 1, GST_FORMAT_TIME,
824                 &stop_time)) {
825           GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
826               (gint) stop);
827           return FALSE;
828         }
829         stop_type = GST_SEEK_TYPE_SET;
830         break;
831       case GST_SEEK_TYPE_NONE:
832         stop_time = -1;
833         break;
834       default:
835         g_return_val_if_reached (FALSE);
836     }
837 
838     GST_LOG_OBJECT (src, "seek segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT,
839         GST_TIME_ARGS (start_time), GST_TIME_ARGS (stop_time));
840 
841     /* send fake segment seek event in TIME format to
842      * base class, which will hopefully handle the rest */
843 
844     event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type,
845         start_time, stop_type, stop_time);
846 
847     return GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
848   }
849 
850   /* not a segment seek */
851 
852   if (start_type == GST_SEEK_TYPE_NONE) {
853     GST_LOG_OBJECT (src, "start seek type is NONE, nothing to do");
854     return TRUE;
855   }
856 
857   if (stop_type != GST_SEEK_TYPE_NONE) {
858     GST_WARNING_OBJECT (src, "ignoring stop seek type (expected NONE)");
859   }
860 
861   if (start < 0 || start >= src->priv->num_tracks) {
862     GST_DEBUG_OBJECT (src, "invalid track %" G_GINT64_FORMAT, start);
863     return FALSE;
864   }
865 
866   GST_DEBUG_OBJECT (src, "seeking to track %" G_GINT64_FORMAT, start + 1);
867 
868   src->priv->cur_sector = src->priv->tracks[start].start;
869   GST_DEBUG_OBJECT (src, "starting at sector %d", src->priv->cur_sector);
870 
871   if (src->priv->cur_track != start) {
872     src->priv->cur_track = (gint) start;
873     src->priv->uri_track = -1;
874     src->priv->prev_track = -1;
875 
876     gst_audio_cd_src_update_duration (src);
877   } else {
878     GST_DEBUG_OBJECT (src, "is current track, just seeking back to start");
879   }
880 
881   /* send fake segment seek event in TIME format to
882    * base class (so we get a newsegment etc.) */
883   event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
884       GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1);
885 
886   return GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
887 }
888 
889 static gboolean
gst_audio_cd_src_handle_event(GstBaseSrc * basesrc,GstEvent * event)890 gst_audio_cd_src_handle_event (GstBaseSrc * basesrc, GstEvent * event)
891 {
892   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (basesrc);
893   gboolean ret = FALSE;
894 
895   GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event));
896 
897   switch (GST_EVENT_TYPE (event)) {
898     case GST_EVENT_SEEK:{
899       GstSeekType start_type, stop_type;
900       GstSeekFlags flags;
901       GstFormat format;
902       gdouble rate;
903       gint64 start, stop;
904 
905       if (!GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_FLAG_STARTED)) {
906         GST_DEBUG_OBJECT (src, "seek failed: device not open");
907         break;
908       }
909 
910       gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
911           &stop_type, &stop);
912 
913       if (format == sector_format) {
914         GST_DEBUG_OBJECT (src, "seek in sector format not supported");
915         break;
916       }
917 
918       if (format == track_format) {
919         ret = gst_audio_cd_src_handle_track_seek (src, rate, flags,
920             start_type, start, stop_type, stop);
921       } else {
922         GST_LOG_OBJECT (src, "let base class handle seek in %s format",
923             gst_format_get_name (format));
924         event = gst_event_ref (event);
925         ret = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
926       }
927       break;
928     }
929     case GST_EVENT_TOC_SELECT:{
930       guint track_num = 0;
931       gchar *uid = NULL;
932 
933       gst_event_parse_toc_select (event, &uid);
934       if (uid != NULL && sscanf (uid, "audiocd-track-%03u", &track_num) == 1) {
935         ret = gst_audio_cd_src_handle_track_seek (src, 1.0, GST_SEEK_FLAG_FLUSH,
936             GST_SEEK_TYPE_SET, track_num, GST_SEEK_TYPE_NONE, -1);
937       }
938       g_free (uid);
939       break;
940     }
941     default:{
942       GST_LOG_OBJECT (src, "let base class handle event");
943       ret = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
944       break;
945     }
946   }
947 
948   return ret;
949 }
950 
951 static GstURIType
gst_audio_cd_src_uri_get_type(GType type)952 gst_audio_cd_src_uri_get_type (GType type)
953 {
954   return GST_URI_SRC;
955 }
956 
957 static const gchar *const *
gst_audio_cd_src_uri_get_protocols(GType type)958 gst_audio_cd_src_uri_get_protocols (GType type)
959 {
960   static const gchar *protocols[] = { "cdda", NULL };
961 
962   return protocols;
963 }
964 
965 static gchar *
gst_audio_cd_src_uri_get_uri(GstURIHandler * handler)966 gst_audio_cd_src_uri_get_uri (GstURIHandler * handler)
967 {
968   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (handler);
969 
970   GST_OBJECT_LOCK (src);
971 
972   /* FIXME: can we get rid of all that here and just return a copy of the
973    * existing URI perhaps? */
974   g_free (src->priv->uri);
975 
976   if (GST_OBJECT_FLAG_IS_SET (GST_BASE_SRC (src), GST_BASE_SRC_FLAG_STARTED)) {
977     src->priv->uri =
978         g_strdup_printf ("cdda://%s#%d", src->priv->device,
979         (src->priv->uri_track > 0) ? src->priv->uri_track : 1);
980   } else {
981     src->priv->uri = g_strdup ("cdda://1");
982   }
983 
984   GST_OBJECT_UNLOCK (src);
985 
986   return g_strdup (src->priv->uri);
987 }
988 
989 /* Note: gst_element_make_from_uri() might call us with just 'cdda://' as
990  * URI and expects us to return TRUE then (and this might be in any state) */
991 
992 /* We accept URIs of the format cdda://(device#track)|(track) */
993 
994 static gboolean
gst_audio_cd_src_uri_set_uri(GstURIHandler * handler,const gchar * uri,GError ** error)995 gst_audio_cd_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
996     GError ** error)
997 {
998   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (handler);
999   const gchar *location;
1000   gchar *track_number;
1001 
1002   GST_OBJECT_LOCK (src);
1003 
1004   location = uri + 7;
1005   track_number = g_strrstr (location, "#");
1006   src->priv->uri_track = 0;
1007   /* FIXME 0.11: ignore URI fragments that look like device paths for
1008    * the benefit of rhythmbox and possibly other applications.
1009    */
1010   if (track_number && track_number[1] != '/') {
1011     gchar *device, *nuri = g_strdup (uri);
1012 
1013     track_number = nuri + (track_number - uri);
1014     *track_number = '\0';
1015     device = gst_uri_get_location (nuri);
1016     gst_audio_cd_src_set_device (src, device);
1017     g_free (device);
1018     src->priv->uri_track = strtol (track_number + 1, NULL, 10);
1019     g_free (nuri);
1020   } else {
1021     if (*location == '\0')
1022       src->priv->uri_track = 1;
1023     else
1024       src->priv->uri_track = strtol (location, NULL, 10);
1025   }
1026 
1027   if (src->priv->uri_track < 1)
1028     goto failed;
1029 
1030   if (src->priv->num_tracks > 0
1031       && src->priv->tracks != NULL
1032       && src->priv->uri_track > src->priv->num_tracks)
1033     goto failed;
1034 
1035   if (src->priv->uri_track > 0 && src->priv->tracks != NULL) {
1036     GST_OBJECT_UNLOCK (src);
1037 
1038     gst_pad_send_event (GST_BASE_SRC_PAD (src),
1039         gst_event_new_seek (1.0, track_format, GST_SEEK_FLAG_FLUSH,
1040             GST_SEEK_TYPE_SET, src->priv->uri_track - 1, GST_SEEK_TYPE_NONE,
1041             -1));
1042   } else {
1043     /* seek will be done in start() */
1044     GST_OBJECT_UNLOCK (src);
1045   }
1046 
1047   GST_LOG_OBJECT (handler, "successfully handled uri '%s'", uri);
1048 
1049   return TRUE;
1050 
1051 failed:
1052   {
1053     GST_OBJECT_UNLOCK (src);
1054     GST_DEBUG_OBJECT (src, "cannot handle URI '%s'", uri);
1055     g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
1056         "Could not handle CDDA URI");
1057     return FALSE;
1058   }
1059 }
1060 
1061 static void
gst_audio_cd_src_uri_handler_init(gpointer g_iface,gpointer iface_data)1062 gst_audio_cd_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1063 {
1064   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1065 
1066   iface->get_type = gst_audio_cd_src_uri_get_type;
1067   iface->get_uri = gst_audio_cd_src_uri_get_uri;
1068   iface->set_uri = gst_audio_cd_src_uri_set_uri;
1069   iface->get_protocols = gst_audio_cd_src_uri_get_protocols;
1070 }
1071 
1072 /**
1073  * gst_audio_cd_src_add_track:
1074  * @src: a #GstAudioCdSrc
1075  * @track: address of #GstAudioCdSrcTrack to add
1076  *
1077  * CDDA sources use this function from their start vfunc to announce the
1078  * available data and audio tracks to the base source class. The caller
1079  * should allocate @track on the stack, the base source will do a shallow
1080  * copy of the structure (and take ownership of the taglist if there is one).
1081  *
1082  * Returns: FALSE on error, otherwise TRUE.
1083  */
1084 
1085 gboolean
gst_audio_cd_src_add_track(GstAudioCdSrc * src,GstAudioCdSrcTrack * track)1086 gst_audio_cd_src_add_track (GstAudioCdSrc * src, GstAudioCdSrcTrack * track)
1087 {
1088   g_return_val_if_fail (GST_IS_AUDIO_CD_SRC (src), FALSE);
1089   g_return_val_if_fail (track != NULL, FALSE);
1090   g_return_val_if_fail (track->num > 0, FALSE);
1091 
1092   GST_DEBUG_OBJECT (src, "adding track %2u (%2u) [%6u-%6u] [%5s], tags: %"
1093       GST_PTR_FORMAT, src->priv->num_tracks + 1, track->num, track->start,
1094       track->end, (track->is_audio) ? "AUDIO" : "DATA ", track->tags);
1095 
1096   if (src->priv->num_tracks > 0) {
1097     guint end_of_previous_track =
1098         src->priv->tracks[src->priv->num_tracks - 1].end;
1099 
1100     if (track->start <= end_of_previous_track) {
1101       GST_WARNING ("track %2u overlaps with previous tracks", track->num);
1102       return FALSE;
1103     }
1104   }
1105 
1106   GST_OBJECT_LOCK (src);
1107 
1108   ++src->priv->num_tracks;
1109   src->priv->tracks =
1110       g_renew (GstAudioCdSrcTrack, src->priv->tracks, src->priv->num_tracks);
1111   src->priv->tracks[src->priv->num_tracks - 1] = *track;
1112 
1113   GST_OBJECT_UNLOCK (src);
1114 
1115   return TRUE;
1116 }
1117 
1118 static void
gst_audio_cd_src_update_duration(GstAudioCdSrc * src)1119 gst_audio_cd_src_update_duration (GstAudioCdSrc * src)
1120 {
1121   GstBaseSrc *basesrc;
1122   gint64 dur;
1123 
1124   basesrc = GST_BASE_SRC (src);
1125 
1126   if (!gst_pad_query_duration (GST_BASE_SRC_PAD (src), GST_FORMAT_TIME, &dur)) {
1127     dur = GST_CLOCK_TIME_NONE;
1128   }
1129   basesrc->segment.duration = dur;
1130 
1131   gst_element_post_message (GST_ELEMENT (src),
1132       gst_message_new_duration_changed (GST_OBJECT (src)));
1133 
1134   GST_LOG_OBJECT (src, "duration updated to %" GST_TIME_FORMAT,
1135       GST_TIME_ARGS (dur));
1136 }
1137 
1138 #define CD_MSF_OFFSET 150
1139 
1140 /* the cddb hash function */
1141 static guint
cddb_sum(gint n)1142 cddb_sum (gint n)
1143 {
1144   guint ret;
1145 
1146   ret = 0;
1147   while (n > 0) {
1148     ret += (n % 10);
1149     n /= 10;
1150   }
1151   return ret;
1152 }
1153 
1154 static void
gst_audio_cd_src_calculate_musicbrainz_discid(GstAudioCdSrc * src)1155 gst_audio_cd_src_calculate_musicbrainz_discid (GstAudioCdSrc * src)
1156 {
1157   GString *s;
1158   GChecksum *sha;
1159   guchar digest[20];
1160   gchar *ptr;
1161   gchar tmp[9];
1162   gulong i;
1163   unsigned int last_audio_track;
1164   guint leadout_sector;
1165   gsize digest_len;
1166 
1167   s = g_string_new (NULL);
1168 
1169   /* MusicBrainz doesn't consider trailing data tracks
1170    * data tracks up front stay, since the disc has to start with 1 */
1171   last_audio_track = 0;
1172   for (i = 0; i < src->priv->num_tracks; i++) {
1173     if (src->priv->tracks[i].is_audio) {
1174       last_audio_track = src->priv->tracks[i].num;
1175     }
1176   }
1177 
1178   leadout_sector =
1179       src->priv->tracks[last_audio_track - 1].end + 1 + CD_MSF_OFFSET;
1180 
1181   /* generate SHA digest */
1182   sha = g_checksum_new (G_CHECKSUM_SHA1);
1183   g_snprintf (tmp, sizeof (tmp), "%02X", src->priv->tracks[0].num);
1184   g_string_append_printf (s, "%02X", src->priv->tracks[0].num);
1185   g_checksum_update (sha, (guchar *) tmp, 2);
1186 
1187   g_snprintf (tmp, sizeof (tmp), "%02X", last_audio_track);
1188   g_string_append_printf (s, " %02X", last_audio_track);
1189   g_checksum_update (sha, (guchar *) tmp, 2);
1190 
1191   g_snprintf (tmp, sizeof (tmp), "%08X", leadout_sector);
1192   g_string_append_printf (s, " %08X", leadout_sector);
1193   g_checksum_update (sha, (guchar *) tmp, 8);
1194 
1195   for (i = 0; i < 99; i++) {
1196     if (i < last_audio_track) {
1197       guint frame_offset = src->priv->tracks[i].start + CD_MSF_OFFSET;
1198 
1199       g_snprintf (tmp, sizeof (tmp), "%08X", frame_offset);
1200       g_string_append_printf (s, " %08X", frame_offset);
1201       g_checksum_update (sha, (guchar *) tmp, 8);
1202     } else {
1203       g_checksum_update (sha, (guchar *) "00000000", 8);
1204     }
1205   }
1206   digest_len = 20;
1207   g_checksum_get_digest (sha, (guint8 *) & digest, &digest_len);
1208 
1209   /* re-encode to base64 */
1210   ptr = g_base64_encode (digest, digest_len);
1211   g_checksum_free (sha);
1212   i = strlen (ptr);
1213 
1214   g_assert (i < sizeof (src->priv->mb_discid) + 1);
1215   memcpy (src->priv->mb_discid, ptr, i);
1216   src->priv->mb_discid[i] = '\0';
1217   free (ptr);
1218 
1219   /* Replace '/', '+' and '=' by '_', '.' and '-' as specified on
1220    * http://musicbrainz.org/doc/DiscIDCalculation
1221    */
1222   for (ptr = src->priv->mb_discid; *ptr != '\0'; ptr++) {
1223     if (*ptr == '/')
1224       *ptr = '_';
1225     else if (*ptr == '+')
1226       *ptr = '.';
1227     else if (*ptr == '=')
1228       *ptr = '-';
1229   }
1230 
1231   GST_DEBUG_OBJECT (src, "musicbrainz-discid      = %s", src->priv->mb_discid);
1232   GST_DEBUG_OBJECT (src, "musicbrainz-discid-full = %s", s->str);
1233 
1234   gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
1235       GST_TAG_CDDA_MUSICBRAINZ_DISCID, src->priv->mb_discid,
1236       GST_TAG_CDDA_MUSICBRAINZ_DISCID_FULL, s->str, NULL);
1237 
1238   g_string_free (s, TRUE);
1239 }
1240 
1241 static void
lba_to_msf(guint sector,guint * p_m,guint * p_s,guint * p_f,guint * p_secs)1242 lba_to_msf (guint sector, guint * p_m, guint * p_s, guint * p_f, guint * p_secs)
1243 {
1244   guint m, s, f;
1245 
1246   m = sector / SECTORS_PER_MINUTE;
1247   sector = sector % SECTORS_PER_MINUTE;
1248   s = sector / SECTORS_PER_SECOND;
1249   f = sector % SECTORS_PER_SECOND;
1250 
1251   if (p_m)
1252     *p_m = m;
1253   if (p_s)
1254     *p_s = s;
1255   if (p_f)
1256     *p_f = f;
1257   if (p_secs)
1258     *p_secs = s + (m * 60);
1259 }
1260 
1261 static void
gst_audio_cd_src_calculate_cddb_id(GstAudioCdSrc * src)1262 gst_audio_cd_src_calculate_cddb_id (GstAudioCdSrc * src)
1263 {
1264   GString *s;
1265   guint first_sector = 0, last_sector = 0;
1266   guint start_secs, end_secs, secs, len_secs;
1267   guint total_secs, num_audio_tracks;
1268   guint id, t, i;
1269 
1270   id = 0;
1271   total_secs = 0;
1272   num_audio_tracks = 0;
1273 
1274   /* FIXME: do we use offsets and duration of ALL tracks (data + audio)
1275    * for the CDDB ID calculation, or only audio tracks? */
1276   for (i = 0; i < src->priv->num_tracks; ++i) {
1277     if (1) {                    /* src->priv->tracks[i].is_audio) { */
1278       if (num_audio_tracks == 0) {
1279         first_sector = src->priv->tracks[i].start + CD_MSF_OFFSET;
1280       }
1281       last_sector = src->priv->tracks[i].end + CD_MSF_OFFSET + 1;
1282       ++num_audio_tracks;
1283 
1284       lba_to_msf (src->priv->tracks[i].start + CD_MSF_OFFSET, NULL, NULL, NULL,
1285           &secs);
1286 
1287       len_secs =
1288           (src->priv->tracks[i].end - src->priv->tracks[i].start + 1) / 75;
1289 
1290       GST_DEBUG_OBJECT (src, "track %02u: lsn %6u (%02u:%02u), "
1291           "length: %u seconds (%02u:%02u)",
1292           num_audio_tracks, src->priv->tracks[i].start + CD_MSF_OFFSET,
1293           secs / 60, secs % 60, len_secs, len_secs / 60, len_secs % 60);
1294 
1295       id += cddb_sum (secs);
1296       total_secs += len_secs;
1297     }
1298   }
1299 
1300   /* first_sector = src->priv->tracks[0].start + CD_MSF_OFFSET; */
1301   lba_to_msf (first_sector, NULL, NULL, NULL, &start_secs);
1302 
1303   /* last_sector = src->priv->tracks[src->priv->num_tracks-1].end + CD_MSF_OFFSET; */
1304   lba_to_msf (last_sector, NULL, NULL, NULL, &end_secs);
1305 
1306   GST_DEBUG_OBJECT (src, "first_sector = %u = %u secs (%02u:%02u)",
1307       first_sector, start_secs, start_secs / 60, start_secs % 60);
1308   GST_DEBUG_OBJECT (src, "last_sector  = %u = %u secs (%02u:%02u)",
1309       last_sector, end_secs, end_secs / 60, end_secs % 60);
1310 
1311   t = end_secs - start_secs;
1312 
1313   GST_DEBUG_OBJECT (src, "total length = %u secs (%02u:%02u), added title "
1314       "lengths = %u seconds (%02u:%02u)", t, t / 60, t % 60, total_secs,
1315       total_secs / 60, total_secs % 60);
1316 
1317   src->priv->discid = ((id % 0xff) << 24 | t << 8 | num_audio_tracks);
1318 
1319   s = g_string_new (NULL);
1320   g_string_append_printf (s, "%08x", src->priv->discid);
1321 
1322   gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
1323       GST_TAG_CDDA_CDDB_DISCID, s->str, NULL);
1324 
1325   g_string_append_printf (s, " %u", src->priv->num_tracks);
1326   for (i = 0; i < src->priv->num_tracks; ++i) {
1327     g_string_append_printf (s, " %u",
1328         src->priv->tracks[i].start + CD_MSF_OFFSET);
1329   }
1330   g_string_append_printf (s, " %u", t);
1331 
1332   gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
1333       GST_TAG_CDDA_CDDB_DISCID_FULL, s->str, NULL);
1334 
1335   GST_DEBUG_OBJECT (src, "cddb discid = %s", s->str);
1336 
1337   g_string_free (s, TRUE);
1338 }
1339 
1340 static void
gst_audio_cd_src_add_tags(GstAudioCdSrc * src)1341 gst_audio_cd_src_add_tags (GstAudioCdSrc * src)
1342 {
1343   gint i;
1344 
1345   /* fill in details for each track */
1346   for (i = 0; i < src->priv->num_tracks; ++i) {
1347     gint64 duration;
1348     guint num_sectors;
1349 
1350     if (src->priv->tracks[i].tags == NULL)
1351       src->priv->tracks[i].tags = gst_tag_list_new_empty ();
1352 
1353     num_sectors = src->priv->tracks[i].end - src->priv->tracks[i].start + 1;
1354     gst_audio_cd_src_convert (src, sector_format, num_sectors,
1355         GST_FORMAT_TIME, &duration);
1356 
1357     gst_tag_list_add (src->priv->tracks[i].tags,
1358         GST_TAG_MERGE_REPLACE,
1359         GST_TAG_TRACK_NUMBER, i + 1,
1360         GST_TAG_TRACK_COUNT, src->priv->num_tracks, GST_TAG_DURATION, duration,
1361         NULL);
1362   }
1363 
1364   /* now fill in per-album tags and include each track's tags
1365    * in the album tags, so that interested parties can retrieve
1366    * the relevant details for each track in one go */
1367 
1368   /* /////////////////////////////// FIXME should we rather insert num_tracks
1369    * tags by the name of 'track-tags' and have the caller use
1370    * gst_tag_list_get_value_index() rather than use tag names incl.
1371    * the track number ?? *////////////////////////////////////////
1372 
1373   gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
1374       GST_TAG_TRACK_COUNT, src->priv->num_tracks, NULL);
1375 #if 0
1376   for (i = 0; i < src->priv->num_tracks; ++i) {
1377     gst_tag_list_add (src->tags, GST_TAG_MERGE_APPEND,
1378         GST_TAG_CDDA_TRACK_TAGS, src->priv->tracks[i].tags, NULL);
1379   }
1380 #endif
1381 
1382   GST_DEBUG ("src->tags = %" GST_PTR_FORMAT, src->tags);
1383 }
1384 
1385 static GstToc *
gst_audio_cd_src_make_toc(GstAudioCdSrc * src,GstTocScope scope)1386 gst_audio_cd_src_make_toc (GstAudioCdSrc * src, GstTocScope scope)
1387 {
1388   GstToc *toc;
1389   gint i;
1390 
1391   toc = gst_toc_new (scope);
1392 
1393   for (i = 0; i < src->priv->num_tracks; ++i) {
1394     GstAudioCdSrcTrack *track;
1395     gint64 start_time, stop_time;
1396     GstTocEntry *entry;
1397     gchar *uid;
1398 
1399     track = &src->priv->tracks[i];
1400 
1401     /* keep uid in sync with toc select event handler below */
1402     uid = g_strdup_printf ("audiocd-track-%03u", track->num);
1403     entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_TRACK, uid);
1404     gst_toc_entry_set_tags (entry, gst_tag_list_ref (track->tags));
1405 
1406     gst_audio_cd_src_convert (src, sector_format, track->start,
1407         GST_FORMAT_TIME, &start_time);
1408     gst_audio_cd_src_convert (src, sector_format, track->end + 1,
1409         GST_FORMAT_TIME, &stop_time);
1410 
1411     GST_INFO ("Track %03u  %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
1412         track->num, GST_TIME_ARGS (start_time), GST_TIME_ARGS (stop_time));
1413 
1414     gst_toc_entry_set_start_stop_times (entry, start_time, stop_time);
1415     gst_toc_append_entry (toc, entry);
1416     g_free (uid);
1417   }
1418 
1419   return toc;
1420 }
1421 
1422 static void
gst_audio_cd_src_add_toc(GstAudioCdSrc * src)1423 gst_audio_cd_src_add_toc (GstAudioCdSrc * src)
1424 {
1425   GstToc *toc;
1426 
1427   /* FIXME: send two TOC events if needed, one global, one current */
1428   toc = gst_audio_cd_src_make_toc (src, GST_TOC_SCOPE_GLOBAL);
1429 
1430   src->priv->toc_event = gst_event_new_toc (toc, FALSE);
1431 
1432   /* If we're in continuous mode (stream = whole disc), send a TOC event
1433    * downstream, so matroskamux etc. can write a TOC to indicate where the
1434    * various tracks are */
1435   if (src->priv->mode == GST_AUDIO_CD_SRC_MODE_CONTINUOUS)
1436     src->priv->toc_event = gst_event_new_toc (toc, FALSE);
1437 
1438   src->priv->toc = toc;
1439 }
1440 
1441 #if 0
1442 static void
1443 gst_audio_cd_src_add_index_associations (GstAudioCdSrc * src)
1444 {
1445   gint i;
1446 
1447   for (i = 0; i < src->priv->num_tracks; i++) {
1448     gint64 sector;
1449 
1450     sector = src->priv->tracks[i].start;
1451     gst_index_add_association (src->priv->index, src->priv->index_id, GST_ASSOCIATION_FLAG_KEY_UNIT, track_format, i,   /* here we count from 0 */
1452         sector_format, sector,
1453         GST_FORMAT_TIME,
1454         (gint64) (((CD_FRAMESIZE_RAW >> 2) * sector * GST_SECOND) / 44100),
1455         GST_FORMAT_BYTES, (gint64) (sector << 2), GST_FORMAT_DEFAULT,
1456         (gint64) ((CD_FRAMESIZE_RAW >> 2) * sector), NULL);
1457   }
1458 }
1459 
1460 static void
1461 gst_audio_cd_src_set_index (GstElement * element, GstIndex * index)
1462 {
1463   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (element);
1464   GstIndex *old;
1465 
1466   GST_OBJECT_LOCK (element);
1467   old = src->priv->index;
1468   if (old == index) {
1469     GST_OBJECT_UNLOCK (element);
1470     return;
1471   }
1472   if (index)
1473     gst_object_ref (index);
1474   src->priv->index = index;
1475   GST_OBJECT_UNLOCK (element);
1476   if (old)
1477     gst_object_unref (old);
1478 
1479   if (index) {
1480     gst_index_get_writer_id (index, GST_OBJECT (src), &src->priv->index_id);
1481     gst_index_add_format (index, src->priv->index_id, track_format);
1482     gst_index_add_format (index, src->priv->index_id, sector_format);
1483   }
1484 }
1485 
1486 
1487 static GstIndex *
1488 gst_audio_cd_src_get_index (GstElement * element)
1489 {
1490   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (element);
1491   GstIndex *index;
1492 
1493   GST_OBJECT_LOCK (element);
1494   if ((index = src->priv->index))
1495     gst_object_ref (index);
1496   GST_OBJECT_UNLOCK (element);
1497 
1498   return index;
1499 }
1500 #endif
1501 
1502 static gint
gst_audio_cd_src_track_sort_func(gconstpointer a,gconstpointer b,gpointer foo)1503 gst_audio_cd_src_track_sort_func (gconstpointer a, gconstpointer b,
1504     gpointer foo)
1505 {
1506   GstAudioCdSrcTrack *track_a = ((GstAudioCdSrcTrack *) a);
1507   GstAudioCdSrcTrack *track_b = ((GstAudioCdSrcTrack *) b);
1508 
1509   /* sort data tracks to the end, and audio tracks by track number */
1510   if (track_a->is_audio == track_b->is_audio)
1511     return (gint) track_a->num - (gint) track_b->num;
1512 
1513   if (track_a->is_audio) {
1514     return -1;
1515   } else {
1516     return 1;
1517   }
1518 }
1519 
1520 static gboolean
gst_audio_cd_src_start(GstBaseSrc * basesrc)1521 gst_audio_cd_src_start (GstBaseSrc * basesrc)
1522 {
1523   GstAudioCdSrcClass *klass = GST_AUDIO_CD_SRC_GET_CLASS (basesrc);
1524   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (basesrc);
1525   gboolean ret;
1526   gchar *device = NULL;
1527 
1528   src->priv->discid = 0;
1529   src->priv->mb_discid[0] = '\0';
1530 
1531   g_assert (klass->open != NULL);
1532 
1533   if (src->priv->device != NULL) {
1534     device = g_strdup (src->priv->device);
1535   }
1536 #if 0
1537   else if (klass->get_default_device != NULL) {
1538     device = klass->get_default_device (src);
1539   }
1540 #endif
1541 
1542   if (device == NULL)
1543     device = g_strdup (DEFAULT_DEVICE);
1544 
1545   GST_LOG_OBJECT (basesrc, "opening device %s", device);
1546 
1547   src->tags = gst_tag_list_new_empty ();
1548 
1549   ret = klass->open (src, device);
1550   g_free (device);
1551   device = NULL;
1552 
1553   if (!ret)
1554     goto open_failed;
1555 
1556   if (src->priv->num_tracks == 0 || src->priv->tracks == NULL)
1557     goto no_tracks;
1558 
1559   /* need to calculate disc IDs before we ditch the data tracks */
1560   gst_audio_cd_src_calculate_cddb_id (src);
1561   gst_audio_cd_src_calculate_musicbrainz_discid (src);
1562 
1563 #if 0
1564   /* adjust sector offsets if necessary */
1565   if (src->priv->toc_bias) {
1566     src->priv->toc_offset -= src->priv->tracks[0].start;
1567   }
1568   for (i = 0; i < src->priv->num_tracks; ++i) {
1569     src->priv->tracks[i].start += src->priv->toc_offset;
1570     src->priv->tracks[i].end += src->priv->toc_offset;
1571   }
1572 #endif
1573 
1574   /* now that we calculated the various disc IDs,
1575    * sort the data tracks to end and ignore them */
1576   src->priv->num_all_tracks = src->priv->num_tracks;
1577 
1578   g_qsort_with_data (src->priv->tracks, src->priv->num_tracks,
1579       sizeof (GstAudioCdSrcTrack), gst_audio_cd_src_track_sort_func, NULL);
1580 
1581   while (src->priv->num_tracks > 0
1582       && !src->priv->tracks[src->priv->num_tracks - 1].is_audio)
1583     --src->priv->num_tracks;
1584 
1585   if (src->priv->num_tracks == 0)
1586     goto no_tracks;
1587 
1588   gst_audio_cd_src_add_tags (src);
1589   gst_audio_cd_src_add_toc (src);
1590 
1591 #if 0
1592   if (src->priv->index && GST_INDEX_IS_WRITABLE (src->priv->index))
1593     gst_audio_cd_src_add_index_associations (src);
1594 #endif
1595 
1596   src->priv->cur_track = 0;
1597   src->priv->prev_track = -1;
1598 
1599   if (src->priv->uri_track > 0 && src->priv->uri_track <= src->priv->num_tracks) {
1600     GST_LOG_OBJECT (src, "seek to track %d", src->priv->uri_track);
1601     src->priv->cur_track = src->priv->uri_track - 1;
1602     src->priv->uri_track = -1;
1603     src->priv->mode = GST_AUDIO_CD_SRC_MODE_NORMAL;
1604   }
1605 
1606   src->priv->cur_sector = src->priv->tracks[src->priv->cur_track].start;
1607   GST_LOG_OBJECT (src, "starting at sector %d", src->priv->cur_sector);
1608 
1609   gst_audio_cd_src_update_duration (src);
1610 
1611   return TRUE;
1612 
1613   /* ERRORS */
1614 open_failed:
1615   {
1616     GST_DEBUG_OBJECT (basesrc, "failed to open device");
1617     /* subclass (should have) posted an error message with the details */
1618     gst_audio_cd_src_stop (basesrc);
1619     return FALSE;
1620   }
1621 no_tracks:
1622   {
1623     GST_DEBUG_OBJECT (src, "no audio tracks");
1624     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
1625         (_("This CD has no audio tracks")), (NULL));
1626     gst_audio_cd_src_stop (basesrc);
1627     return FALSE;
1628   }
1629 }
1630 
1631 static void
gst_audio_cd_src_clear_tracks(GstAudioCdSrc * src)1632 gst_audio_cd_src_clear_tracks (GstAudioCdSrc * src)
1633 {
1634   if (src->priv->tracks != NULL) {
1635     gint i;
1636 
1637     for (i = 0; i < src->priv->num_all_tracks; ++i) {
1638       if (src->priv->tracks[i].tags)
1639         gst_tag_list_unref (src->priv->tracks[i].tags);
1640     }
1641 
1642     g_free (src->priv->tracks);
1643     src->priv->tracks = NULL;
1644   }
1645   src->priv->num_tracks = 0;
1646   src->priv->num_all_tracks = 0;
1647 }
1648 
1649 static gboolean
gst_audio_cd_src_stop(GstBaseSrc * basesrc)1650 gst_audio_cd_src_stop (GstBaseSrc * basesrc)
1651 {
1652   GstAudioCdSrcClass *klass = GST_AUDIO_CD_SRC_GET_CLASS (basesrc);
1653   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (basesrc);
1654 
1655   g_assert (klass->close != NULL);
1656 
1657   klass->close (src);
1658 
1659   gst_audio_cd_src_clear_tracks (src);
1660 
1661   if (src->tags) {
1662     gst_tag_list_unref (src->tags);
1663     src->tags = NULL;
1664   }
1665 
1666   gst_event_replace (&src->priv->toc_event, NULL);
1667 
1668   if (src->priv->toc) {
1669     gst_toc_unref (src->priv->toc);
1670     src->priv->toc = NULL;
1671   }
1672 
1673   src->priv->prev_track = -1;
1674   src->priv->cur_track = -1;
1675 
1676   return TRUE;
1677 }
1678 
1679 
1680 static GstFlowReturn
gst_audio_cd_src_create(GstPushSrc * pushsrc,GstBuffer ** buffer)1681 gst_audio_cd_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
1682 {
1683   GstAudioCdSrcClass *klass = GST_AUDIO_CD_SRC_GET_CLASS (pushsrc);
1684   GstAudioCdSrc *src = GST_AUDIO_CD_SRC (pushsrc);
1685   GstBuffer *buf;
1686   gboolean eos;
1687 
1688   GstClockTime position = GST_CLOCK_TIME_NONE;
1689   GstClockTime duration = GST_CLOCK_TIME_NONE;
1690   gint64 qry_position;
1691 
1692   g_assert (klass->read_sector != NULL);
1693 
1694   switch (src->priv->mode) {
1695     case GST_AUDIO_CD_SRC_MODE_NORMAL:
1696       eos =
1697           (src->priv->cur_sector > src->priv->tracks[src->priv->cur_track].end);
1698       break;
1699     case GST_AUDIO_CD_SRC_MODE_CONTINUOUS:
1700       eos =
1701           (src->priv->cur_sector >
1702           src->priv->tracks[src->priv->num_tracks - 1].end);
1703       src->priv->cur_track =
1704           gst_audio_cd_src_get_track_from_sector (src, src->priv->cur_sector);
1705       break;
1706     default:
1707       g_return_val_if_reached (GST_FLOW_ERROR);
1708   }
1709 
1710   if (eos) {
1711     src->priv->prev_track = -1;
1712     GST_DEBUG_OBJECT (src, "EOS at sector %d, cur_track=%d, mode=%d",
1713         src->priv->cur_sector, src->priv->cur_track, src->priv->mode);
1714     /* base class will send EOS for us */
1715     return GST_FLOW_EOS;
1716   }
1717 
1718   if (src->priv->toc_event != NULL) {
1719     gst_pad_push_event (GST_BASE_SRC_PAD (src), src->priv->toc_event);
1720     src->priv->toc_event = NULL;
1721   }
1722 
1723   if (src->priv->prev_track != src->priv->cur_track) {
1724     GstTagList *tags;
1725 
1726     tags =
1727         gst_tag_list_merge (src->tags,
1728         src->priv->tracks[src->priv->cur_track].tags, GST_TAG_MERGE_REPLACE);
1729     GST_LOG_OBJECT (src, "announcing tags: %" GST_PTR_FORMAT, tags);
1730     gst_pad_push_event (GST_BASE_SRC_PAD (src), gst_event_new_tag (tags));
1731     src->priv->prev_track = src->priv->cur_track;
1732 
1733     gst_audio_cd_src_update_duration (src);
1734 
1735     g_object_notify (G_OBJECT (src), "track");
1736   }
1737 
1738   GST_LOG_OBJECT (src, "asking for sector %u", src->priv->cur_sector);
1739 
1740   buf = klass->read_sector (src, src->priv->cur_sector);
1741 
1742   if (buf == NULL) {
1743     GST_WARNING_OBJECT (src, "failed to read sector %u", src->priv->cur_sector);
1744     return GST_FLOW_ERROR;
1745   }
1746 
1747   if (gst_pad_query_position (GST_BASE_SRC_PAD (src), GST_FORMAT_TIME,
1748           &qry_position)) {
1749     gint64 next_ts = 0;
1750 
1751     position = (GstClockTime) qry_position;
1752 
1753     ++src->priv->cur_sector;
1754     if (gst_pad_query_position (GST_BASE_SRC_PAD (src), GST_FORMAT_TIME,
1755             &next_ts)) {
1756       duration = (GstClockTime) (next_ts - qry_position);
1757     }
1758     --src->priv->cur_sector;
1759   }
1760 
1761   /* fallback duration: 4 bytes per sample, 44100 samples per second */
1762   if (duration == GST_CLOCK_TIME_NONE) {
1763     duration = gst_util_uint64_scale_int (gst_buffer_get_size (buf) >> 2,
1764         GST_SECOND, 44100);
1765   }
1766 
1767   GST_BUFFER_PTS (buf) = position;
1768   GST_BUFFER_DURATION (buf) = duration;
1769 
1770   GST_LOG_OBJECT (src, "pushing sector %d with timestamp %" GST_TIME_FORMAT,
1771       src->priv->cur_sector, GST_TIME_ARGS (position));
1772 
1773   ++src->priv->cur_sector;
1774 
1775   *buffer = buf;
1776 
1777   return GST_FLOW_OK;
1778 }
1779