[FFmpeg-devel] [PATCH 3/3] Add Lame tag when updating Xing header
Giovanni Motta
giovanni.motta at gmail.com
Sat May 24 04:40:16 CEST 2014
Add Lame tag to the Xing/Info header of mp3 files.
Fixes ticket #3577
A few notes/comments:
- A failing FATE test has been updated with new CRC.
- The Lame info tag is generated by libmp3lame and passed to the mp3enc via extradata.
- To keep the size of the tag constant and simplify the code, vbr_scale is always added.
- The Lame string vendor in the tag is fixed length, so vendor is trimmed
to 9 bytes and padded with 0x20 if shorter.
- replay_gain and find_peak need a version of lame patched with
libmp3lame/lame.c Revision 1.367 (patch tracker item #66): http://sourceforge.net/p/lame/patches/66/
They have no effect otherwise.
- find_peak_sample only works if Lame is configured with --enable-decoder.
It has no effect otherwise.
- Some fields in the Lame tag are not set because not accessible from
the set/get API (preset and noise shaping, for example). I will bring this to
the attention of the Lame developers and help there with any change if we
decide to merge the patch.
Thanks
Giovanni
---
libavformat/mp3enc.c | 99 ++++++++++++++++++++++++++++++++++++++++---------
tests/ref/lavf-fate/mp3 | 2 +-
2 files changed, 83 insertions(+), 18 deletions(-)
diff --git a/libavformat/mp3enc.c b/libavformat/mp3enc.c
index 43f7656..05e1be2 100644
--- a/libavformat/mp3enc.c
+++ b/libavformat/mp3enc.c
@@ -80,6 +80,11 @@ static int id3v1_create_tag(AVFormatContext *s, uint8_t *buf)
// maximum size of the xing frame: offset/Xing/flags/frames/size/TOC
#define XING_MAX_SIZE (32 + 4 + 4 + 4 + 4 + XING_TOC_SIZE)
+#define XING_FLAG_FRAMES 0x01
+#define XING_FLAG_SIZE 0x02
+#define XING_FLAG_TOC 0x04
+#define XING_FLAG_VBR_SCALE 0x08
+
typedef struct MP3Context {
const AVClass *class;
ID3v2EncContext id3;
@@ -109,6 +114,15 @@ typedef struct MP3Context {
static const uint8_t xing_offtbl[2][2] = {{32, 17}, {17, 9}};
+/* Bitrates used to determine the size of a Xing header (MPEG1 and MPEG2) in kbps */
+#define XING_BITRATE1 128
+#define XING_BITRATE2 64
+#define XING_BITRATE25 32
+
+/* Size in bytes of the Lame tag added to the Xing/Info header */
+#define LAME_EXT_SIZE 40
+#define LAME_EXT_VENDOR_LEN 9
+
/*
* Write an empty XING header and initialize respective data.
*/
@@ -125,22 +139,46 @@ static int mp3_write_xing(AVFormatContext *s)
int xing_offset;
int ver = 0;
int bytes_needed;
- const char *vendor = (s->flags & AVFMT_FLAG_BITEXACT) ? "Lavf" : LIBAVFORMAT_IDENT;
+ int kbps_header;
if (!s->pb->seekable || !mp3->write_xing)
return 0;
+ /*
+ * Xing VBR pretends to be a 48kbs layer III frame. (at 44.1kHz).
+ * (at 48kHz they use 56kbs since 48kbs frame not big enough for
+ * table of contents)
+ * let's always embed Xing header inside a 64kbs layer III frame.
+ * this gives us enough room for a LAME version string too.
+ * size determined by sampling frequency (MPEG1)
+ * 32kHz: 216 bytes@ 48kbs 288 bytes@ 64kbs
+ * 44.1kHz: 156 bytes@ 48kbs 208 bytes@ 64kbs (+1 if padding = 1)
+ * 48kHz: 144 bytes@ 48kbs 192 bytes@ 64kbs
+ *
+ * MPEG 2 values are the same since the framesize and samplerate
+ * are each reduced by a factor of 2.
+ */
for (i = 0; i < FF_ARRAY_ELEMS(avpriv_mpa_freq_tab); i++) {
const uint16_t base_freq = avpriv_mpa_freq_tab[i];
-
- if (codec->sample_rate == base_freq) ver = 0x3; // MPEG 1
- else if (codec->sample_rate == base_freq / 2) ver = 0x2; // MPEG 2
- else if (codec->sample_rate == base_freq / 4) ver = 0x0; // MPEG 2.5
- else continue;
-
+ if (codec->sample_rate == base_freq) {
+ ver = 0x3; // MPEG 1
+ kbps_header = XING_BITRATE1;
+ } else if (codec->sample_rate == base_freq / 2) {
+ ver = 0x2; // MPEG 2
+ kbps_header = XING_BITRATE2;
+ } else if (codec->sample_rate == base_freq / 4) {
+ ver = 0x0; // MPEG 2.5
+ kbps_header = XING_BITRATE25;
+ } else {
+ continue;
+ }
srate_idx = i;
break;
}
+
+ if (!mp3->has_variable_bitrate)
+ kbps_header = codec->bit_rate;
+
if (i == FF_ARRAY_ELEMS(avpriv_mpa_freq_tab)) {
av_log(s, AV_LOG_WARNING, "Unsupported sample rate, not writing Xing header.\n");
return -1;
@@ -162,7 +200,7 @@ static int mp3_write_xing(AVFormatContext *s)
for (bitrate_idx = 1; bitrate_idx < 15; bitrate_idx++) {
int bit_rate = 1000 * avpriv_mpa_bitrate_tab[ver != 3][3 - 1][bitrate_idx];
- int error = FFABS(bit_rate - codec->bit_rate);
+ int error = FFABS(bit_rate - kbps_header);
if (error < best_bitrate_error) {
best_bitrate_error = error;
@@ -182,11 +220,11 @@ static int mp3_write_xing(AVFormatContext *s)
bytes_needed = 4 // header
+ xing_offset
+ 4 // xing tag
- + 4 // frames/size/toc flags
+ + 4 // frames/size/toc/vbr_scale flags
+ 4 // frames
+ 4 // size
- + XING_TOC_SIZE // toc
- + 24
+ + XING_TOC_SIZE // toc
+ + LAME_EXT_SIZE // lame extension (includes vbr_scale)
;
if (bytes_needed <= mpah.frame_size)
@@ -200,7 +238,7 @@ static int mp3_write_xing(AVFormatContext *s)
ffio_fill(s->pb, 0, xing_offset);
mp3->xing_offset = avio_tell(s->pb);
ffio_wfourcc(s->pb, "Xing");
- avio_wb32(s->pb, 0x01 | 0x02 | 0x04); // frames / size / TOC
+ avio_wb32(s->pb, XING_FLAG_FRAMES | XING_FLAG_SIZE | XING_FLAG_TOC | XING_FLAG_VBR_SCALE); // frames / size / TOC / vbr_scale
mp3->size = mpah.frame_size;
mp3->want=1;
@@ -214,11 +252,8 @@ static int mp3_write_xing(AVFormatContext *s)
for (i = 0; i < XING_TOC_SIZE; ++i)
avio_w8(s->pb, (uint8_t)(255 * i / XING_TOC_SIZE));
- for (i = 0; i < strlen(vendor); ++i)
- avio_w8(s->pb, vendor[i]);
- for (; i < 21; ++i)
+ for (i = 0; i < LAME_EXT_SIZE; ++i)
avio_w8(s->pb, 0);
- avio_wb24(s->pb, FFMAX(codec->delay - 528 - 1, 0)<<12);
ffio_fill(s->pb, 0, mpah.frame_size - bytes_needed);
@@ -324,6 +359,8 @@ static int mp3_queue_flush(AVFormatContext *s)
static void mp3_update_xing(AVFormatContext *s)
{
MP3Context *mp3 = s->priv_data;
+ AVCodecContext *codec = s->streams[mp3->audio_stream_idx]->codec;
+ const char *vendor = (s->flags & AVFMT_FLAG_BITEXACT) ? "Lavf" : LIBAVFORMAT_IDENT;
int i;
/* replace "Xing" identification string with "Info" for CBR files. */
@@ -344,6 +381,34 @@ static void mp3_update_xing(AVFormatContext *s)
avio_w8(s->pb, FFMIN(seek_point, 255));
}
+ /* add Lame Info tag */
+ if (codec->extradata_size == LAME_EXT_SIZE) {
+ int64_t lame_tag_offset = mp3->xing_offset
+ + 4 // "Xing" or "Info"
+ + 4 // frames/size/toc/vbr_scale flags
+ + 4 // frame count
+ + 4 // stream size
+ + 100; // toc
+
+ /* extradata starts with vbr_scale */
+ avio_seek(s->pb, lame_tag_offset, SEEK_SET);
+
+ /* copy the tag from extradata */
+ avio_write(s->pb, codec->extradata, codec->extradata_size);
+
+ /* replace vendor string */
+ avio_seek(s->pb, lame_tag_offset + 4, SEEK_SET); // rewind and skip vbr_scale
+ for (i = 0; i < FFMIN(strlen(vendor), LAME_EXT_VENDOR_LEN); ++i)
+ avio_w8(s->pb, vendor[i]);
+ for (; i < LAME_EXT_VENDOR_LEN; ++i)
+ avio_w8(s->pb, 0x20);
+ } else if (codec->extradata_size != 0) {
+ av_log(s, AV_LOG_ERROR,
+ "lame info tag in extradata has incorrect size (%d vs. %d bytes)\n",
+ codec->extradata_size, LAME_EXT_SIZE);
+ }
+
+ avio_flush(s->pb);
avio_seek(s->pb, 0, SEEK_END);
}
@@ -363,7 +428,7 @@ static int mp3_write_trailer(struct AVFormatContext *s)
avio_write(s->pb, buf, ID3v1_TAG_SIZE);
}
- if (mp3->xing_offset)
+ if (mp3->write_xing && mp3->xing_offset)
mp3_update_xing(s);
return 0;
diff --git a/tests/ref/lavf-fate/mp3 b/tests/ref/lavf-fate/mp3
index 6f201e0..5381e0e 100644
--- a/tests/ref/lavf-fate/mp3
+++ b/tests/ref/lavf-fate/mp3
@@ -1,3 +1,3 @@
-6bdea919dc6856d76ef2553698e2b0d3 *./tests/data/lavf-fate/lavf.mp3
+e062b13f521a06e5ee579a48600974a0 *./tests/data/lavf-fate/lavf.mp3
96376 ./tests/data/lavf-fate/lavf.mp3
./tests/data/lavf-fate/lavf.mp3 CRC=0x6c9850fe
--
1.9.1.423.g4596e3a
More information about the ffmpeg-devel
mailing list