[FFmpeg-devel] [PATCH 2/3] Copy Lame tag to extradata

Giovanni Motta giovanni.motta at gmail.com
Sat May 24 04:40:15 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


---
 libavcodec/libmp3lame.c | 207 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 207 insertions(+)

diff --git a/libavcodec/libmp3lame.c b/libavcodec/libmp3lame.c
index 01b8c7b..2857c40 100644
--- a/libavcodec/libmp3lame.c
+++ b/libavcodec/libmp3lame.c
@@ -26,6 +26,8 @@

 #include <lame/lame.h>

+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
 #include "libavutil/channel_layout.h"
 #include "libavutil/common.h"
 #include "libavutil/float_dsp.h"
@@ -34,12 +36,16 @@
 #include "libavutil/opt.h"
 #include "avcodec.h"
 #include "audio_frame_queue.h"
+#include "bytestream.h"
 #include "internal.h"
 #include "mpegaudio.h"
 #include "mpegaudiodecheader.h"

 #define BUFFER_SIZE (7200 + 2 * MPA_FRAME_SIZE + MPA_FRAME_SIZE / 4+1000) // FIXME: Buffer size to small? Adding 1000 to make up for it.

+/* size of the Lame tag added to the Xing/Info header */
+#define LAME_EXT_SIZE 40
+
 typedef struct LAMEContext {
     AVClass *class;
     AVCodecContext *avctx;
@@ -183,6 +189,200 @@ error:
                        s->buffer_size - s->buffer_index);                   \
 } while (0)

+/* flow and comments adapted from PutLameVBR() in Lame's VbrTag.c */
+static int GetLameInfoTag(lame_global_flags const *gfp, uint8_t *extradata, int extradata_size)
+{
+    uint8_t *extradata_ptr = extradata;
+
+    int enc_delay = lame_get_encoder_delay(gfp);      // encoder delay
+    int enc_padding = lame_get_encoder_padding(gfp);  // encoder padding
+
+    /* recall: gfp->VBR_q is for example set by the switch -V */
+    /*         gfp->quality by -q, -h, -f, etc.               */
+    int nQuality = (100 - 10 * lame_get_VBR_q(gfp) - lame_get_quality(gfp));
+
+    const char *szVersion = get_lame_very_short_version();
+    uint8_t nVBR;
+    uint8_t nRevision = 0x00;
+    uint8_t nRevMethod;
+
+    /* numbering is different in vbr_mode vs. Lame tag */
+    uint8_t vbr_type_translator[] = { 1, 5, 3, 2, 4, 0, 3 };
+
+    uint8_t nLowpass =
+        (((lame_get_lowpassfreq(gfp) / 100.0) + .5) > 255 ?
+                255 : (lame_get_lowpassfreq(gfp) / 100.0) + .5);
+
+    uint32_t nPeakSignalAmplitude = 0;
+    uint16_t nRadioReplayGain = 0;
+    uint16_t nAudiophileReplayGain = 0;
+
+    uint8_t nNoiseShaping = 0;
+    uint8_t nStereoMode = 0;
+    int bNonOptimal = 0;
+    uint8_t nSourceFreq = 0;
+
+    uint8_t nMisc = 0;
+
+    size_t  nMusicLength = 0;
+    uint16_t nMusicCRC = 0;
+    uint16_t nTagCRC = 0;
+
+    /* psy model type: Gpsycho or NsPsytune */
+    unsigned char bExpNPsyTune = lame_get_exp_nspsytune(gfp) & 1;
+    unsigned char bSafeJoint = (lame_get_exp_nspsytune(gfp) & 2) != 0;
+
+    unsigned char bNoGapMore = 0;
+    unsigned char bNoGapPrevious = 0;
+
+    int nNoGapCount = lame_get_nogap_total(gfp);
+    int nNoGapCurr = lame_get_nogap_currentindex(gfp);
+
+    uint8_t nAthType = lame_get_ATHtype(gfp);  /* 4 bits */
+    int nInSampleRate = lame_get_in_samplerate(gfp);
+    uint8_t nFlags = 0;
+
+    int nABRBitrate;
+
+    /* Lame tag is fixed size */
+    if (extradata_size < LAME_EXT_SIZE)
+      return 0;
+
+    /* if ABR, {store bitrate <= 255} else {store "-b"} */
+    switch (lame_get_VBR(gfp)) {
+    case vbr_abr:
+        nABRBitrate = lame_get_VBR_mean_bitrate_kbps(gfp);
+        break;
+    case vbr_off:
+        nABRBitrate = lame_get_brate(gfp);
+        break;
+    default:          // vbr modes
+        nABRBitrate = lame_get_VBR_mean_bitrate_kbps(gfp);
+    }
+
+    /* revision and vbr method */
+    if (lame_get_VBR(gfp) < sizeof(vbr_type_translator))
+        nVBR = vbr_type_translator[lame_get_VBR(gfp)];
+    else
+        nVBR = 0x00;    // unknown
+    nRevMethod = 0x10 * nRevision + nVBR;
+
+
+    /* ReplayGain */
+    if (lame_get_findReplayGain(gfp)) {
+        int nRadioGain = lame_get_RadioGain(gfp);
+        if (nRadioGain > 0x1FE)
+            nRadioGain = 0x1FE;
+        if (nRadioGain < -0x1FE)
+            nRadioGain = -0x1FE;
+
+        nRadioReplayGain = 0x2000;  // set name code
+        nRadioReplayGain |= 0xC00;  // set originator code to "determined automatically"
+
+        if (nRadioGain >= 0) {
+            nRadioReplayGain |= nRadioGain;  // set gain adjustment
+        } else {
+            nRadioReplayGain |= 0x200;       // set the sign bit
+            nRadioReplayGain |= -nRadioGain; // set gain adjustment
+        }
+    }
+
+    /* peak sample */
+    if (lame_get_decode_on_the_fly(gfp))
+        nPeakSignalAmplitude = abs((int) ((lame_get_PeakSample(gfp) / 32767.0) * pow(2, 23) + .5));
+
+    /* nogap */
+    if (nNoGapCount != -1) {
+        if (nNoGapCurr > 0)
+            bNoGapPrevious = 1;
+
+        if (nNoGapCurr < nNoGapCount - 1)
+            bNoGapMore = 1;
+    }
+
+    /* flags */
+    nFlags = nAthType + (bExpNPsyTune << 4)
+        + (bSafeJoint << 5)
+        + (bNoGapMore << 6)
+        + (bNoGapPrevious << 7);
+
+    if (nQuality < 0)
+        nQuality = 0;
+
+    /* stereo mode field... a bit ugly */
+    switch (lame_get_mode(gfp)) {
+    case MONO:
+        nStereoMode = 0;
+        break;
+    case STEREO:
+        nStereoMode = 1;
+        break;
+    case DUAL_CHANNEL:
+        nStereoMode = 2;
+        break;
+    case JOINT_STEREO:
+        if (lame_get_force_ms(gfp))
+            nStereoMode = 4;
+        else
+            nStereoMode = 3;
+        break;
+    case NOT_SET:
+        // FALLTHROUGH
+    default:
+        nStereoMode = 7;
+        break;
+    }
+
+    /* intensity stereo : nStereoMode = 6. IS is not implemented */
+    if (nInSampleRate <= 32000)
+        nSourceFreq = 0x00;
+    else if (nInSampleRate == 48000)
+        nSourceFreq = 0x02;
+    else if (nInSampleRate > 48000)
+        nSourceFreq = 0x03;
+    else
+        nSourceFreq = 0x01;   // default is 44100Hz
+
+    /* check if the user overrided the default LAME behaviour with some nasty options */
+    if ((lame_get_no_short_blocks(gfp) == 1) ||     // short blocks dispensed
+        (lame_get_force_short_blocks(gfp) == 1) ||  // short blocks forced
+        ((lame_get_lowpassfreq(gfp) == -1) && (lame_get_highpassfreq(gfp) == -1)) ||    // "-k"
+        (lame_get_scale_left(gfp) < lame_get_scale_right(gfp)) ||
+        (lame_get_scale_left(gfp) > lame_get_scale_right(gfp)) ||
+        (lame_get_disable_reservoir(gfp) && lame_get_brate(gfp) < 320) ||
+        lame_get_noATH(gfp) || lame_get_ATHonly(gfp) || (nAthType == 0) ||
+        (nInSampleRate <= 32000))
+        bNonOptimal = 1;
+
+    nMisc = nNoiseShaping + (nStereoMode << 2)
+        + (bNonOptimal << 5)
+        + (nSourceFreq << 6);
+
+    /* write LAME_EXT_SIZE bytes to extradata */
+    bytestream_put_be32(&extradata_ptr, nQuality);
+    bytestream_put_buffer(&extradata_ptr, szVersion, 9);
+    bytestream_put_byte(&extradata_ptr, nRevMethod);
+    bytestream_put_byte(&extradata_ptr, nLowpass);
+    bytestream_put_be32(&extradata_ptr, nPeakSignalAmplitude);
+    bytestream_put_be16(&extradata_ptr, nRadioReplayGain);
+    bytestream_put_be16(&extradata_ptr, nAudiophileReplayGain);
+    bytestream_put_byte(&extradata_ptr, nFlags);
+    bytestream_put_byte(&extradata_ptr, (nABRBitrate >= 255)? 0xFF: nABRBitrate);
+    bytestream_put_byte(&extradata_ptr, enc_delay >> 4);
+    bytestream_put_byte(&extradata_ptr, (enc_delay << 4) + (enc_padding >> 8));
+    bytestream_put_byte(&extradata_ptr, enc_padding);
+    bytestream_put_byte(&extradata_ptr, nMisc);
+    bytestream_put_byte(&extradata_ptr, 0x00);    // unused in rev0
+    bytestream_put_be16(&extradata_ptr, 0x0000);
+    bytestream_put_be32(&extradata_ptr, (int) nMusicLength);
+    bytestream_put_be16(&extradata_ptr, nMusicCRC);
+    bytestream_put_be16(&extradata_ptr, nTagCRC);
+
+    av_assert1(extradata_ptr - extradata == LAME_EXT_SIZE);
+
+    return extradata_ptr - extradata;
+}
+
 static int mp3lame_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
                                 const AVFrame *frame, int *got_packet_ptr)
 {
@@ -220,6 +420,13 @@ static int mp3lame_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
     } else {
         lame_result = lame_encode_flush(s->gfp, s->buffer + s->buffer_index,
                                         s->buffer_size - s->buffer_index);
+        avctx->extradata = av_malloc(LAME_EXT_SIZE);
+        avctx->extradata_size = GetLameInfoTag(s->gfp, avctx->extradata, LAME_EXT_SIZE);
+        /* Lame tag is fixed size; we expect exactly LAME_EXT_SIZE bytes */
+        if (avctx->extradata_size != LAME_EXT_SIZE) {
+                av_log(avctx, AV_LOG_ERROR, "error storing Lame info tag\n");
+                avctx->extradata_size = 0;
+        }
     }
     if (lame_result < 0) {
         if (lame_result == -1) {
--
1.9.1.423.g4596e3a


More information about the ffmpeg-devel mailing list