[Ffmpeg-devel] [PATCH] FLV decoder metadata reading
Allan Hsu
allan
Fri Dec 8 23:49:41 CET 2006
>From the secret journal of Aurelien Jacobs:
[...]
> > +typedef struct {
> > + int hasVideo;
> > + int hasAudio;
> > [...]
> > + int isStereo;
>
> Please avoid camelCase style in var name.
>
The hasVideo and hasAudio names were inspired by the fields in the
flvenc.c FLVContext struct. I assumed that since the names were fine in
the muxer, they should be fine in the demuxer... Regardless, I've taken
out the camelcasing.
[...]
> Maybe this could be simplified with something like this
> (note that it is untested):
>
> + if (!strcmp(buffer, "stereo")) {
> + if (!amf_get_object(ioc, AMF_DATA_TYPE_BOOL, &bool))
> + context->isStereo = bool;
> + } else if (!amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl) {
> + if(!strcmp(buffer, "duration")) s->duration = AV_TIME_BASE * dbl;
> + else if(!strcmp(buffer, "width")) context->width = dbl;
> + else if(!strcmp(buffer, "height")) context->height = dbl;
> + else if(!strcmp(buffer, "audiocodecid")) context->audiocodecid = dbl;
> + else if(!strcmp(buffer, "videocodecid")) context->videocodecid = dbl;
> + else if(!strcmp(buffer, "audiosamplerate")) context->samplerate = dbl;
> + else if(!strcmp(buffer, "audiosamplesize")) context->samplesize = dbl;
> + }
>
I've changed the structure of this section to match your suggestion (and
tested it).
Here is the new patch, with requested changes.
-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,309 @@
#include "avformat.h"
#include "flv.h"
+typedef struct {
+ int has_video;
+ int has_audio;
+ int is_stereo;
+ int videocodecid;
+ int audiocodecid;
+ int width;
+ int height;
+ int samplerate;
+ int samplesize;
+} 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, "stereo")) {
+ if(!amf_get_object(ioc, AMF_DATA_TYPE_BOOL, &bool)) context->is_stereo = bool;
+ } else if(!amf_get_object(ioc, AMF_DATA_TYPE_NUMBER, &dbl)) {
+ if(!strcmp(buffer, "duration")) s->duration = dbl * AV_TIME_BASE;
+ else if(!strcmp(buffer, "width")) context->width = dbl;
+ else if(!strcmp(buffer, "height")) context->height = dbl;
+ else if(!strcmp(buffer, "audiocodecid")) context->audiocodecid = dbl;
+ else if(!strcmp(buffer, "videocodecid")) context->videocodecid = dbl;
+ else if(!strcmp(buffer, "audiosamplerate")) context->samplerate = dbl;
+ else if(!strcmp(buffer, "audiosamplesize")) context->samplesize = dbl;
+ }
+ }
+
+ 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->has_audio) {
+ 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->has_video) {
+ 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->has_video) {
+ 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->has_audio) {
+ 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->is_stereo ? 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 +346,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->has_video = 1;
+ if(flags & FLV_HEADER_FLAG_HASAUDIO)
+ context->has_audio = 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 +379,8 @@
}
}
- url_fseek(&s->pb, offset, SEEK_SET);
+ url_fseek(&s->pb, offset, SEEK_SET);
+ }
s->start_time = 0;
@@ -71,76 +389,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 +437,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 +474,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 +485,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 +520,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