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
This commit is contained in:
Magnus Holmgren 2005-07-27 11:54:33 +00:00
parent e44372ef18
commit 988ea2cffc
7 changed files with 236 additions and 97 deletions

View file

@ -118,6 +118,7 @@ enum codec_status codec_start(struct codec_api* api)
frequency_divider = 441;
ci->configure(DSP_SET_FREQUENCY, (int *)ci->id3->frequency);
codec_set_replaygain(ci->id3);
ci->request_buffer(&size, ci->id3->first_frame_offset);
ci->advance_buffer(size);

View file

@ -528,7 +528,7 @@ static bool get_apetag_info (struct mp3entry *entry, int fd)
if (rem_space > 1 &&
get_apetag_item (&temp_apetag, "replaygain_track_gain", temp_buffer, rem_space)) {
entry->track_gain = get_replaygain (entry->track_gain_str = temp_buffer);
entry->track_gain = get_replaygain (entry->track_gain_string = temp_buffer);
str_space = strlen (temp_buffer) + 1;
temp_buffer += str_space;
rem_space -= str_space;
@ -536,7 +536,7 @@ static bool get_apetag_info (struct mp3entry *entry, int fd)
if (rem_space > 1 &&
get_apetag_item (&temp_apetag, "replaygain_album_gain", temp_buffer, rem_space)) {
entry->album_gain = get_replaygain (entry->album_gain_str = temp_buffer);
entry->album_gain = get_replaygain (entry->album_gain_string = temp_buffer);
str_space = strlen (temp_buffer) + 1;
temp_buffer += str_space;
rem_space -= str_space;
@ -910,35 +910,11 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
} else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
name_length = 11;
p = &(entry->track_string);
} else if ((strncasecmp(temp, "RG_RADIO=", 9) == 0)
&& !entry->track_gain) {
entry->track_gain = get_replaygain(&temp[9]);
name_length = 8;
p = &(entry->track_gain_str);
} else if (strncasecmp(temp, "REPLAYGAIN_TRACK_GAIN=", 22) == 0) {
entry->track_gain = get_replaygain(&temp[22]);
name_length = 21;
p = &(entry->track_gain_str);
} else if ((strncasecmp(temp, "RG_AUDIOPHILE=", 14) == 0)
&& !entry->album_gain) {
entry->album_gain = get_replaygain(&temp[14]);
name_length = 13;
p = &(entry->album_gain_str);
} else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_GAIN=", 22) == 0) {
entry->album_gain = get_replaygain(&temp[22]);
name_length = 21;
p = &(entry->album_gain_str);
} else if ((strncasecmp(temp, "RG_PEAK=", 8) == 0)
&& !entry->track_peak) {
entry->track_peak = get_replaypeak(&temp[8]);
p = NULL;
} else if (strncasecmp(temp, "REPLAYGAIN_TRACK_PEAK=", 22) == 0) {
entry->track_peak = get_replaypeak(&temp[22]);
p = NULL;
} else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_PEAK=", 22) == 0) {
entry->album_peak = get_replaypeak(&temp[22]);
p = NULL;
} else {
int value_length = parse_replaygain(temp, NULL, entry, buffer,
buffer_remaining);
buffer_remaining -= value_length;
buffer += value_length;
p = NULL;
}

View file

@ -1388,15 +1388,15 @@ bool browse_id3(void)
#if CONFIG_HWCODEC == MASNONE
case 11:
lcd_puts(0, 0, str(LANG_ID3_TRACK_GAIN));
lcd_puts(0, 1, id3->track_gain_str
? id3->track_gain_str
lcd_puts(0, 1, id3->track_gain_string
? id3->track_gain_string
: (char*) str(LANG_ID3_NO_GAIN));
break;
case 12:
lcd_puts(0, 0, str(LANG_ID3_ALBUM_GAIN));
lcd_puts(0, 1, id3->album_gain_str
? id3->album_gain_str
lcd_puts(0, 1, id3->album_gain_string
? id3->album_gain_string
: (char*) str(LANG_ID3_NO_GAIN));
break;
#endif

View file

@ -119,8 +119,8 @@ struct mp3entry {
/* replaygain support */
#if CONFIG_HWCODEC == MASNONE
char* track_gain_str;
char* album_gain_str;
char* track_gain_string;
char* album_gain_string;
long track_gain; /* 7.24 signed fixed point. 0 for no gain. */
long album_gain;
long track_peak; /* 7.24 signed fixed point. 0 for no peak. */

View file

@ -20,7 +20,11 @@
#ifndef _REPLAYGAIN_H
#define _REPLAYGAIN_H
#include "id3.h"
long get_replaygain(const char* str);
long get_replaypeak(const char* str);
long parse_replaygain(const char* key, const char* value,
struct mp3entry* entry, char* buffer, int length);
#endif

View file

@ -41,6 +41,7 @@
#include "id3.h"
#include "mp3data.h"
#include "system.h"
#include "replaygain.h"
#define UNSYNC(b0,b1,b2,b3) (((long)(b0 & 0x7F) << (3*7)) | \
((long)(b1 & 0x7F) << (2*7)) | \
@ -163,6 +164,10 @@ char* id3_get_codec(const struct mp3entry* id3)
Many ID3 symbolic names come in more than one form. You can add both
forms, each referencing the same variable in struct mp3entry.
If both forms are present, the last found will be used.
Note that the offset can be zero, in which case no entry will be set
in the mp3entry struct; the frame is still read into the buffer and
the special processing function is called (several times, if there
are several frames with the same name).
4. Alternately, use the TAG_LIST_ENTRY macro with
ID3 tag symbolic name,
@ -305,6 +310,34 @@ static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos )
}
}
#if CONFIG_HWCODEC == MASNONE
/* parse user defined text, looking for replaygain information. */
static int parseuser( struct mp3entry* entry, char* tag, int bufferpos )
{
char* value = NULL;
int desc_len = strlen(tag);
int value_len = 0;
if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) {
/* At least part of the value was read, so we can safely try to
* parse it
*/
value = tag + desc_len + 1;
value_len = parse_replaygain(tag, value, entry, tag,
bufferpos - (tag - entry->id3v2buf));
}
if (value_len) {
bufferpos = tag - entry->id3v2buf + value_len;
} else {
bufferpos = tag - entry->id3v2buf;
}
return bufferpos;
}
#endif
static const struct tag_resolver taglist[] = {
{ "TPE1", 4, offsetof(struct mp3entry, artist), NULL },
{ "TP1", 3, offsetof(struct mp3entry, artist), NULL },
@ -319,6 +352,9 @@ static const struct tag_resolver taglist[] = {
{ "TCOM", 4, offsetof(struct mp3entry, composer), NULL },
{ "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre },
{ "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre },
#if CONFIG_HWCODEC == MASNONE
{ "TXXX", 4, 0, &parseuser },
#endif
};
#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0])))
@ -327,12 +363,12 @@ static const struct tag_resolver taglist[] = {
string. If it is, we attempt to convert it to a 8-bit ASCII string
(for valid 8-bit ASCII characters). If it's not unicode, we leave
it alone. At some point we should fully support unicode strings */
static int unicode_munge(char** string, int *len) {
static int unicode_munge(char* string, int *len) {
long tmp;
bool le = false;
int i;
char *str = *string;
char *outstr = *string;
char *str = string;
char *outstr = string;
bool bom = false;
int outlen;
@ -343,8 +379,14 @@ static int unicode_munge(char** string, int *len) {
/* Type 0x00 is ordinary ISO 8859-1 */
if(str[0] == 0x00) {
(*len)--;
(*string)++; /* Skip the encoding type byte */
int i = --(*len);
/* We must move the string to the left */
while (i--) {
string[0] = string[1];
string++;
}
return 0;
}
@ -352,6 +394,11 @@ static int unicode_munge(char** string, int *len) {
if(str[0] == 0x01 || str[0] == 0x02) {
(*len)--;
str++;
i = 0;
/* Handle frames with more than one string (needed for TXXX frames).
*/
do {
tmp = BYTES2INT(0, 0, str[0], str[1]);
/* Now check if there is a BOM (zero-width non-breaking space, 0xfeff)
@ -377,8 +424,6 @@ static int unicode_munge(char** string, int *len) {
le = true;
}
i = 0;
outlen = *len / 2;
do {
@ -396,9 +441,12 @@ static int unicode_munge(char** string, int *len) {
str += 2;
} while((str[0] || str[1]) && (i < outlen));
*len = i;
str += 2;
outstr[i++] = 0; /* Terminate the string */
} while(i < outlen);
*len = i - 1;
outstr[i] = 0; /* Terminate the string */
return 0;
}
@ -686,29 +734,33 @@ static void setid3v2title(int fd, struct mp3entry *entry)
for (i=0; i<TAGLIST_SIZE; i++) {
const struct tag_resolver* tr = &taglist[i];
char** ptag = (char**) (((char*)entry) + tr->offset);
char** ptag = tr->offset ? (char**) (((char*)entry) + tr->offset)
: NULL;
char* tag;
if( !*ptag && !memcmp( header, tr->tag, tr->tag_length ) ) {
if( (!ptag || !*ptag) && !memcmp( header, tr->tag, tr->tag_length ) ) {
/* found a tag matching one in tagList, and not yet filled */
tag = buffer + bufferpos;
if(global_unsynch && version <= ID3_VER_2_3)
bytesread = read_unsynched(fd, buffer + bufferpos,
framelen);
bytesread = read_unsynched(fd, tag, framelen);
else
bytesread = read(fd, buffer + bufferpos, framelen);
bytesread = read(fd, tag, framelen);
if( bytesread != framelen )
return;
size -= bytesread;
*ptag = buffer + bufferpos;
if(unsynch || (global_unsynch && version >= ID3_VER_2_4))
bytesread = unsynchronize_frame(*ptag, bytesread);
bytesread = unsynchronize_frame(tag, bytesread);
unicode_munge( tag, &bytesread );
if (ptag)
*ptag = tag;
unicode_munge( ptag, &bytesread );
tag = *ptag;
/* remove trailing spaces */
while ( bytesread > 0 && isspace(tag[bytesread-1]))
bytesread--;

View file

@ -20,9 +20,12 @@
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <system.h>
#include "id3.h"
#include "debug.h"
/* The fixed point math routines (with the exception of fp_atof) are based
@ -326,3 +329,106 @@ long get_replaypeak(const char* str)
return peak;
}
/* Compare two strings, ignoring case, up to the end nil or another end of
* string character. E.g., if eos is '=', "a=" would equal "a". Returns
* true for a match, false otherwise.
* TODO: This should be placed somewhere else, as it could be useful in
* other places too.
*/
static bool str_equal(const char* s1, const char* s2, char eos)
{
char c1 = 0;
char c2 = 0;
while (*s1 && *s2 && (*s1 != eos) && (*s2 != eos))
{
if ((c1 = toupper(*s1)) != (c2 = toupper(*s2)))
{
return false;
}
s1++;
s2++;
}
if (c1 == eos)
{
c1 = '\0';
}
if (c2 == eos)
{
c2 = '\0';
}
return c1 == c2;
}
/* Check for a ReplayGain tag conforming to the "VorbisGain standard". If
* found, set the mp3entry accordingly. If value is NULL, key is expected
* to be on the "key=value" format, and the comparion/extraction is done
* accordingly. buffer is where to store the text contents of the gain tags;
* up to length bytes (including end nil) can be written.
* Returns number of bytes written to the tag text buffer, or zero if
* no ReplayGain tag was found (or nothing was copied to the buffer for
* other reasons).
*/
long parse_replaygain(const char* key, const char* value,
struct mp3entry* entry, char* buffer, int length)
{
const char* val = value;
char **p = NULL;
char eos = '\0';
if (!val)
{
if (!(val = strchr(key, '=')))
{
return 0;
}
val++;
eos = '=';
}
if (str_equal(key, "replaygain_track_gain", eos)
|| (str_equal(key, "rg_radio", eos) && !entry->track_gain))
{
entry->track_gain = get_replaygain(val);
p = &(entry->track_gain_string);
}
else if (str_equal(key, "replaygain_album_gain", eos)
|| (str_equal(key, "rg_audiophile", eos) && !entry->album_gain))
{
entry->album_gain = get_replaygain(val);
p = &(entry->album_gain_string);
}
else if (str_equal(key, "replaygain_track_peak", eos)
|| (str_equal(key, "rg_peak", eos) && !entry->track_peak))
{
entry->track_peak = get_replaypeak(val);
}
else if (str_equal(key, "replaygain_album_peak", eos))
{
entry->album_peak = get_replaypeak(val);
}
if (p)
{
int len = strlen(val);
len = MIN(len, length - 1);
/* A few characters just isn't interesting... */
if (len > 1)
{
strncpy(buffer, val, len);
buffer[len] = 0;
*p = buffer;
return len + 1;
}
}
return 0;
}