1 /* GStreamer AccurateRip (TM) audio checksumming element
2 *
3 * Copyright (C) 2012 Christophe Fergeau <teuf@gnome.org>
4 *
5 * Based on the GStreamer chromaprint audio fingerprinting element
6 * Copyright (C) 2006 M. Derezynski
7 * Copyright (C) 2008 Eric Buehl
8 * Copyright (C) 2008 Sebastian Dröge <slomo@circular-chaos.org>
9 * Copyright (C) 2011 Lukáš Lalinský <lalinsky@gmail.com>
10 * Copyright (C) 2012 Collabora Ltd. <tim.muller@collabora.co.uk>
11 *
12 * This library is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Library General Public
14 * License as published by the Free Software Foundation; either
15 * version 2 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with this library; if not, write to the
24 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 * Boston, MA 02110-1301, USA.
26 */
27 /*
28 * Based on the documentation from
29 * http://forum.dbpoweramp.com/showthread.php?20641-AccurateRip-CRC-Calculation
30 * and
31 * http://jonls.dk/2009/10/calculating-accuraterip-checksums/
32 */
33
34 /**
35 * SECTION:element-accurip
36 * @title: accurip
37 * @short_desc: Computes an AccurateRip CRC
38 *
39 * The accurip element calculates a CRC for an audio stream which can be used
40 * to match the audio stream to a database hosted on
41 * [AccurateRip](http://accuraterip.com/). This database is used to check for a
42 * CD rip accuracy.
43 *
44 * ## Example launch line
45 * |[
46 * gst-launch-1.0 -m uridecodebin uri=file:///path/to/song.flac ! audioconvert ! accurip ! fakesink
47 * ]|
48 *
49 */
50
51 #ifdef HAVE_CONFIG_H
52 #include <config.h>
53 #endif
54
55 #include "gstaccurip.h"
56
57 #define DEFAULT_MAX_DURATION 120
58
59 #define PAD_CAPS \
60 "audio/x-raw, " \
61 "format = (string) " GST_AUDIO_NE(S16) ", "\
62 "rate = (int) 44100, " \
63 "channels = (int) 2"
64
65 GST_DEBUG_CATEGORY_STATIC (gst_accurip_debug);
66 #define GST_CAT_DEFAULT gst_accurip_debug
67
68 enum
69 {
70 PROP_0,
71 PROP_FIRST_TRACK,
72 PROP_LAST_TRACK
73 };
74
75
76
77
78 static void gst_accurip_finalize (GObject * object);
79 static void gst_accurip_set_property (GObject * object, guint prop_id,
80 const GValue * value, GParamSpec * pspec);
81 static void gst_accurip_get_property (GObject * object, guint prop_id,
82 GValue * value, GParamSpec * pspec);
83 static GstFlowReturn gst_accurip_transform_ip (GstBaseTransform * trans,
84 GstBuffer * buf);
85 static gboolean gst_accurip_sink_event (GstBaseTransform * trans,
86 GstEvent * event);
87 static gboolean accurip_element_init (GstPlugin * plugin);
88
89 #define parent_class gst_accurip_parent_class
90 G_DEFINE_TYPE (GstAccurip, gst_accurip, GST_TYPE_AUDIO_FILTER);
91 GST_ELEMENT_REGISTER_DEFINE_CUSTOM (accurip, accurip_element_init);
92
93 static void
gst_accurip_class_init(GstAccuripClass * klass)94 gst_accurip_class_init (GstAccuripClass * klass)
95 {
96 GObjectClass *gobject_class;
97 GstBaseTransformClass *gstbasetrans_class;
98 GstCaps *caps;
99
100 gobject_class = G_OBJECT_CLASS (klass);
101 gstbasetrans_class = GST_BASE_TRANSFORM_CLASS (klass);
102
103 gobject_class->set_property = gst_accurip_set_property;
104 gobject_class->get_property = gst_accurip_get_property;
105
106 g_object_class_install_property (gobject_class, PROP_FIRST_TRACK,
107 g_param_spec_boolean ("first-track", "First track",
108 "Indicate to the CRC calculation algorithm that this is the first track of a set",
109 FALSE, G_PARAM_READWRITE));
110
111 g_object_class_install_property (gobject_class, PROP_LAST_TRACK,
112 g_param_spec_boolean ("last-track", "Last track",
113 "Indicate to the CRC calculation algorithm that this is the last track of a set",
114 FALSE, G_PARAM_READWRITE));
115
116 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_accurip_finalize);
117
118 gstbasetrans_class->transform_ip =
119 GST_DEBUG_FUNCPTR (gst_accurip_transform_ip);
120 gstbasetrans_class->sink_event = GST_DEBUG_FUNCPTR (gst_accurip_sink_event);
121 gstbasetrans_class->passthrough_on_same_caps = TRUE;
122
123 gst_element_class_set_metadata (GST_ELEMENT_CLASS (klass),
124 "AccurateRip(TM) CRC element",
125 "Filter/Analyzer/Audio",
126 "Computes an AccurateRip CRC", "Christophe Fergeau <teuf@gnome.org>");
127
128 caps = gst_caps_from_string (PAD_CAPS);
129 gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
130 caps);
131 gst_caps_unref (caps);
132 }
133
134 static void
ring_free(GstAccurip * accurip)135 ring_free (GstAccurip * accurip)
136 {
137 g_free (accurip->crcs_ring);
138 g_free (accurip->crcs_v2_ring);
139 accurip->crcs_ring = NULL;
140 accurip->crcs_v2_ring = NULL;
141 accurip->ring_samples = 0;
142 }
143
144 static void
gst_accurip_reset(GstAccurip * accurip)145 gst_accurip_reset (GstAccurip * accurip)
146 {
147 if (accurip->num_samples != 0) {
148 /* Don't reset these values on the NEW_SEGMENT event we get when
149 * the pipeline starts playing, they may have been set by the
150 * element user while creating the pipeline
151 */
152 accurip->is_first = FALSE;
153 accurip->is_last = FALSE;
154 ring_free (accurip);
155 }
156 accurip->crc = 0;
157 accurip->crc_v2 = 0;
158
159 accurip->num_samples = 0;
160 }
161
162 /* We must ignore the first and last 5 CD sectors. A CD sector is worth
163 * 2352 bytes of audio */
164 #define IGNORED_SAMPLES_COUNT (2352 * 5 / (2*2))
165
166 static void
gst_accurip_emit_tags(GstAccurip * accurip)167 gst_accurip_emit_tags (GstAccurip * accurip)
168 {
169 GstTagList *tags;
170
171 if (accurip->num_samples == 0)
172 return;
173
174 if (accurip->is_last) {
175 guint index;
176 if (accurip->ring_samples <= IGNORED_SAMPLES_COUNT) {
177 return;
178 }
179 index = accurip->ring_samples - IGNORED_SAMPLES_COUNT;
180 index %= (IGNORED_SAMPLES_COUNT + 1);
181 accurip->crc = accurip->crcs_ring[index];
182 accurip->crc_v2 = accurip->crcs_v2_ring[index];
183 }
184
185 GST_DEBUG_OBJECT (accurip,
186 "Generating CRC based on %" G_GUINT64_FORMAT " samples",
187 accurip->num_samples);
188
189 tags = gst_tag_list_new (GST_TAG_ACCURIP_CRC, accurip->crc,
190 GST_TAG_ACCURIP_CRC_V2, accurip->crc_v2, NULL);
191
192 GST_DEBUG_OBJECT (accurip, "Computed CRC=%08X and CRCv2=0x%08X",
193 accurip->crc, accurip->crc_v2);
194
195 gst_pad_push_event (GST_BASE_TRANSFORM_SRC_PAD (accurip),
196 gst_event_new_tag (tags));
197 }
198
199 static void
gst_accurip_init(GstAccurip * accurip)200 gst_accurip_init (GstAccurip * accurip)
201 {
202 gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (accurip), TRUE);
203 }
204
205 static void
gst_accurip_finalize(GObject * object)206 gst_accurip_finalize (GObject * object)
207 {
208 ring_free (GST_ACCURIP (object));
209
210 G_OBJECT_CLASS (parent_class)->finalize (object);
211 }
212
213 static GstFlowReturn
gst_accurip_transform_ip(GstBaseTransform * trans,GstBuffer * buf)214 gst_accurip_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
215 {
216 GstAccurip *accurip = GST_ACCURIP (trans);
217 GstAudioFilter *filter = GST_AUDIO_FILTER (trans);
218 guint32 *data;
219 GstMapInfo map_info;
220 guint nsamples;
221 gint channels;
222 guint i;
223
224 channels = GST_AUDIO_INFO_CHANNELS (&filter->info);
225
226 if (G_UNLIKELY (channels != 2))
227 return GST_FLOW_NOT_NEGOTIATED;
228
229 if (!gst_buffer_map (buf, &map_info, GST_MAP_READ))
230 return GST_FLOW_ERROR;
231
232 data = (guint32 *) map_info.data;
233 nsamples = map_info.size / (channels * 2);
234
235 for (i = 0; i < nsamples; i++) {
236 guint64 mult_sample;
237
238 /* the AccurateRip algorithm counts samples starting from 1 instead
239 * of 0, that's why we start by incrementing the number of samples
240 * before doing the calculations
241 */
242 accurip->num_samples++;
243
244 /* On the first track, we have to ignore the first 5 CD sectors of
245 * audio data
246 */
247 if (accurip->is_first && accurip->num_samples < IGNORED_SAMPLES_COUNT)
248 continue;
249
250 /* Actual CRC computation is here */
251 mult_sample = data[i] * accurip->num_samples;
252 accurip->crc += mult_sample;
253 accurip->crc_v2 += mult_sample & 0xffffffff;
254 accurip->crc_v2 += (mult_sample >> 32);
255
256 /* On the last track, we've got to ignore the last 5 CD sectors of
257 * audio data, since we cannot know in advance when the last buffer
258 * will be, we keep 5 CD sectors samples + 1 in memory so that we
259 * can rollback to the 'good' value when we reach the end of stream.
260 * This magic is only needed when the 'track-last' property is set.
261 */
262 if (accurip->is_last) {
263 guint index = accurip->ring_samples % (IGNORED_SAMPLES_COUNT + 1);
264 accurip->ring_samples++;
265 accurip->crcs_ring[index] = accurip->crc;
266 accurip->crcs_v2_ring[index] = accurip->crc_v2;
267 }
268 }
269
270 gst_buffer_unmap (buf, &map_info);
271
272 return GST_FLOW_OK;
273 }
274
275 static gboolean
gst_accurip_sink_event(GstBaseTransform * trans,GstEvent * event)276 gst_accurip_sink_event (GstBaseTransform * trans, GstEvent * event)
277 {
278 GstAccurip *accurip = GST_ACCURIP (trans);
279
280 switch (GST_EVENT_TYPE (event)) {
281 case GST_EVENT_FLUSH_STOP:
282 case GST_EVENT_SEGMENT:
283 GST_DEBUG_OBJECT (trans, "Got %s event, clearing buffer",
284 GST_EVENT_TYPE_NAME (event));
285 gst_accurip_emit_tags (accurip);
286 gst_accurip_reset (accurip);
287 break;
288 case GST_EVENT_EOS:
289 gst_accurip_emit_tags (accurip);
290 break;
291 default:
292 break;
293 }
294
295 return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
296 }
297
298 static void
gst_accurip_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)299 gst_accurip_set_property (GObject * object, guint prop_id,
300 const GValue * value, GParamSpec * pspec)
301 {
302 GstAccurip *accurip = GST_ACCURIP (object);
303
304 switch (prop_id) {
305 case PROP_FIRST_TRACK:
306 accurip->is_first = g_value_get_boolean (value);
307 break;
308 case PROP_LAST_TRACK:
309 if (accurip->is_last != g_value_get_boolean (value)) {
310 ring_free (accurip);
311 }
312 accurip->is_last = g_value_get_boolean (value);
313 if (accurip->is_last) {
314 if (accurip->crcs_ring == NULL) {
315 accurip->crcs_ring = g_new0 (guint32, IGNORED_SAMPLES_COUNT + 1);
316 }
317 if (accurip->crcs_v2_ring == NULL) {
318 accurip->crcs_v2_ring = g_new0 (guint32, IGNORED_SAMPLES_COUNT + 1);
319 }
320 }
321 break;
322 default:
323 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
324 break;
325 }
326 }
327
328 static void
gst_accurip_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)329 gst_accurip_get_property (GObject * object, guint prop_id,
330 GValue * value, GParamSpec * pspec)
331 {
332 GstAccurip *accurip = GST_ACCURIP (object);
333
334 switch (prop_id) {
335 case PROP_FIRST_TRACK:
336 g_value_set_boolean (value, accurip->is_first);
337 break;
338 case PROP_LAST_TRACK:
339 g_value_set_boolean (value, accurip->is_last);
340 break;
341 default:
342 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
343 break;
344 }
345 }
346
347 static gboolean
accurip_element_init(GstPlugin * plugin)348 accurip_element_init (GstPlugin * plugin)
349 {
350 gboolean ret;
351
352 GST_DEBUG_CATEGORY_INIT (gst_accurip_debug, "accurip", 0, "accurip element");
353
354 ret = gst_element_register (plugin, "accurip", GST_RANK_NONE,
355 GST_TYPE_ACCURIP);
356
357 if (ret) {
358 gst_tag_register (GST_TAG_ACCURIP_CRC, GST_TAG_FLAG_META,
359 G_TYPE_UINT, "accurip crc", "AccurateRip(TM) CRC", NULL);
360 gst_tag_register (GST_TAG_ACCURIP_CRC_V2, GST_TAG_FLAG_META,
361 G_TYPE_UINT, "accurip crc (v2)", "AccurateRip(TM) CRC (version 2)",
362 NULL);
363 }
364
365 return ret;
366 }
367
368 static gboolean
plugin_init(GstPlugin * plugin)369 plugin_init (GstPlugin * plugin)
370 {
371 return GST_ELEMENT_REGISTER (accurip, plugin);
372 }
373
374 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
375 GST_VERSION_MINOR,
376 accurip,
377 "Computes an AccurateRip CRC",
378 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
379