[PATCH] detect where trailing metadata begins in mp3 files so =

David Byron none dbyron
Mon Sep 27 10:29:03 CEST 2010

 not returned from av_read_frame=0A=
 Changelog                     |    2 +=0A=
 libavcodec/mpegaudio.h        |    4 +=0A=
 libavcodec/mpegaudio_parser.c |   30 ++++++++-=0A=
 libavformat/mp3.c             |  135 =
 4 files changed, 167 insertions(+), 4 deletions(-)=0A=
diff --git a/Changelog b/Changelog=0A=
index a76cec1..289466d 100644=0A=
--- a/Changelog=0A=
+++ b/Changelog=0A=
@@ -4,6 +4,8 @@ releases are sorted from youngest to oldest.=0A=
 version <next>:=0A=
+- detect where trailing metadata begins in mp3 files so it's not=0A=
+  returned from av_read_frame=0A=
 - WebM support in Matroska de/muxer=0A=
 - low overhead Ogg muxing=0A=
 - MMS-TCP support=0A=
diff --git a/libavcodec/mpegaudio.h b/libavcodec/mpegaudio.h=0A=
index e2ad911..a838cc8 100644=0A=
--- a/libavcodec/mpegaudio.h=0A=
+++ b/libavcodec/mpegaudio.h=0A=
@@ -190,6 +190,10 @@ void ff_mpa_synth_filter_float(MPADecodeContext *s,=0A=
 void ff_mpegaudiodec_init_mmx(MPADecodeContext *s);=0A=
 void ff_mpegaudiodec_init_altivec(MPADecodeContext *s);=0A=
+struct MpegAudioParseContext;=0A=
+void ff_mpegaudio_parse_set_trailing_metadata_offset(struct =
MpegAudioParseContext *s,=0A=
+                                                     int64_t =
 /* fast header check for resync */=0A=
 static inline int ff_mpa_check_header(uint32_t header){=0A=
     /* header */=0A=
diff --git a/libavcodec/mpegaudio_parser.c =
index 6d7ab8a..84a8cb8 100644=0A=
--- a/libavcodec/mpegaudio_parser.c=0A=
+++ b/libavcodec/mpegaudio_parser.c=0A=
@@ -25,12 +25,17 @@=0A=
 #include "mpegaudiodecheader.h"=0A=
-typedef struct MpegAudioParseContext {=0A=
+struct MpegAudioParseContext {=0A=
     ParseContext pc;=0A=
     int frame_size;=0A=
     uint32_t header;=0A=
     int header_count;=0A=
-} MpegAudioParseContext;=0A=
+    /**=0A=
+     * -1 if no trailing metadata is detected, otherwise the=0A=
+     * offset where trailing metadata begins=0A=
+     */=0A=
+    int64_t     trailing_metadata_offset;=0A=
 #define MPA_HEADER_SIZE 4=0A=
@@ -83,12 +88,21 @@ static int mpegaudio_parse(AVCodecParserContext *s1,=0A=
                            const uint8_t **poutbuf, int *poutbuf_size,=0A=
                            const uint8_t *buf, int buf_size)=0A=
-    MpegAudioParseContext *s =3D s1->priv_data;=0A=
+    struct MpegAudioParseContext *s =3D s1->priv_data;=0A=
     ParseContext *pc =3D &s->pc;=0A=
     uint32_t state=3D pc->state;=0A=
     int i;=0A=
     int next=3D END_NOT_FOUND;=0A=
+    if ((s->trailing_metadata_offset !=3D -1) &&=0A=
+        (s1->cur_offset >=3D s->trailing_metadata_offset)) {=0A=
+        av_log(avctx, AV_LOG_DEBUG, "%s: giving up at offset: %" PRId64=0A=
+               " due to trailing metadata\n", =
+        *poutbuf =3D NULL;=0A=
+        *poutbuf_size =3D 0;=0A=
+        return buf_size;=0A=
+    }=0A=
     for(i=3D0; i<buf_size; ){=0A=
             int inc=3D FFMIN(buf_size - i, s->frame_size);=0A=
@@ -139,10 +153,18 @@ static int mpegaudio_parse(AVCodecParserContext =
     return next;=0A=
+ff_mpegaudio_parse_set_trailing_metadata_offset(struct =
MpegAudioParseContext *s,=0A=
+                                                int64_t =
+    assert(s);=0A=
+    s->trailing_metadata_offset =3D trailing_metadata_offset;=0A=
 AVCodecParser mpegaudio_parser =3D {=0A=
-    sizeof(MpegAudioParseContext),=0A=
+    sizeof(struct MpegAudioParseContext),=0A=
diff --git a/libavformat/mp3.c b/libavformat/mp3.c=0A=
index c1622a3..a2191b8 100644=0A=
--- a/libavformat/mp3.c=0A=
+++ b/libavformat/mp3.c=0A=
@@ -19,9 +19,12 @@=0A=
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA =
02110-1301 USA=0A=
+#include <assert.h>=0A=
 #include <strings.h>=0A=
 #include "libavutil/avstring.h"=0A=
 #include "libavutil/intreadwrite.h"=0A=
+#include "libavcodec/mpegaudio.h"=0A=
+#include "apetag.h"=0A=
 #include "avformat.h"=0A=
 #include "id3v2.h"=0A=
 #include "id3v1.h"=0A=
@@ -33,6 +36,117 @@=0A=
 /* mp3 read */=0A=
+ * Find the offset where trailing metadata begins.  If there=0A=
+ * are multiple items of trailing metadata (e.g. APE +=0A=
+ * lyrics3 + id3v1), this function finds the offset of the=0A=
+ * beginning of them all.  If there is space between the=0A=
+ * items, this function doesn't look before the space.=0A=
+ *=0A=
+ * Assume that the current offset of s->pb is the first byte=0A=
+ * of audio data.  We need this since the offsets we have=0A=
+ * available when parsing packets is relative to the=0A=
+ * beginning of the audio.=0A=
+ *=0A=
+ * @retval 0 successfully populated s->priv_data (an=0A=
+ * MP3Context)->trailing_metadata_offset with the offset of=0A=
+ * trailing metadata (-1 if there is none)=0A=
+ */=0A=
+static int ff_mp3_find_trailing_metadata(AVFormatContext *s,=0A=
+                                         AVStream *st)=0A=
+    int64_t ape_offset;=0A=
+    uint64_t ape_size;=0A=
+    int64_t id3v1_offset;=0A=
+    int64_t trailing_offset;=0A=
+    int ret;=0A=
+    struct MpegAudioParseContext *mpeg_parse_ctxt;=0A=
+    int64_t     audio_begin;=0A=
+    assert(s);=0A=
+    assert(st);=0A=
+    audio_begin =3D url_ftell(s->pb);=0A=
+    if (audio_begin < 0)=0A=
+        return audio_begin;=0A=
+    /* MpegAudioParseContext is the right type because=0A=
+       avcodec/mpegaudio_parse.c defines mpegaudio_parser=0A=
+       with codec_ids containing CODEC_ID_MP3 and=0A=
+       priv_data_size to sizeof(MpegAudioParseContext).  As=0A=
+       well, mp3_read_header sets st->codec->codec_id to=0A=
+       CODEC_ID_MP3. */=0A=
+    assert(st->parser);=0A=
+    mpeg_parse_ctxt =3D st->parser->priv_data;=0A=
+    assert(mpeg_parse_ctxt);=0A=
+    trailing_offset =3D -1;=0A=
+    /* First look for the id3v1 tag at the end */=0A=
+    ret =3D ff_id3v1_offset(s,&id3v1_offset);=0A=
+    if (ret < 0)=0A=
+        goto done;=0A=
+    if (id3v1_offset !=3D -1)=0A=
+        av_log(s, AV_LOG_VERBOSE, "%s: \"%s\": id3v1 tag at offset %"=0A=
+               PRId64 "(0x%" PRIx64 ")\n", __FUNCTION__, s->filename,=0A=
+               id3v1_offset, id3v1_offset);=0A=
+    trailing_offset =3D id3v1_offset;=0A=
+    /* Because of the way ff_id3v1_offset works, we don't=0A=
+       need to make sure the id3v1 tag is at the very end of=0A=
+       the file. */=0A=
+    /* Now look for an APE tag.  Pass 0 because we don't=0A=
+       care how many fields it's got. */=0A=
+    ret =3D ff_ape_offset(s,id3v1_offset,&ape_offset,&ape_size,0);=0A=
+    if (ret < 0)=0A=
+        goto done;=0A=
+    assert((id3v1_offset =3D=3D -1) || (ape_offset < id3v1_offset));=0A=
+    if (ape_offset !=3D -1) {=0A=
+        if (id3v1_offset !=3D -1) {=0A=
+            /* If the APE tag isn't immediately before the=0A=
+               id3v1 tag, we're done */=0A=
+            if ((ape_offset + ape_size) !=3D id3v1_offset) {=0A=
+                av_log(s, AV_LOG_WARNING, "%s: \"%s\": gap between end =
of "=0A=
+                       "APE tag and start of id3v1 tag", __FUNCTION__,=0A=
+                       s->filename);=0A=
+                /* Just give the offset of the id3v1 tag so=0A=
+                   we don't ditch the info between the=0A=
+                   tags */=0A=
+                trailing_offset =3D id3v1_offset;=0A=
+                goto done;=0A=
+            }=0A=
+        }=0A=
+        trailing_offset =3D ape_offset;=0A=
+    }=0A=
+ done:=0A=
+    if (audio_begin > trailing_offset) {=0A=
+        av_log(s, AV_LOG_ERROR, "%s: \"%s\": beginning of audio is =
after "=0A=
+               "trailing metadata offset -- something's wrong\n",=0A=
+               __FUNCTION__, s->filename);=0A=
+        return -1;=0A=
+    }=0A=
+    trailing_offset -=3D audio_begin;=0A=
+    av_log(s, AV_LOG_DEBUG, "%s: \"%s\": trailing metadata offset =
relative "=0A=
+           "to audio: %" PRId64 "\n", __FUNCTION__, s->filename,=0A=
+           trailing_offset);=0A=
+    ff_mpegaudio_parse_set_trailing_metadata_offset(mpeg_parse_ctxt,=0A=
+                                                    trailing_offset);=0A=
+    url_fseek(s->pb, 0, SEEK_SET);=0A=
+    return ret;=0A=
 static int mp3_read_probe(AVProbeData *p)=0A=
     int max_frames, first_frames =3D 0;=0A=
@@ -143,6 +257,7 @@ static int mp3_read_header(AVFormatContext *s,=0A=
     AVStream *st;=0A=
     int64_t off;=0A=
+    int ret;=0A=
     st =3D av_new_stream(s, 0);=0A=
     if (!st)=0A=
@@ -153,6 +268,13 @@ static int mp3_read_header(AVFormatContext *s,=0A=
     st->need_parsing =3D AVSTREAM_PARSE_FULL;=0A=
     st->start_time =3D 0;=0A=
+    /* Initialize the parser so we can store the offset of=0A=
+       trailing metadata there */=0A=
+    assert(!st->parser);=0A=
+    st->parser =3D av_parser_init(st->codec->codec_id);=0A=
+    if (!st->parser)=0A=
+        return -1;=0A=
     // lcm of all mp3 sample rates=0A=
     av_set_pts_info(st, 64, 1, 14112000);=0A=
@@ -165,6 +287,18 @@ static int mp3_read_header(AVFormatContext *s,=0A=
     if (mp3_parse_vbr_tags(s, st, off) < 0)=0A=
         url_fseek(s->pb, off, SEEK_SET);=0A=
+    /* Eventually we may want to parse metadata from APE or=0A=
+       lyrics3 tags, but for now find the offset of trailing=0A=
+       metadata (i.e. lyrics3/APE/id3v1 tags with id3v1 at=0A=
+       the end (if present), but the other two in either=0A=
+       order before that.  If we do decide to read all the=0A=
+       trailing metadata, perhaps this function becomes=0A=
+       ff_mp3_read_trailing_metadata and supercedes=0A=
+       ff_id3v1_read. */=0A=
+    ret =3D ff_mp3_find_trailing_metadata(s,st);=0A=
+    if (ret < 0)=0A=
+        return ret;=0A=
     /* the parameters will be extracted from the compressed bitstream */=0A=
     return 0;=0A=
@@ -174,6 +308,7 @@ static int mp3_read_header(AVFormatContext *s,=0A=
 static int mp3_read_packet(AVFormatContext *s, AVPacket *pkt)=0A=
     int ret, size;=0A=
     //    AVStream *st =3D s->streams[0];=0A=
     size=3D MP3_PACKET_SIZE;=0A=
-- =0A=

Content-Type: text/plain;
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;

#include <assert.h>=0A=
#include <inttypes.h>=0A=
#include <libavcodec/avcodec.h>=0A=
#include <libavformat/avformat.h>=0A=
#include <libavformat/avio.h>=0A=
#include <stdio.h>=0A=
#include <stdint.h>=0A=
main ( int      argc,=0A=
       char     **argv )=0A=
    AVFormatParameters  ap;=0A=
    static char         errbuf[512];=0A=
    const char          *filename;=0A=
    AVFormatContext     *format_context;=0A=
    unsigned long       num_frames;=0A=
    AVPacket            packet;=0A=
    int                 retval;=0A=
    unsigned long       total_bytes;=0A=
    if (argc < 2)=0A=
        printf("usage: %s filename\n",argv[0]);=0A=
        return 1;=0A=
    filename =3D argv[1];=0A=
    ** Tell the parser that we only want frame data.  In=0A=
    ** other words, we don't care about metadata that comes=0A=
    ** before or after frames=0A=
    format_context =3D avformat_alloc_context();=0A=
    if (!format_context)=0A=
        printf("avformat_alloc_context failed\n");=0A=
        return 1;=0A=
    //    format_context->debug =3D FF_FDEBUG_TS;=0A=
    ap.prealloced_context =3D 1;=0A=
    retval =3D av_open_input_file(&format_context,filename,NULL,0,&ap);=0A=
    if (retval !=3D 0)=0A=
        printf("av_open_input_file(%s) failed (%d) -- =
        format_context =3D NULL;=0A=
        return 1;=0A=
    num_frames =3D 0;=0A=
    total_bytes =3D 0;=0A=
    while (av_read_frame(format_context,&packet) =3D=3D 0)=0A=
        if (packet.size > 0)=0A=
            total_bytes +=3D packet.size;=0A=
            printf("\"%s\": frame %ld: offset: %" PRId64 ", size %d "=0A=
    format_context =3D NULL;=0A=
    printf("\"%s\": %ld frame(s), %ld byte(s)\n",filename,num_frames,=0A=
    return 0;=0A=


More information about the ffmpeg-devel mailing list