[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