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