summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMagnus Holmgren <magnushol@gmail.com>2005-07-27 11:54:33 +0000
committerMagnus Holmgren <magnushol@gmail.com>2005-07-27 11:54:33 +0000
commit988ea2cffc36d891d5b4752484c741a98eddede3 (patch)
tree7118198c6da300be6f5d90692988d243bcbcda04
parente44372ef18cbf30f0e174ed76be4ee4e6206f2cc (diff)
downloadrockbox-988ea2cffc36d891d5b4752484c741a98eddede3.tar.gz
rockbox-988ea2cffc36d891d5b4752484c741a98eddede3.zip
Added support for ID3V2 ReplayGain tags (as written by Foobar). Generalized the replaygain tag parsing a bit, to cut down the code size (APE tags should use this as well, but as it requires larger changes, it will have to wait for another commit). Also fixed a bug in the ID3V2 parser; ISO-8859-1 strings could confuse the main parsing loop (causing bufferpos to come out of sync).
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7243 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/codecs/mpa.c3
-rw-r--r--apps/metadata.c38
-rw-r--r--apps/screens.c8
-rw-r--r--firmware/export/id3.h4
-rw-r--r--firmware/export/replaygain.h4
-rw-r--r--firmware/id3.c166
-rw-r--r--firmware/replaygain.c108
7 files changed, 235 insertions, 96 deletions
diff --git a/apps/codecs/mpa.c b/apps/codecs/mpa.c
index 2dcc71922e..fb478222a4 100644
--- a/apps/codecs/mpa.c
+++ b/apps/codecs/mpa.c
@@ -118,6 +118,7 @@ enum codec_status codec_start(struct codec_api* api)
118 frequency_divider = 441; 118 frequency_divider = 441;
119 119
120 ci->configure(DSP_SET_FREQUENCY, (int *)ci->id3->frequency); 120 ci->configure(DSP_SET_FREQUENCY, (int *)ci->id3->frequency);
121 codec_set_replaygain(ci->id3);
121 122
122 ci->request_buffer(&size, ci->id3->first_frame_offset); 123 ci->request_buffer(&size, ci->id3->first_frame_offset);
123 ci->advance_buffer(size); 124 ci->advance_buffer(size);
@@ -144,7 +145,7 @@ enum codec_status codec_start(struct codec_api* api)
144 samplecount = ci->id3->length * frequency_divider / 10; 145 samplecount = ci->id3->length * frequency_divider / 10;
145 samplesdone = ci->id3->elapsed * frequency_divider / 10; 146 samplesdone = ci->id3->elapsed * frequency_divider / 10;
146 } 147 }
147 148
148 /* This is the decoding loop. */ 149 /* This is the decoding loop. */
149 while (1) { 150 while (1) {
150 ci->yield(); 151 ci->yield();
diff --git a/apps/metadata.c b/apps/metadata.c
index 409bdf88c5..bbbfe07d66 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -528,7 +528,7 @@ static bool get_apetag_info (struct mp3entry *entry, int fd)
528 528
529 if (rem_space > 1 && 529 if (rem_space > 1 &&
530 get_apetag_item (&temp_apetag, "replaygain_track_gain", temp_buffer, rem_space)) { 530 get_apetag_item (&temp_apetag, "replaygain_track_gain", temp_buffer, rem_space)) {
531 entry->track_gain = get_replaygain (entry->track_gain_str = temp_buffer); 531 entry->track_gain = get_replaygain (entry->track_gain_string = temp_buffer);
532 str_space = strlen (temp_buffer) + 1; 532 str_space = strlen (temp_buffer) + 1;
533 temp_buffer += str_space; 533 temp_buffer += str_space;
534 rem_space -= str_space; 534 rem_space -= str_space;
@@ -536,7 +536,7 @@ static bool get_apetag_info (struct mp3entry *entry, int fd)
536 536
537 if (rem_space > 1 && 537 if (rem_space > 1 &&
538 get_apetag_item (&temp_apetag, "replaygain_album_gain", temp_buffer, rem_space)) { 538 get_apetag_item (&temp_apetag, "replaygain_album_gain", temp_buffer, rem_space)) {
539 entry->album_gain = get_replaygain (entry->album_gain_str = temp_buffer); 539 entry->album_gain = get_replaygain (entry->album_gain_string = temp_buffer);
540 str_space = strlen (temp_buffer) + 1; 540 str_space = strlen (temp_buffer) + 1;
541 temp_buffer += str_space; 541 temp_buffer += str_space;
542 rem_space -= str_space; 542 rem_space -= str_space;
@@ -910,37 +910,13 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
910 } else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) { 910 } else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
911 name_length = 11; 911 name_length = 11;
912 p = &(entry->track_string); 912 p = &(entry->track_string);
913 } else if ((strncasecmp(temp, "RG_RADIO=", 9) == 0)
914 && !entry->track_gain) {
915 entry->track_gain = get_replaygain(&temp[9]);
916 name_length = 8;
917 p = &(entry->track_gain_str);
918 } else if (strncasecmp(temp, "REPLAYGAIN_TRACK_GAIN=", 22) == 0) {
919 entry->track_gain = get_replaygain(&temp[22]);
920 name_length = 21;
921 p = &(entry->track_gain_str);
922 } else if ((strncasecmp(temp, "RG_AUDIOPHILE=", 14) == 0)
923 && !entry->album_gain) {
924 entry->album_gain = get_replaygain(&temp[14]);
925 name_length = 13;
926 p = &(entry->album_gain_str);
927 } else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_GAIN=", 22) == 0) {
928 entry->album_gain = get_replaygain(&temp[22]);
929 name_length = 21;
930 p = &(entry->album_gain_str);
931 } else if ((strncasecmp(temp, "RG_PEAK=", 8) == 0)
932 && !entry->track_peak) {
933 entry->track_peak = get_replaypeak(&temp[8]);
934 p = NULL;
935 } else if (strncasecmp(temp, "REPLAYGAIN_TRACK_PEAK=", 22) == 0) {
936 entry->track_peak = get_replaypeak(&temp[22]);
937 p = NULL;
938 } else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_PEAK=", 22) == 0) {
939 entry->album_peak = get_replaypeak(&temp[22]);
940 p = NULL;
941 } else { 913 } else {
914 int value_length = parse_replaygain(temp, NULL, entry, buffer,
915 buffer_remaining);
916 buffer_remaining -= value_length;
917 buffer += value_length;
942 p = NULL; 918 p = NULL;
943 } 919 }
944 920
945 if (p) { 921 if (p) {
946 comment_length -= (name_length + 1); 922 comment_length -= (name_length + 1);
diff --git a/apps/screens.c b/apps/screens.c
index 86d11a73a4..2c7d5fe9b1 100644
--- a/apps/screens.c
+++ b/apps/screens.c
@@ -1388,15 +1388,15 @@ bool browse_id3(void)
1388#if CONFIG_HWCODEC == MASNONE 1388#if CONFIG_HWCODEC == MASNONE
1389 case 11: 1389 case 11:
1390 lcd_puts(0, 0, str(LANG_ID3_TRACK_GAIN)); 1390 lcd_puts(0, 0, str(LANG_ID3_TRACK_GAIN));
1391 lcd_puts(0, 1, id3->track_gain_str 1391 lcd_puts(0, 1, id3->track_gain_string
1392 ? id3->track_gain_str 1392 ? id3->track_gain_string
1393 : (char*) str(LANG_ID3_NO_GAIN)); 1393 : (char*) str(LANG_ID3_NO_GAIN));
1394 break; 1394 break;
1395 1395
1396 case 12: 1396 case 12:
1397 lcd_puts(0, 0, str(LANG_ID3_ALBUM_GAIN)); 1397 lcd_puts(0, 0, str(LANG_ID3_ALBUM_GAIN));
1398 lcd_puts(0, 1, id3->album_gain_str 1398 lcd_puts(0, 1, id3->album_gain_string
1399 ? id3->album_gain_str 1399 ? id3->album_gain_string
1400 : (char*) str(LANG_ID3_NO_GAIN)); 1400 : (char*) str(LANG_ID3_NO_GAIN));
1401 break; 1401 break;
1402#endif 1402#endif
diff --git a/firmware/export/id3.h b/firmware/export/id3.h
index 348b17e191..abb354b233 100644
--- a/firmware/export/id3.h
+++ b/firmware/export/id3.h
@@ -119,8 +119,8 @@ struct mp3entry {
119 /* replaygain support */ 119 /* replaygain support */
120 120
121#if CONFIG_HWCODEC == MASNONE 121#if CONFIG_HWCODEC == MASNONE
122 char* track_gain_str; 122 char* track_gain_string;
123 char* album_gain_str; 123 char* album_gain_string;
124 long track_gain; /* 7.24 signed fixed point. 0 for no gain. */ 124 long track_gain; /* 7.24 signed fixed point. 0 for no gain. */
125 long album_gain; 125 long album_gain;
126 long track_peak; /* 7.24 signed fixed point. 0 for no peak. */ 126 long track_peak; /* 7.24 signed fixed point. 0 for no peak. */
diff --git a/firmware/export/replaygain.h b/firmware/export/replaygain.h
index 09b0776069..e96a7f907a 100644
--- a/firmware/export/replaygain.h
+++ b/firmware/export/replaygain.h
@@ -20,7 +20,11 @@
20#ifndef _REPLAYGAIN_H 20#ifndef _REPLAYGAIN_H
21#define _REPLAYGAIN_H 21#define _REPLAYGAIN_H
22 22
23#include "id3.h"
24
23long get_replaygain(const char* str); 25long get_replaygain(const char* str);
24long get_replaypeak(const char* str); 26long get_replaypeak(const char* str);
27long parse_replaygain(const char* key, const char* value,
28 struct mp3entry* entry, char* buffer, int length);
25 29
26#endif 30#endif
diff --git a/firmware/id3.c b/firmware/id3.c
index 7344bf53c7..d783053426 100644
--- a/firmware/id3.c
+++ b/firmware/id3.c
@@ -41,6 +41,7 @@
41#include "id3.h" 41#include "id3.h"
42#include "mp3data.h" 42#include "mp3data.h"
43#include "system.h" 43#include "system.h"
44#include "replaygain.h"
44 45
45#define UNSYNC(b0,b1,b2,b3) (((long)(b0 & 0x7F) << (3*7)) | \ 46#define UNSYNC(b0,b1,b2,b3) (((long)(b0 & 0x7F) << (3*7)) | \
46 ((long)(b1 & 0x7F) << (2*7)) | \ 47 ((long)(b1 & 0x7F) << (2*7)) | \
@@ -163,6 +164,10 @@ char* id3_get_codec(const struct mp3entry* id3)
163 Many ID3 symbolic names come in more than one form. You can add both 164 Many ID3 symbolic names come in more than one form. You can add both
164 forms, each referencing the same variable in struct mp3entry. 165 forms, each referencing the same variable in struct mp3entry.
165 If both forms are present, the last found will be used. 166 If both forms are present, the last found will be used.
167 Note that the offset can be zero, in which case no entry will be set
168 in the mp3entry struct; the frame is still read into the buffer and
169 the special processing function is called (several times, if there
170 are several frames with the same name).
166 171
167 4. Alternately, use the TAG_LIST_ENTRY macro with 172 4. Alternately, use the TAG_LIST_ENTRY macro with
168 ID3 tag symbolic name, 173 ID3 tag symbolic name,
@@ -305,6 +310,34 @@ static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos )
305 } 310 }
306} 311}
307 312
313#if CONFIG_HWCODEC == MASNONE
314/* parse user defined text, looking for replaygain information. */
315static int parseuser( struct mp3entry* entry, char* tag, int bufferpos )
316{
317 char* value = NULL;
318 int desc_len = strlen(tag);
319 int value_len = 0;
320
321 if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) {
322 /* At least part of the value was read, so we can safely try to
323 * parse it
324 */
325
326 value = tag + desc_len + 1;
327 value_len = parse_replaygain(tag, value, entry, tag,
328 bufferpos - (tag - entry->id3v2buf));
329 }
330
331 if (value_len) {
332 bufferpos = tag - entry->id3v2buf + value_len;
333 } else {
334 bufferpos = tag - entry->id3v2buf;
335 }
336
337 return bufferpos;
338}
339#endif
340
308static const struct tag_resolver taglist[] = { 341static const struct tag_resolver taglist[] = {
309 { "TPE1", 4, offsetof(struct mp3entry, artist), NULL }, 342 { "TPE1", 4, offsetof(struct mp3entry, artist), NULL },
310 { "TP1", 3, offsetof(struct mp3entry, artist), NULL }, 343 { "TP1", 3, offsetof(struct mp3entry, artist), NULL },
@@ -319,6 +352,9 @@ static const struct tag_resolver taglist[] = {
319 { "TCOM", 4, offsetof(struct mp3entry, composer), NULL }, 352 { "TCOM", 4, offsetof(struct mp3entry, composer), NULL },
320 { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre }, 353 { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre },
321 { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre }, 354 { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre },
355#if CONFIG_HWCODEC == MASNONE
356 { "TXXX", 4, 0, &parseuser },
357#endif
322}; 358};
323 359
324#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0]))) 360#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0])))
@@ -327,12 +363,12 @@ static const struct tag_resolver taglist[] = {
327 string. If it is, we attempt to convert it to a 8-bit ASCII string 363 string. If it is, we attempt to convert it to a 8-bit ASCII string
328 (for valid 8-bit ASCII characters). If it's not unicode, we leave 364 (for valid 8-bit ASCII characters). If it's not unicode, we leave
329 it alone. At some point we should fully support unicode strings */ 365 it alone. At some point we should fully support unicode strings */
330static int unicode_munge(char** string, int *len) { 366static int unicode_munge(char* string, int *len) {
331 long tmp; 367 long tmp;
332 bool le = false; 368 bool le = false;
333 int i; 369 int i;
334 char *str = *string; 370 char *str = string;
335 char *outstr = *string; 371 char *outstr = string;
336 bool bom = false; 372 bool bom = false;
337 int outlen; 373 int outlen;
338 374
@@ -343,8 +379,14 @@ static int unicode_munge(char** string, int *len) {
343 379
344 /* Type 0x00 is ordinary ISO 8859-1 */ 380 /* Type 0x00 is ordinary ISO 8859-1 */
345 if(str[0] == 0x00) { 381 if(str[0] == 0x00) {
346 (*len)--; 382 int i = --(*len);
347 (*string)++; /* Skip the encoding type byte */ 383
384 /* We must move the string to the left */
385 while (i--) {
386 string[0] = string[1];
387 string++;
388 }
389
348 return 0; 390 return 0;
349 } 391 }
350 392
@@ -352,53 +394,59 @@ static int unicode_munge(char** string, int *len) {
352 if(str[0] == 0x01 || str[0] == 0x02) { 394 if(str[0] == 0x01 || str[0] == 0x02) {
353 (*len)--; 395 (*len)--;
354 str++; 396 str++;
355 tmp = BYTES2INT(0, 0, str[0], str[1]);
356
357 /* Now check if there is a BOM (zero-width non-breaking space, 0xfeff)
358 and if it is in little or big endian format */
359 if(tmp == 0xfffe) { /* Little endian? */
360 bom = true;
361 le = true;
362 str += 2;
363 (*len)-=2;
364 }
365
366 if(tmp == 0xfeff) { /* Big endian? */
367 bom = true;
368 str += 2;
369 (*len)-=2;
370 }
371
372 /* If there is no BOM (which is a specification violation),
373 let's try to guess it. If one of the bytes is 0x00, it is
374 probably the most significant one. */
375 if(!bom) {
376 if(str[1] == 0)
377 le = true;
378 }
379
380 i = 0; 397 i = 0;
381 398
382 outlen = *len / 2; 399 /* Handle frames with more than one string (needed for TXXX frames).
383 400 */
384 do { 401 do {
385 if(le) { 402 tmp = BYTES2INT(0, 0, str[0], str[1]);
386 if(str[1]) 403
387 outstr[i++] = '.'; 404 /* Now check if there is a BOM (zero-width non-breaking space, 0xfeff)
388 else 405 and if it is in little or big endian format */
389 outstr[i++] = str[0]; 406 if(tmp == 0xfffe) { /* Little endian? */
390 } else { 407 bom = true;
391 if(str[0]) 408 le = true;
392 outstr[i++] = '.'; 409 str += 2;
393 else 410 (*len)-=2;
394 outstr[i++] = str[1]; 411 }
412
413 if(tmp == 0xfeff) { /* Big endian? */
414 bom = true;
415 str += 2;
416 (*len)-=2;
417 }
418
419 /* If there is no BOM (which is a specification violation),
420 let's try to guess it. If one of the bytes is 0x00, it is
421 probably the most significant one. */
422 if(!bom) {
423 if(str[1] == 0)
424 le = true;
395 } 425 }
426
427 outlen = *len / 2;
428
429 do {
430 if(le) {
431 if(str[1])
432 outstr[i++] = '.';
433 else
434 outstr[i++] = str[0];
435 } else {
436 if(str[0])
437 outstr[i++] = '.';
438 else
439 outstr[i++] = str[1];
440 }
441 str += 2;
442 } while((str[0] || str[1]) && (i < outlen));
443
396 str += 2; 444 str += 2;
397 } while((str[0] || str[1]) && (i < outlen)); 445 outstr[i++] = 0; /* Terminate the string */
446 } while(i < outlen);
398 447
399 *len = i; 448 *len = i - 1;
400 449
401 outstr[i] = 0; /* Terminate the string */
402 return 0; 450 return 0;
403 } 451 }
404 452
@@ -686,29 +734,33 @@ static void setid3v2title(int fd, struct mp3entry *entry)
686 734
687 for (i=0; i<TAGLIST_SIZE; i++) { 735 for (i=0; i<TAGLIST_SIZE; i++) {
688 const struct tag_resolver* tr = &taglist[i]; 736 const struct tag_resolver* tr = &taglist[i];
689 char** ptag = (char**) (((char*)entry) + tr->offset); 737 char** ptag = tr->offset ? (char**) (((char*)entry) + tr->offset)
738 : NULL;
690 char* tag; 739 char* tag;
691 740
692 if( !*ptag && !memcmp( header, tr->tag, tr->tag_length ) ) { 741 if( (!ptag || !*ptag) && !memcmp( header, tr->tag, tr->tag_length ) ) {
693 742
694 /* found a tag matching one in tagList, and not yet filled */ 743 /* found a tag matching one in tagList, and not yet filled */
744 tag = buffer + bufferpos;
745
695 if(global_unsynch && version <= ID3_VER_2_3) 746 if(global_unsynch && version <= ID3_VER_2_3)
696 bytesread = read_unsynched(fd, buffer + bufferpos, 747 bytesread = read_unsynched(fd, tag, framelen);
697 framelen);
698 else 748 else
699 bytesread = read(fd, buffer + bufferpos, framelen); 749 bytesread = read(fd, tag, framelen);
700 750
701 if( bytesread != framelen ) 751 if( bytesread != framelen )
702 return; 752 return;
703 753
704 size -= bytesread; 754 size -= bytesread;
705 *ptag = buffer + bufferpos; 755
706
707 if(unsynch || (global_unsynch && version >= ID3_VER_2_4)) 756 if(unsynch || (global_unsynch && version >= ID3_VER_2_4))
708 bytesread = unsynchronize_frame(*ptag, bytesread); 757 bytesread = unsynchronize_frame(tag, bytesread);
709 758
710 unicode_munge( ptag, &bytesread ); 759 unicode_munge( tag, &bytesread );
711 tag = *ptag; 760
761 if (ptag)
762 *ptag = tag;
763
712 /* remove trailing spaces */ 764 /* remove trailing spaces */
713 while ( bytesread > 0 && isspace(tag[bytesread-1])) 765 while ( bytesread > 0 && isspace(tag[bytesread-1]))
714 bytesread--; 766 bytesread--;
diff --git a/firmware/replaygain.c b/firmware/replaygain.c
index 23a25cc310..542eee6101 100644
--- a/firmware/replaygain.c
+++ b/firmware/replaygain.c
@@ -20,9 +20,12 @@
20#include <ctype.h> 20#include <ctype.h>
21#include <inttypes.h> 21#include <inttypes.h>
22#include <math.h> 22#include <math.h>
23#include <stdbool.h>
23#include <stdio.h> 24#include <stdio.h>
24#include <stdlib.h> 25#include <stdlib.h>
25#include <stdbool.h> 26#include <string.h>
27#include <system.h>
28#include "id3.h"
26#include "debug.h" 29#include "debug.h"
27 30
28/* The fixed point math routines (with the exception of fp_atof) are based 31/* The fixed point math routines (with the exception of fp_atof) are based
@@ -326,3 +329,106 @@ long get_replaypeak(const char* str)
326 329
327 return peak; 330 return peak;
328} 331}
332
333/* Compare two strings, ignoring case, up to the end nil or another end of
334 * string character. E.g., if eos is '=', "a=" would equal "a". Returns
335 * true for a match, false otherwise.
336 * TODO: This should be placed somewhere else, as it could be useful in
337 * other places too.
338 */
339static bool str_equal(const char* s1, const char* s2, char eos)
340{
341 char c1 = 0;
342 char c2 = 0;
343
344 while (*s1 && *s2 && (*s1 != eos) && (*s2 != eos))
345 {
346 if ((c1 = toupper(*s1)) != (c2 = toupper(*s2)))
347 {
348 return false;
349 }
350
351 s1++;
352 s2++;
353 }
354
355 if (c1 == eos)
356 {
357 c1 = '\0';
358 }
359
360 if (c2 == eos)
361 {
362 c2 = '\0';
363 }
364
365 return c1 == c2;
366}
367
368/* Check for a ReplayGain tag conforming to the "VorbisGain standard". If
369 * found, set the mp3entry accordingly. If value is NULL, key is expected
370 * to be on the "key=value" format, and the comparion/extraction is done
371 * accordingly. buffer is where to store the text contents of the gain tags;
372 * up to length bytes (including end nil) can be written.
373 * Returns number of bytes written to the tag text buffer, or zero if
374 * no ReplayGain tag was found (or nothing was copied to the buffer for
375 * other reasons).
376 */
377long parse_replaygain(const char* key, const char* value,
378 struct mp3entry* entry, char* buffer, int length)
379{
380 const char* val = value;
381 char **p = NULL;
382 char eos = '\0';
383
384 if (!val)
385 {
386 if (!(val = strchr(key, '=')))
387 {
388 return 0;
389 }
390
391 val++;
392 eos = '=';
393 }
394
395 if (str_equal(key, "replaygain_track_gain", eos)
396 || (str_equal(key, "rg_radio", eos) && !entry->track_gain))
397 {
398 entry->track_gain = get_replaygain(val);
399 p = &(entry->track_gain_string);
400 }
401 else if (str_equal(key, "replaygain_album_gain", eos)
402 || (str_equal(key, "rg_audiophile", eos) && !entry->album_gain))
403 {
404 entry->album_gain = get_replaygain(val);
405 p = &(entry->album_gain_string);
406 }
407 else if (str_equal(key, "replaygain_track_peak", eos)
408 || (str_equal(key, "rg_peak", eos) && !entry->track_peak))
409 {
410 entry->track_peak = get_replaypeak(val);
411 }
412 else if (str_equal(key, "replaygain_album_peak", eos))
413 {
414 entry->album_peak = get_replaypeak(val);
415 }
416
417 if (p)
418 {
419 int len = strlen(val);
420
421 len = MIN(len, length - 1);
422
423 /* A few characters just isn't interesting... */
424 if (len > 1)
425 {
426 strncpy(buffer, val, len);
427 buffer[len] = 0;
428 *p = buffer;
429 return len + 1;
430 }
431 }
432
433 return 0;
434}