diff options
author | Dan Everton <dan@iocaine.org> | 2006-07-20 10:35:20 +0000 |
---|---|---|
committer | Dan Everton <dan@iocaine.org> | 2006-07-20 10:35:20 +0000 |
commit | 7b5af8c045d1db6e805981e1c6d1b32de200e3f3 (patch) | |
tree | 669e1b044c9681203ef5fb72ebac466c701447a7 /firmware/replaygain.c | |
parent | 928c33c255e47c39c5f549b3e1aa40fbb3aaa09a (diff) | |
download | rockbox-7b5af8c045d1db6e805981e1c6d1b32de200e3f3.tar.gz rockbox-7b5af8c045d1db6e805981e1c6d1b32de200e3f3.zip |
Add support for parsing binary data ID3 tags. Add support for parsing RVA2 ID3 tags for replaygain values instead of TXXX tags when playing files tagged with ID3v2.4. Patch by psyche78 (task #2937)
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10259 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/replaygain.c')
-rw-r--r-- | firmware/replaygain.c | 110 |
1 files changed, 93 insertions, 17 deletions
diff --git a/firmware/replaygain.c b/firmware/replaygain.c index a8331ae483..ffabb4b2ac 100644 --- a/firmware/replaygain.c +++ b/firmware/replaygain.c | |||
@@ -28,6 +28,10 @@ | |||
28 | #include "id3.h" | 28 | #include "id3.h" |
29 | #include "debug.h" | 29 | #include "debug.h" |
30 | 30 | ||
31 | /* Type of channel for RVA2 frame. There are more than this defined in the spec | ||
32 | but we don't use them. */ | ||
33 | #define MASTER_CHANNEL 1 | ||
34 | |||
31 | /* The fixed point math routines (with the exception of fp_atof) are based | 35 | /* The fixed point math routines (with the exception of fp_atof) are based |
32 | * on oMathFP by Dan Carter (http://orbisstudios.com). | 36 | * on oMathFP by Dan Carter (http://orbisstudios.com). |
33 | */ | 37 | */ |
@@ -278,7 +282,7 @@ static long fp_atof(const char* s, int precision) | |||
278 | frac_max_int *= 10; | 282 | frac_max_int *= 10; |
279 | } | 283 | } |
280 | 284 | ||
281 | return sign * ((int_part * int_one) | 285 | return sign * ((int_part * int_one) |
282 | + (((int64_t) frac_part * int_one) / frac_max_int)); | 286 | + (((int64_t) frac_part * int_one) / frac_max_int)); |
283 | } | 287 | } |
284 | 288 | ||
@@ -286,14 +290,14 @@ static long convert_gain(long gain) | |||
286 | { | 290 | { |
287 | if (gain != 0) | 291 | if (gain != 0) |
288 | { | 292 | { |
289 | /* Don't allow unreasonably low or high gain changes. | 293 | /* Don't allow unreasonably low or high gain changes. |
290 | * Our math code can't handle it properly anyway. :) | 294 | * Our math code can't handle it properly anyway. :) |
291 | */ | 295 | */ |
292 | if (gain < (-48 * FP_ONE)) | 296 | if (gain < (-48 * FP_ONE)) |
293 | { | 297 | { |
294 | gain = -48 * FP_ONE; | 298 | gain = -48 * FP_ONE; |
295 | } | 299 | } |
296 | 300 | ||
297 | if (gain > (17 * FP_ONE)) | 301 | if (gain > (17 * FP_ONE)) |
298 | { | 302 | { |
299 | gain = 17 * FP_ONE; | 303 | gain = 17 * FP_ONE; |
@@ -301,55 +305,55 @@ static long convert_gain(long gain) | |||
301 | 305 | ||
302 | gain = fp_exp10(gain / 20) << (24 - FP_BITS); | 306 | gain = fp_exp10(gain / 20) << (24 - FP_BITS); |
303 | } | 307 | } |
304 | 308 | ||
305 | return gain; | 309 | return gain; |
306 | } | 310 | } |
307 | 311 | ||
308 | long get_replaygain_int(long int_gain) | 312 | long get_replaygain_int(long int_gain) |
309 | { | 313 | { |
310 | long gain = 0; | 314 | long gain = 0; |
311 | 315 | ||
312 | if (int_gain) | 316 | if (int_gain) |
313 | { | 317 | { |
314 | gain = convert_gain(int_gain * FP_ONE / 100); | 318 | gain = convert_gain(int_gain * FP_ONE / 100); |
315 | } | 319 | } |
316 | 320 | ||
317 | return gain; | 321 | return gain; |
318 | } | 322 | } |
319 | 323 | ||
320 | long get_replaygain(const char* str) | 324 | long get_replaygain(const char* str) |
321 | { | 325 | { |
322 | long gain = 0; | 326 | long gain = 0; |
323 | 327 | ||
324 | if (str) | 328 | if (str) |
325 | { | 329 | { |
326 | gain = fp_atof(str, FP_BITS); | 330 | gain = fp_atof(str, FP_BITS); |
327 | gain = convert_gain(gain); | 331 | gain = convert_gain(gain); |
328 | } | 332 | } |
329 | 333 | ||
330 | return gain; | 334 | return gain; |
331 | } | 335 | } |
332 | 336 | ||
333 | long get_replaypeak(const char* str) | 337 | long get_replaypeak(const char* str) |
334 | { | 338 | { |
335 | long peak = 0; | 339 | long peak = 0; |
336 | 340 | ||
337 | if (str) | 341 | if (str) |
338 | { | 342 | { |
339 | peak = fp_atof(str, 24); | 343 | peak = fp_atof(str, 24); |
340 | } | 344 | } |
341 | 345 | ||
342 | return peak; | 346 | return peak; |
343 | } | 347 | } |
344 | 348 | ||
345 | /* Check for a ReplayGain tag conforming to the "VorbisGain standard". If | 349 | /* Check for a ReplayGain tag conforming to the "VorbisGain standard". If |
346 | * found, set the mp3entry accordingly. buffer is where to store the text | 350 | * found, set the mp3entry accordingly. buffer is where to store the text |
347 | * contents of the gain tags; up to length bytes (including end nil) can be | 351 | * contents of the gain tags; up to length bytes (including end nil) can be |
348 | * written. Returns number of bytes written to the tag text buffer, or zero | 352 | * written. Returns number of bytes written to the tag text buffer, or zero |
349 | * if no ReplayGain tag was found (or nothing was copied to the buffer for | 353 | * if no ReplayGain tag was found (or nothing was copied to the buffer for |
350 | * other reasons). | 354 | * other reasons). |
351 | */ | 355 | */ |
352 | long parse_replaygain(const char* key, const char* value, | 356 | long parse_replaygain(const char* key, const char* value, |
353 | struct mp3entry* entry, char* buffer, int length) | 357 | struct mp3entry* entry, char* buffer, int length) |
354 | { | 358 | { |
355 | char **p = NULL; | 359 | char **p = NULL; |
@@ -359,7 +363,7 @@ long parse_replaygain(const char* key, const char* value, | |||
359 | { | 363 | { |
360 | entry->track_gain = get_replaygain(value); | 364 | entry->track_gain = get_replaygain(value); |
361 | p = &(entry->track_gain_string); | 365 | p = &(entry->track_gain_string); |
362 | } | 366 | } |
363 | else if ((strcasecmp(key, "replaygain_album_gain") == 0) | 367 | else if ((strcasecmp(key, "replaygain_album_gain") == 0) |
364 | || ((strcasecmp(key, "rg_audiophile") == 0) && !entry->album_gain)) | 368 | || ((strcasecmp(key, "rg_audiophile") == 0) && !entry->album_gain)) |
365 | { | 369 | { |
@@ -370,7 +374,7 @@ long parse_replaygain(const char* key, const char* value, | |||
370 | || ((strcasecmp(key, "rg_peak") == 0) && !entry->track_peak)) | 374 | || ((strcasecmp(key, "rg_peak") == 0) && !entry->track_peak)) |
371 | { | 375 | { |
372 | entry->track_peak = get_replaypeak(value); | 376 | entry->track_peak = get_replaypeak(value); |
373 | } | 377 | } |
374 | else if (strcasecmp(key, "replaygain_album_peak") == 0) | 378 | else if (strcasecmp(key, "replaygain_album_peak") == 0) |
375 | { | 379 | { |
376 | entry->album_peak = get_replaypeak(value); | 380 | entry->album_peak = get_replaypeak(value); |
@@ -379,7 +383,7 @@ long parse_replaygain(const char* key, const char* value, | |||
379 | if (p) | 383 | if (p) |
380 | { | 384 | { |
381 | int len = strlen(value); | 385 | int len = strlen(value); |
382 | 386 | ||
383 | len = MIN(len, length - 1); | 387 | len = MIN(len, length - 1); |
384 | 388 | ||
385 | /* A few characters just isn't interesting... */ | 389 | /* A few characters just isn't interesting... */ |
@@ -394,3 +398,75 @@ long parse_replaygain(const char* key, const char* value, | |||
394 | 398 | ||
395 | return 0; | 399 | return 0; |
396 | } | 400 | } |
401 | |||
402 | static long get_rva_values(const char *frame, long *gain, long *peak, | ||
403 | char **string, char *buffer, int length) | ||
404 | { | ||
405 | long value, len; | ||
406 | int negative = 0; | ||
407 | char tmpbuf[10]; | ||
408 | int peakbits, peakbytes, shift; | ||
409 | unsigned long peakvalue = 0; | ||
410 | |||
411 | value = 256 * ((unsigned char)*frame) + ((unsigned char)*(frame + 1)); | ||
412 | if (value & 0x8000) | ||
413 | { | ||
414 | value = -(value | ~0xFFFF); | ||
415 | negative = 1; | ||
416 | } | ||
417 | |||
418 | len = snprintf(tmpbuf, sizeof(tmpbuf), "%s%d.%02d dB", negative ? "-" : "", | ||
419 | value / 512, (value & 0x1FF) * 195 / 1000); | ||
420 | |||
421 | *gain = get_replaygain(tmpbuf); | ||
422 | |||
423 | len = MIN(len, length - 1); | ||
424 | if (len > 1) | ||
425 | { | ||
426 | strncpy(buffer, tmpbuf, len); | ||
427 | buffer[len] = 0; | ||
428 | *string = buffer; | ||
429 | } | ||
430 | |||
431 | frame += 2; | ||
432 | peakbits = *(unsigned char *)frame++; | ||
433 | peakbytes = MIN(4, (peakbits + 7) >> 3); | ||
434 | shift = ((8 - (peakbits & 7)) & 7) + (4 - peakbytes) * 8; | ||
435 | |||
436 | for (; peakbytes; peakbytes--) | ||
437 | { | ||
438 | peakvalue <<= 8; | ||
439 | peakvalue += (unsigned long)*frame++; | ||
440 | } | ||
441 | |||
442 | peakvalue <<= shift; | ||
443 | |||
444 | if (peakbits > 32) | ||
445 | peakvalue += (unsigned long)*frame >> (8 - shift); | ||
446 | |||
447 | snprintf(tmpbuf, sizeof(tmpbuf), "%d.%06d", peakvalue >> 31, | ||
448 | (peakvalue & ~(1 << 31)) / 2147); | ||
449 | |||
450 | *peak = get_replaypeak(tmpbuf); | ||
451 | |||
452 | return len + 1; | ||
453 | } | ||
454 | |||
455 | long parse_replaygain_rva(const char* key, const char* value, | ||
456 | struct mp3entry* entry, char* buffer, int length) | ||
457 | { | ||
458 | if ((strcasecmp(key, "track") == 0) && *value == MASTER_CHANNEL | ||
459 | && !entry->track_gain && !entry->track_peak) | ||
460 | { | ||
461 | return get_rva_values(value + 1, &(entry->track_gain), &(entry->track_peak), | ||
462 | &(entry->track_gain_string), buffer, length); | ||
463 | } | ||
464 | else if ((strcasecmp(key, "album") == 0) && *value == MASTER_CHANNEL | ||
465 | && !entry->album_gain && !entry->album_peak) | ||
466 | { | ||
467 | return get_rva_values(value + 1, &(entry->album_gain), &(entry->album_peak), | ||
468 | &(entry->album_gain_string), buffer, length); | ||
469 | } | ||
470 | |||
471 | return 0; | ||
472 | } | ||