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 * <ulink url="http://accuraterip.com/">AccurateRip</ulink>. This database
42 * is used to check for a 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 #define parent_class gst_accurip_parent_class
76 G_DEFINE_TYPE (GstAccurip, gst_accurip, GST_TYPE_AUDIO_FILTER);
77
78
79
80 static void gst_accurip_finalize (GObject * object);
81 static void gst_accurip_set_property (GObject * object, guint prop_id,
82 const GValue * value, GParamSpec * pspec);
83 static void gst_accurip_get_property (GObject * object, guint prop_id,
84 GValue * value, GParamSpec * pspec);
85 static GstFlowReturn gst_accurip_transform_ip (GstBaseTransform * trans,
86 GstBuffer * buf);
87 static gboolean gst_accurip_sink_event (GstBaseTransform * trans,
88 GstEvent * event);
89
90 static void
gst_accurip_class_init(GstAccuripClass * klass)91 gst_accurip_class_init (GstAccuripClass * klass)
92 {
93 GObjectClass *gobject_class;
94 GstBaseTransformClass *gstbasetrans_class;
95 GstCaps *caps;
96
97 gobject_class = G_OBJECT_CLASS (klass);
98 gstbasetrans_class = GST_BASE_TRANSFORM_CLASS (klass);
99
100 gobject_class->set_property = gst_accurip_set_property;
101 gobject_class->get_property = gst_accurip_get_property;
102
103 g_object_class_install_property (gobject_class, PROP_FIRST_TRACK,
104 g_param_spec_boolean ("first-track", "First track",
105 "Indicate to the CRC calculation algorithm that this is the first track of a set",
106 FALSE, G_PARAM_READWRITE));
107
108 g_object_class_install_property (gobject_class, PROP_LAST_TRACK,
109 g_param_spec_boolean ("last-track", "Last track",
110 "Indicate to the CRC calculation algorithm that this is the last track of a set",
111 FALSE, G_PARAM_READWRITE));
112
113 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_accurip_finalize);
114
115 gstbasetrans_class->transform_ip =
116 GST_DEBUG_FUNCPTR (gst_accurip_transform_ip);
117 gstbasetrans_class->sink_event = GST_DEBUG_FUNCPTR (gst_accurip_sink_event);
118 gstbasetrans_class->passthrough_on_same_caps = TRUE;
119
120 gst_element_class_set_metadata (GST_ELEMENT_CLASS (klass),
121 "AccurateRip(TM) CRC element",
122 "Filter/Analyzer/Audio",
123 "Computes an AccurateRip CRC", "Christophe Fergeau <teuf@gnome.org>");
124
125 caps = gst_caps_from_string (PAD_CAPS);
126 gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
127 caps);
128 gst_caps_unref (caps);
129 }
130
131 static void
ring_free(GstAccurip * accurip)132 ring_free (GstAccurip * accurip)
133 {
134 g_free (accurip->crcs_ring);
135 g_free (accurip->crcs_v2_ring);
136 accurip->crcs_ring = NULL;
137 accurip->crcs_v2_ring = NULL;
138 accurip->ring_samples = 0;
139 }
140
141 static void
gst_accurip_reset(GstAccurip * accurip)142 gst_accurip_reset (GstAccurip * accurip)
143 {
144 if (accurip->num_samples != 0) {
145 /* Don't reset these values on the NEW_SEGMENT event we get when
146 * the pipeline starts playing, they may have been set by the
147 * element user while creating the pipeline
148 */
149 accurip->is_first = FALSE;
150 accurip->is_last = FALSE;
151 ring_free (accurip);
152 }
153 accurip->crc = 0;
154 accurip->crc_v2 = 0;
155
156 accurip->num_samples = 0;
157 }
158
159 /* We must ignore the first and last 5 CD sectors. A CD sector is worth
160 * 2352 bytes of audio */
161 #define IGNORED_SAMPLES_COUNT (2352 * 5 / (2*2))
162
163 static void
gst_accurip_emit_tags(GstAccurip * accurip)164 gst_accurip_emit_tags (GstAccurip * accurip)
165 {
166 GstTagList *tags;
167
168 if (accurip->num_samples == 0)
169 return;
170
171 if (accurip->is_last) {
172 guint index;
173 if (accurip->ring_samples <= IGNORED_SAMPLES_COUNT) {
174 return;
175 }
176 index = accurip->ring_samples - IGNORED_SAMPLES_COUNT;
177 index %= (IGNORED_SAMPLES_COUNT + 1);
178 accurip->crc = accurip->crcs_ring[index];
179 accurip->crc_v2 = accurip->crcs_v2_ring[index];
180 }
181
182 GST_DEBUG_OBJECT (accurip,
183 "Generating CRC based on %" G_GUINT64_FORMAT " samples",
184 accurip->num_samples);
185
186 tags = gst_tag_list_new (GST_TAG_ACCURIP_CRC, accurip->crc,
187 GST_TAG_ACCURIP_CRC_V2, accurip->crc_v2, NULL);
188
189 GST_DEBUG_OBJECT (accurip, "Computed CRC=%08X and CRCv2=0x%08X \n",
190 accurip->crc, accurip->crc_v2);
191
192 gst_pad_push_event (GST_BASE_TRANSFORM_SRC_PAD (accurip),
193 gst_event_new_tag (tags));
194 }
195
196 static void
gst_accurip_init(GstAccurip * accurip)197 gst_accurip_init (GstAccurip * accurip)
198 {
199 gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (accurip), TRUE);
200 }
201
202 static void
gst_accurip_finalize(GObject * object)203 gst_accurip_finalize (GObject * object)
204 {
205 ring_free (GST_ACCURIP (object));
206
207 G_OBJECT_CLASS (parent_class)->finalize (object);
208 }
209
210 static GstFlowReturn
gst_accurip_transform_ip(GstBaseTransform * trans,GstBuffer * buf)211 gst_accurip_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
212 {
213 GstAccurip *accurip = GST_ACCURIP (trans);
214 GstAudioFilter *filter = GST_AUDIO_FILTER (trans);
215 guint32 *data;
216 GstMapInfo map_info;
217 guint nsamples;
218 gint channels;
219 guint i;
220
221 channels = GST_AUDIO_INFO_CHANNELS (&filter->info);
222
223 if (G_UNLIKELY (channels != 2))
224 return GST_FLOW_NOT_NEGOTIATED;
225
226 if (!gst_buffer_map (buf, &map_info, GST_MAP_READ))
227 return GST_FLOW_ERROR;
228
229 data = (guint32 *) map_info.data;
230 nsamples = map_info.size / (channels * 2);
231
232 for (i = 0; i < nsamples; i++) {
233 guint64 mult_sample;
234
235 /* the AccurateRip algorithm counts samples starting from 1 instead
236 * of 0, that's why we start by incrementing the number of samples
237 * before doing the calculations
238 */
239 accurip->num_samples++;
240
241 /* On the first track, we have to ignore the first 5 CD sectors of
242 * audio data
243 */
244 if (accurip->is_first && accurip->num_samples < IGNORED_SAMPLES_COUNT)
245 continue;
246
247 /* Actual CRC computation is here */
248 mult_sample = data[i] * accurip->num_samples;
249 accurip->crc += mult_sample;
250 accurip->crc_v2 += mult_sample & 0xffffffff;
251 accurip->crc_v2 += (mult_sample >> 32);
252
253 /* On the last track, we've got to ignore the last 5 CD sectors of
254 * audio data, since we cannot know in advance when the last buffer
255 * will be, we keep 5 CD sectors samples + 1 in memory so that we
256 * can rollback to the 'good' value when we reach the end of stream.
257 * This magic is only needed when the 'track-last' property is set.
258 */
259 if (accurip->is_last) {
260 guint index = accurip->ring_samples % (IGNORED_SAMPLES_COUNT + 1);
261 accurip->ring_samples++;
262 accurip->crcs_ring[index] = accurip->crc;
263 accurip->crcs_v2_ring[index] = accurip->crc_v2;
264 }
265 }
266
267 gst_buffer_unmap (buf, &map_info);
268
269 return GST_FLOW_OK;
270 }
271
272 static gboolean
gst_accurip_sink_event(GstBaseTransform * trans,GstEvent * event)273 gst_accurip_sink_event (GstBaseTransform * trans, GstEvent * event)
274 {
275 GstAccurip *accurip = GST_ACCURIP (trans);
276
277 switch (GST_EVENT_TYPE (event)) {
278 case GST_EVENT_FLUSH_STOP:
279 case GST_EVENT_SEGMENT:
280 GST_DEBUG_OBJECT (trans, "Got %s event, clearing buffer",
281 GST_EVENT_TYPE_NAME (event));
282 gst_accurip_emit_tags (accurip);
283 gst_accurip_reset (accurip);
284 break;
285 case GST_EVENT_EOS:
286 gst_accurip_emit_tags (accurip);
287 break;
288 default:
289 break;
290 }
291
292 return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
293 }
294
295 static void
gst_accurip_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)296 gst_accurip_set_property (GObject * object, guint prop_id,
297 const GValue * value, GParamSpec * pspec)
298 {
299 GstAccurip *accurip = GST_ACCURIP (object);
300
301 switch (prop_id) {
302 case PROP_FIRST_TRACK:
303 accurip->is_first = g_value_get_boolean (value);
304 break;
305 case PROP_LAST_TRACK:
306 if (accurip->is_last != g_value_get_boolean (value)) {
307 ring_free (accurip);
308 }
309 accurip->is_last = g_value_get_boolean (value);
310 if (accurip->is_last) {
311 if (accurip->crcs_ring == NULL) {
312 accurip->crcs_ring = g_new0 (guint32, IGNORED_SAMPLES_COUNT + 1);
313 }
314 if (accurip->crcs_v2_ring == NULL) {
315 accurip->crcs_v2_ring = g_new0 (guint32, IGNORED_SAMPLES_COUNT + 1);
316 }
317 }
318 break;
319 default:
320 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
321 break;
322 }
323 }
324
325 static void
gst_accurip_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)326 gst_accurip_get_property (GObject * object, guint prop_id,
327 GValue * value, GParamSpec * pspec)
328 {
329 GstAccurip *accurip = GST_ACCURIP (object);
330
331 switch (prop_id) {
332 case PROP_FIRST_TRACK:
333 g_value_set_boolean (value, accurip->is_first);
334 break;
335 case PROP_LAST_TRACK:
336 g_value_set_boolean (value, accurip->is_last);
337 break;
338 default:
339 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
340 break;
341 }
342 }
343
344 static gboolean
plugin_init(GstPlugin * plugin)345 plugin_init (GstPlugin * plugin)
346 {
347 gboolean ret;
348
349 GST_DEBUG_CATEGORY_INIT (gst_accurip_debug, "accurip", 0, "accurip element");
350
351 ret = gst_element_register (plugin, "accurip", GST_RANK_NONE,
352 GST_TYPE_ACCURIP);
353
354 if (ret) {
355 gst_tag_register (GST_TAG_ACCURIP_CRC, GST_TAG_FLAG_META,
356 G_TYPE_UINT, "accurip crc", "AccurateRip(TM) CRC", NULL);
357 gst_tag_register (GST_TAG_ACCURIP_CRC_V2, GST_TAG_FLAG_META,
358 G_TYPE_UINT, "accurip crc (v2)", "AccurateRip(TM) CRC (version 2)",
359 NULL);
360 }
361
362 return ret;
363 }
364
365 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
366 GST_VERSION_MINOR,
367 accurip,
368 "Computes an AccurateRip CRC",
369 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
370