[Ffmpeg-devel] [PATCH] FLV decoder metadata reading

Allan Hsu allan
Fri Dec 8 09:41:32 CET 2006


This is my second try at the FLV decoder metadata patch. I've tried to
adhere to the submission guidelines and Michael's suggestions. Please
let me know if this patch is acceptable or if I still need to do
anything before it can be merged in SVN.

	-Allan
-- 
Allan Hsu <allan at counterpop dot net>
1E64 E20F 34D9 CBA7 1300 1457 AC37 CBBB 0E92 C779
-------------- next part --------------
Index: libavformat/flvdec.c
===================================================================
--- libavformat/flvdec.c	(revision 7260)
+++ libavformat/flvdec.c	(working copy)
@@ -27,6 +27,317 @@
 #include "avformat.h"
 #include "flv.h"
 
+typedef struct {
+    int hasVideo;
+    int hasAudio;
+    int videocodecid;
+    int audiocodecid;
+    int width;
+    int height;
+    int samplerate;
+    int samplesize;
+    int isStereo;
+} FLVDemuxContext;
+
+typedef struct {
+    unsigned int pos;
+    unsigned int prev_tag_size;
+    unsigned int next_pos;
+    int type;
+    int body_size;
+    int pts;
+} FLVTagInfo;
+
+inline static void create_vp6_extradata(AVStream *stream) {
+    if(stream->codec->extradata_size != 1) {
+        stream->codec->extradata_size = 1;
+        stream->codec->extradata = av_malloc(1);
+    }
+}
+
+static int amf_skip_object(ByteIOContext *ioc, AMFDataType *type) {
+    AMFDataType objectType;
+
+    objectType = (type != NULL ? *type : get_byte(ioc));
+    switch(objectType) {
+        case      AMF_DATA_TYPE_NUMBER:
+            url_fskip(ioc, 8); break; //double precision float
+        case        AMF_DATA_TYPE_BOOL:
+            url_fskip(ioc, 1); break; //byte
+        case      AMF_DATA_TYPE_STRING:
+            url_fskip(ioc, get_be16(ioc)); break;
+        case      AMF_DATA_TYPE_OBJECT: {
+            unsigned int keylen;
+
+            for(keylen = get_be16(ioc); keylen != 0; keylen = get_be16(ioc)) {
+                url_fskip(ioc, keylen); //skip key string
+                if(amf_skip_object(ioc, NULL) < 0) //skip the following object
+                    return -1; //if we couldn't skip, bomb out.
+            }
+            if(get_byte(ioc) != AMF_END_OF_OBJECT)
+                return -1;
+        }
+            break;
+        case        AMF_DATA_TYPE_NULL:
+        case   AMF_DATA_TYPE_UNDEFINED:
+        case AMF_DATA_TYPE_UNSUPPORTED:
+            break; //these take up no additional space
+        case       AMF_DATA_TYPE_ARRAY: {
+            unsigned int arraylen, i;
+
+            arraylen = get_be32(ioc);
+            for(i = 0; i < arraylen; i++)
+                amf_skip_object(ioc, NULL);
+        }
+            break;
+        case        AMF_DATA_TYPE_DATE:
+            url_fskip(ioc, 8 + 2); //timestamp (double) and UTC offset (int16)
+            break;
+        default: //unsupported, we couldn't skip.
+            return -1;
+    }
+
+    return 0;
+}
+
+static int amf_get_object(ByteIOContext *ioc, AMFDataType type, void *dest) {
+    AMFDataType actualType = get_byte(ioc);
+
+    if(actualType != type) {
+        //type was not the one we expected; skip object, don't touch dest, return error.
+        amf_skip_object(ioc, &actualType);
+        return -1;
+    }
+
+    //we currently only need these two types for metadata parsing.
+    switch(type) {
+        case AMF_DATA_TYPE_NUMBER:
+            *(double *)dest = av_int2dbl(get_be64(ioc));
+            break;
+        case   AMF_DATA_TYPE_BOOL:
+            *(unsigned char *)dest = get_byte(ioc);
+            break;
+        default:
+            return -1;
+    }
+
+    return 0;
+}
+
+static int amf_get_string(ByteIOContext *ioc, char *buffer, int buffsize) {
+    int length;
+
+    length = get_be16(ioc);
+    if(length > buffsize)
+        return -1; //string will not fit in buffer
+
+    get_buffer(ioc, buffer, length);
+
+    buffer[length] = '\0';
+
+    return length;
+}
+
+static int flv_read_tag_header(ByteIOContext *ioc, FLVTagInfo *info) {
+    info->pos = url_ftell(ioc);
+    info->prev_tag_size = get_be32(ioc);
+    info->type = get_byte(ioc);
+    info->body_size = get_be24(ioc);
+    info->pts = get_be24(ioc);
+//    av_log(s, AV_LOG_DEBUG, "type:%d, size:%d, pts:%d\n", info->type, info->body_size, info->pts);
+
+    if(url_feof(ioc))
+        return AVERROR_IO;
+
+    url_fskip(ioc, 4); /* reserved */
+
+    info->next_pos = info->body_size + url_ftell(ioc);
+
+    return 0;
+}
+
+static int flv_read_metabody(AVFormatContext *s, unsigned int next_pos) {
+    FLVDemuxContext *context;
+    AMFDataType type;
+    ByteIOContext *ioc;
+    int keylen;
+    unsigned int itemcount;
+    char buffer[256];
+    double dbl;
+    unsigned char bool;
+
+    context = s->priv_data;
+    ioc = &s->pb;
+
+    //first object needs to be "onMetaData" string
+    type = get_byte(ioc);
+    if(type != AMF_DATA_TYPE_STRING || amf_get_string(ioc, buffer, sizeof(buffer) - 1) < 0 || strcmp(buffer, "onMetaData") != 0)
+        goto bail;
+
+    //second object needs to be a mixedarray
+    type = get_byte(ioc);
+    if(type != AMF_DATA_TYPE_MIXEDARRAY)
+        goto bail;
+
+    //this number has been known to misreport the number of items in the mixed array, so we don't use it.
+    itemcount = get_be32(ioc);
+
+    while(url_ftell(ioc) < next_pos - 2 && (keylen = amf_get_string(ioc, buffer, sizeof(buffer) - 1)) > 0) {
+        if(       strcmp(buffer, "duration")        == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl ) == 0) s->duration           = dbl * AV_TIME_BASE;
+        } else if(strcmp(buffer, "width")           == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl ) == 0) context->width        = dbl;
+        } else if(strcmp(buffer, "height")          == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl ) == 0) context->height       = dbl;
+        } else if(strcmp(buffer, "audiocodecid")    == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl ) == 0) context->audiocodecid = dbl;
+        } else if(strcmp(buffer, "videocodecid")    == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl ) == 0) context->videocodecid = dbl;
+        } else if(strcmp(buffer, "stereo")          == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_BOOL  , &bool) == 0) context->isStereo     = bool;
+        } else if(strcmp(buffer, "audiosamplerate") == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl ) == 0) context->samplerate   = dbl;
+        } else if(strcmp(buffer, "audiosamplesize") == 0) {
+            if(amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl ) == 0) context->samplesize   = dbl;
+        } else if(amf_skip_object(ioc, NULL) < 0) {
+            goto bail;
+        }
+    }
+
+    if(keylen < 0 || get_byte(ioc) != AMF_END_OF_OBJECT)
+        goto bail;
+
+    url_fseek(ioc, next_pos, SEEK_SET);
+    return 0;
+
+bail:
+    url_fseek(ioc, next_pos, SEEK_SET);
+    return -1;
+}
+
+static int flv_validate_context(AVFormatContext *s) {
+    FLVDemuxContext *context = s->priv_data;
+
+    //if any values do not validate, assume metadata tool was brain dead and fail.
+    if(s->duration <= 0)
+        return -1;
+
+    if(context->hasAudio) {
+        switch(context->audiocodecid << FLV_AUDIO_CODECID_OFFSET) {
+            case FLV_CODECID_PCM_BE:
+            case FLV_CODECID_ADPCM:
+            case FLV_CODECID_MP3:
+            case FLV_CODECID_PCM_LE:
+            case FLV_CODECID_NELLYMOSER_8HZ_MONO:
+            case FLV_CODECID_NELLYMOSER:
+                break;
+            default:
+                return -1;
+        }
+
+        //flvtool (and maybe others) writes approximate sample rates for some awesome reason.
+        switch(context->samplerate) {
+            case 44100: case 44000: context->samplerate = 44100; break;
+            case 22050: case 22000: context->samplerate = 22050; break;
+            case 11025: case 11000: context->samplerate = 11025; break;
+            case  5512: case  5500: context->samplerate =  5512; break;
+            default:
+                return -1;
+        }
+
+        if(context->samplesize != 8 && context->samplesize != 16)
+            return -1;
+    }
+
+    if(context->hasVideo) {
+        switch(context->videocodecid) {
+            case FLV_CODECID_H263:
+            case FLV_CODECID_SCREEN:
+            case FLV_CODECID_VP6:
+                break;
+            default:
+                return -1;
+        }
+
+        if(context->height == 0 || context->width == 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+static int flv_create_streams(AVFormatContext *s) {
+    FLVDemuxContext *context;
+    AVStream *audioStream, *videoStream;
+    
+    context = s->priv_data;
+    audioStream = NULL;
+    videoStream = NULL;
+
+    if(context->hasVideo) {
+        videoStream = av_new_stream(s, 0);
+        if(videoStream == NULL)
+            return -1;
+
+        av_set_pts_info(videoStream, 24, 1, 1000);
+
+        videoStream->codec->codec_type = CODEC_TYPE_VIDEO;
+        videoStream->codec->width = context->width;
+        videoStream->codec->height = context->height;
+
+        switch(context->videocodecid) {
+            case   FLV_CODECID_H263:
+                videoStream->codec->codec_id = CODEC_ID_FLV1;
+                break;
+            case FLV_CODECID_SCREEN:
+                videoStream->codec->codec_id = CODEC_ID_FLASHSV;
+                break;
+            case    FLV_CODECID_VP6:
+                videoStream->codec->codec_id = CODEC_ID_VP6F;
+                create_vp6_extradata(videoStream);
+                break;
+            default:
+                av_log(s, AV_LOG_INFO, "Unsupported video codec in META tag: (%x)\n", context->videocodecid);
+                videoStream->codec->codec_tag = context->videocodecid;
+        }
+    }
+
+    if(context->hasAudio) {
+        audioStream = av_new_stream(s, 1);
+        if(audioStream == NULL)
+            return -1;
+
+        av_set_pts_info(audioStream, 24, 1, 1000);
+
+        audioStream->codec->codec_type = CODEC_TYPE_AUDIO;
+        audioStream->codec->channels = context->isStereo ? 2 : 1;
+        audioStream->codec->bits_per_sample = context->samplesize;
+        audioStream->codec->sample_rate = context->samplerate;
+
+        switch(context->audiocodecid << FLV_AUDIO_CODECID_OFFSET) {
+            case FLV_CODECID_PCM_BE:
+                audioStream->codec->codec_id = context->samplesize == 16 ? CODEC_ID_PCM_S16BE : CODEC_ID_PCM_S8;
+                break;
+            case FLV_CODECID_ADPCM:
+                audioStream->codec->codec_id = CODEC_ID_ADPCM_SWF;
+                break;
+            case FLV_CODECID_MP3:
+                audioStream->codec->codec_id = CODEC_ID_MP3;
+                break;
+            case FLV_CODECID_PCM_LE:
+                audioStream->codec->codec_id = context->samplesize == 16 ? CODEC_ID_PCM_S16LE : CODEC_ID_PCM_S8;
+                break;
+            case FLV_CODECID_NELLYMOSER_8HZ_MONO:
+            case FLV_CODECID_NELLYMOSER:
+            default:
+                av_log(s, AV_LOG_INFO, "Unsupported audio codec in META tag: (%x)\n", context->audiocodecid);
+                audioStream->codec->codec_tag = context->audiocodecid;
+        }
+    }
+
+    return 0;
+}
+
 static int flv_probe(AVProbeData *p)
 {
     const uint8_t *d;
@@ -43,15 +354,29 @@
 static int flv_read_header(AVFormatContext *s,
                            AVFormatParameters *ap)
 {
+    FLVTagInfo taginfo;
+    FLVDemuxContext *context = s->priv_data;
     int offset, flags, size;
 
-    s->ctx_flags |= AVFMTCTX_NOHEADER; //ok we have a header but theres no fps, codec type, sample_rate, ...
-
     url_fskip(&s->pb, 4);
     flags = get_byte(&s->pb);
 
     offset = get_be32(&s->pb);
 
+    if(flags & FLV_HEADER_FLAG_HASVIDEO)
+        context->hasVideo = 1;
+    if(flags & FLV_HEADER_FLAG_HASAUDIO)
+        context->hasAudio = 1;
+
+    //0 is a valid audio codec id, so set it to something that will cause a validation error if it does not get set in flv_read_metabody
+    context->audiocodecid = -1;
+
+    if(   flv_read_tag_header(&s->pb, &taginfo)  < 0 || taginfo.type != FLV_TAG_TYPE_META
+       || flv_read_metabody(s, taginfo.next_pos) < 0 || flv_validate_context(s) < 0
+       || flv_create_streams(s) < 0) {
+        //error reading first tag header, first tag is not metadata, or metadata incomplete.
+        s->ctx_flags |= AVFMTCTX_NOHEADER;
+
     if(!url_is_streamed(&s->pb)){
         const int fsize= url_fsize(&s->pb);
         url_fseek(&s->pb, fsize-4, SEEK_SET);
@@ -62,7 +387,8 @@
         }
     }
 
-    url_fseek(&s->pb, offset, SEEK_SET);
+        url_fseek(&s->pb, offset, SEEK_SET);
+    }
 
     s->start_time = 0;
 
@@ -71,76 +397,32 @@
 
 static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
 {
-    int ret, i, type, size, pts, flags, is_audio, next, pos;
+    FLVTagInfo taginfo;
+    int ret, i, flags, is_audio;
     AVStream *st = NULL;
 
  for(;;){
-    pos = url_ftell(&s->pb);
-    url_fskip(&s->pb, 4); /* size of previous packet */
-    type = get_byte(&s->pb);
-    size = get_be24(&s->pb);
-    pts = get_be24(&s->pb);
-//    av_log(s, AV_LOG_DEBUG, "type:%d, size:%d, pts:%d\n", type, size, pts);
-    if (url_feof(&s->pb))
+    if(flv_read_tag_header(&s->pb, &taginfo) < 0)
         return AVERROR_IO;
-    url_fskip(&s->pb, 4); /* reserved */
+        
     flags = 0;
 
-    if(size == 0)
+    if(taginfo.body_size == 0)
         continue;
 
-    next= size + url_ftell(&s->pb);
-
-    if (type == FLV_TAG_TYPE_AUDIO) {
+    if (taginfo.type == FLV_TAG_TYPE_AUDIO) {
         is_audio=1;
         flags = get_byte(&s->pb);
-    } else if (type == FLV_TAG_TYPE_VIDEO) {
+    } else if (taginfo.type == FLV_TAG_TYPE_VIDEO) {
         is_audio=0;
         flags = get_byte(&s->pb);
-    } else if (type == FLV_TAG_TYPE_META && size > 13+1+4) {
-        url_fskip(&s->pb, 13); //onMetaData blah
-        if(get_byte(&s->pb) == 8){
-            url_fskip(&s->pb, 4);
-        }
-        while(url_ftell(&s->pb) + 5 < next){
-            char tmp[128];
-            int type, len;
-            double d= 0;
-
-            len= get_be16(&s->pb);
-            if(len >= sizeof(tmp) || !len)
-                break;
-            get_buffer(&s->pb, tmp, len);
-            tmp[len]=0;
-
-            type= get_byte(&s->pb);
-            if(type == AMF_DATA_TYPE_NUMBER){
-                d= av_int2dbl(get_be64(&s->pb));
-            }else if(type == AMF_DATA_TYPE_STRING){
-                len= get_be16(&s->pb);
-                if(len >= sizeof(tmp))
-                    break;
-                url_fskip(&s->pb, len);
-            }else if(type == AMF_DATA_TYPE_MIXEDARRAY){
-                //array
-                break;
-            }else if(type == AMF_DATA_TYPE_DATE){
-                d= av_int2dbl(get_be64(&s->pb));
-                get_be16(&s->pb);
-            }
-
-            if(!strcmp(tmp, "duration")){
-                s->duration = d*AV_TIME_BASE;
-            }else if(!strcmp(tmp, "videodatarate")){
-            }else if(!strcmp(tmp, "audiodatarate")){
-            }
-        }
-        url_fseek(&s->pb, next, SEEK_SET);
+    } else if (taginfo.type == FLV_TAG_TYPE_META && taginfo.body_size > 13+1+4) {
+        flv_read_metabody(s, taginfo.next_pos);
         continue;
     } else {
         /* skip packet */
-        av_log(s, AV_LOG_ERROR, "skipping flv packet: type %d, size %d, flags %d\n", type, size, flags);
-        url_fseek(&s->pb, next, SEEK_SET);
+        av_log(s, AV_LOG_ERROR, "skipping flv packet: type %d, size %d, flags %d\n", taginfo.type, taginfo.body_size, flags);
+        url_fseek(&s->pb, taginfo.next_pos, SEEK_SET);
         continue;
     }
 
@@ -163,11 +445,11 @@
        ||(st->discard >= AVDISCARD_BIDIR  &&  ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER && !is_audio))
        || st->discard >= AVDISCARD_ALL
        ){
-        url_fseek(&s->pb, next, SEEK_SET);
+        url_fseek(&s->pb, taginfo.next_pos, SEEK_SET);
         continue;
     }
     if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY)
-        av_add_index_entry(st, pos, pts, size, 0, AVINDEX_KEYFRAME);
+        av_add_index_entry(st, taginfo.pos, taginfo.pts, taginfo.body_size, 0, AVINDEX_KEYFRAME);
     break;
  }
 
@@ -200,13 +482,10 @@
             case FLV_CODECID_SCREEN: st->codec->codec_id = CODEC_ID_FLASHSV; break;
             case FLV_CODECID_VP6   :
                 st->codec->codec_id = CODEC_ID_VP6F;
-                if (st->codec->extradata_size != 1) {
-                    st->codec->extradata_size = 1;
-                    st->codec->extradata = av_malloc(1);
-                }
+                create_vp6_extradata(st);
                 /* width and height adjustment */
                 st->codec->extradata[0] = get_byte(&s->pb);
-                size--;
+                taginfo.body_size--;
                 break;
             default:
                 av_log(s, AV_LOG_INFO, "Unsupported video codec (%x)\n", flags & FLV_VIDEO_CODECID_MASK);
@@ -214,14 +493,14 @@
             }
     }
 
-    ret= av_get_packet(&s->pb, pkt, size - 1);
+    ret= av_get_packet(&s->pb, pkt, taginfo.body_size - 1);
     if (ret <= 0) {
         return AVERROR_IO;
     }
     /* note: we need to modify the packet size here to handle the last
        packet */
     pkt->size = ret;
-    pkt->pts = pts;
+    pkt->pts = taginfo.pts;
     pkt->stream_index = st->index;
 
     if (is_audio || ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY))
@@ -249,7 +528,7 @@
 AVInputFormat flv_demuxer = {
     "flv",
     "flv format",
-    0,
+    sizeof(FLVDemuxContext),
     flv_probe,
     flv_read_header,
     flv_read_packet,



More information about the ffmpeg-devel mailing list