diff options
Diffstat (limited to 'src/decoder')
-rw-r--r-- | src/decoder/mp3_plugin.c | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/src/decoder/mp3_plugin.c b/src/decoder/mp3_plugin.c index 3a908299f..6fedf3766 100644 --- a/src/decoder/mp3_plugin.c +++ b/src/decoder/mp3_plugin.c @@ -203,6 +203,95 @@ mp3_fill_buffer(struct mp3_data *data) } #ifdef HAVE_ID3TAG +/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */ +static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gain_info) +{ + struct id3_frame const * frame; + + id3_latin1_t const *id; + id3_byte_t const *data; + id3_length_t length; + int found; + + enum { + CHANNEL_OTHER = 0x00, + CHANNEL_MASTER_VOLUME = 0x01, + CHANNEL_FRONT_RIGHT = 0x02, + CHANNEL_FRONT_LEFT = 0x03, + CHANNEL_BACK_RIGHT = 0x04, + CHANNEL_BACK_LEFT = 0x05, + CHANNEL_FRONT_CENTRE = 0x06, + CHANNEL_BACK_CENTRE = 0x07, + CHANNEL_SUBWOOFER = 0x08 + }; + + found = 0; + + /* relative volume adjustment information */ + + frame = id3_tag_findframe(tag, "RVA2", 0); + if (!frame) return 0; + + id = id3_field_getlatin1(id3_frame_field(frame, 0)); + data = id3_field_getbinarydata(id3_frame_field(frame, 1), + &length); + + if (!id || !data) return 0; + + /* + * "The 'identification' string is used to identify the + * situation and/or device where this adjustment should apply. + * The following is then repeated for every channel + * + * Type of channel $xx + * Volume adjustment $xx xx + * Bits representing peak $xx + * Peak volume $xx (xx ...)" + */ + + while (length >= 4) { + unsigned int peak_bytes; + + peak_bytes = (data[3] + 7) / 8; + if (4 + peak_bytes > length) + break; + + if (data[0] == CHANNEL_MASTER_VOLUME) { + signed int voladj_fixed; + double voladj_float; + + /* + * "The volume adjustment is encoded as a fixed + * point decibel value, 16 bit signed integer + * representing (adjustment*512), giving +/- 64 + * dB with a precision of 0.001953125 dB." + */ + + voladj_fixed = (data[1] << 8) | (data[2] << 0); + voladj_fixed |= -(voladj_fixed & 0x8000); + + voladj_float = (double) voladj_fixed / 512; + + replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = voladj_float; + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = voladj_float; + + g_debug("parseRVA2: Relative Volume " + "%+.1f dB adjustment (%s)\n", + voladj_float, id); + + found = 1; + break; + } + + data += 4 + peak_bytes; + length -= 4 + peak_bytes; + } + + return found; +} +#endif + +#ifdef HAVE_ID3TAG static struct replay_gain_info * parse_id3_replay_gain_info(struct id3_tag *tag) { @@ -244,6 +333,11 @@ parse_id3_replay_gain_info(struct id3_tag *tag) free(value); } + if (!found) { + /* fall back on RVA2 if no replaygain tags found */ + found = parse_rva2(tag, replay_gain_info); + } + if (found) return replay_gain_info; replay_gain_info_free(replay_gain_info); |