Newer
Older
Garrick Meeker
committed
/*
* FLV demuxer
Garrick Meeker
committed
* Copyright (c) 2003 The FFmpeg Project.
*
* This demuxer will generate a 1 byte extradata for VP6F content.
* It is composed of:
* - upper 4bits: difference between encoded width and visible width
* - lower 4bits: difference between encoded height and visible height
*
Diego Biurrun
committed
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
Garrick Meeker
committed
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
Diego Biurrun
committed
* version 2.1 of the License, or (at your option) any later version.
Garrick Meeker
committed
*
Diego Biurrun
committed
* FFmpeg is distributed in the hope that it will be useful,
Garrick Meeker
committed
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
Diego Biurrun
committed
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Garrick Meeker
committed
*/
#include "avformat.h"
#include "flv.h"
Garrick Meeker
committed
static int flv_probe(AVProbeData *p)
{
const uint8_t *d;
d = p->buf;
if (d[0] == 'F' && d[1] == 'L' && d[2] == 'V' && d[3] < 5 && d[5]==0) {
return AVPROBE_SCORE_MAX;
Garrick Meeker
committed
}
return 0;
}
static void flv_set_audio_codec(AVFormatContext *s, AVStream *astream, int flv_codecid) {
AVCodecContext *acodec = astream->codec;
switch(flv_codecid) {
//no distinction between S16 and S8 PCM codec flags
case FLV_CODECID_PCM:
acodec->codec_id = acodec->bits_per_sample == 8 ? CODEC_ID_PCM_S8 :
#ifdef WORDS_BIGENDIAN
CODEC_ID_PCM_S16BE;
#else
CODEC_ID_PCM_S16LE;
#endif
break;
case FLV_CODECID_PCM_LE:
acodec->codec_id = acodec->bits_per_sample == 8 ? CODEC_ID_PCM_S8 : CODEC_ID_PCM_S16LE; break;
case FLV_CODECID_ADPCM: acodec->codec_id = CODEC_ID_ADPCM_SWF; break;
case FLV_CODECID_MP3 : acodec->codec_id = CODEC_ID_MP3 ; astream->need_parsing = AVSTREAM_PARSE_FULL; break;
case FLV_CODECID_NELLYMOSER_8HZ_MONO:
acodec->sample_rate = 8000; //in case metadata does not otherwise declare samplerate
case FLV_CODECID_NELLYMOSER:
acodec->codec_id = CODEC_ID_NELLYMOSER;
break;
default:
av_log(s, AV_LOG_INFO, "Unsupported audio codec (%x)\n", flv_codecid >> FLV_AUDIO_CODECID_OFFSET);
acodec->codec_tag = flv_codecid >> FLV_AUDIO_CODECID_OFFSET;
}
}
static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream, int flv_codecid) {
AVCodecContext *vcodec = vstream->codec;
switch(flv_codecid) {
case FLV_CODECID_H263 : vcodec->codec_id = CODEC_ID_FLV1 ; break;
case FLV_CODECID_SCREEN: vcodec->codec_id = CODEC_ID_FLASHSV; break;
case FLV_CODECID_VP6 : vcodec->codec_id = CODEC_ID_VP6F ;
case FLV_CODECID_VP6A :
if(flv_codecid == FLV_CODECID_VP6A)
vcodec->codec_id = CODEC_ID_VP6A;
if(vcodec->extradata_size != 1) {
vcodec->extradata_size = 1;
vcodec->extradata = av_malloc(1);
}
vcodec->extradata[0] = get_byte(s->pb);
return 1; // 1 byte body size adjustment for flv_read_packet()
default:
av_log(s, AV_LOG_INFO, "Unsupported video codec (%x)\n", flv_codecid);
vcodec->codec_tag = flv_codecid;
}
return 0;
}
static int amf_get_string(ByteIOContext *ioc, char *buffer, int buffsize) {
if(length >= buffsize) {
url_fskip(ioc, length);
}
get_buffer(ioc, buffer, length);
buffer[length] = '\0';
return length;
}
static int amf_parse_object(AVFormatContext *s, AVStream *astream, AVStream *vstream, const char *key, unsigned int max_pos, int depth) {
AVCodecContext *acodec, *vcodec;
ByteIOContext *ioc;
AMFDataType amf_type;
char str_val[256];
double num_val;
num_val = 0;
ioc = s->pb;
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
amf_type = get_byte(ioc);
switch(amf_type) {
case AMF_DATA_TYPE_NUMBER:
num_val = av_int2dbl(get_be64(ioc)); break;
case AMF_DATA_TYPE_BOOL:
num_val = get_byte(ioc); break;
case AMF_DATA_TYPE_STRING:
if(amf_get_string(ioc, str_val, sizeof(str_val)) < 0)
return -1;
break;
case AMF_DATA_TYPE_OBJECT: {
unsigned int keylen;
while(url_ftell(ioc) < max_pos - 2 && (keylen = get_be16(ioc))) {
url_fskip(ioc, keylen); //skip key string
if(amf_parse_object(s, NULL, NULL, NULL, max_pos, depth + 1) < 0)
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_MIXEDARRAY:
url_fskip(ioc, 4); //skip 32-bit max array index
while(url_ftell(ioc) < max_pos - 2 && amf_get_string(ioc, str_val, sizeof(str_val)) > 0) {
//this is the only case in which we would want a nested parse to not skip over the object
if(amf_parse_object(s, astream, vstream, str_val, max_pos, depth + 1) < 0)
return -1;
}
if(get_byte(ioc) != AMF_END_OF_OBJECT)
return -1;
break;
case AMF_DATA_TYPE_ARRAY: {
unsigned int arraylen, i;
arraylen = get_be32(ioc);
for(i = 0; i < arraylen && url_ftell(ioc) < max_pos - 1; i++) {
if(amf_parse_object(s, NULL, NULL, NULL, max_pos, depth + 1) < 0)
return -1; //if we couldn't skip, bomb out.
}
}
break;
case AMF_DATA_TYPE_DATE:
url_fskip(ioc, 8 + 2); //timestamp (double) and UTC offset (int16)
break;
default: //unsupported type, we couldn't skip
return -1;
}
if(depth == 1 && key) { //only look for metadata values when we are not nested and key != NULL
acodec = astream ? astream->codec : NULL;
vcodec = vstream ? vstream->codec : NULL;
if(amf_type == AMF_DATA_TYPE_BOOL) {
if(!strcmp(key, "stereo") && acodec) acodec->channels = num_val > 0 ? 2 : 1;
} else if(amf_type == AMF_DATA_TYPE_NUMBER) {
if(!strcmp(key, "duration")) s->duration = num_val * AV_TIME_BASE;
Michael Niedermayer
committed
// else if(!strcmp(key, "width") && vcodec && num_val > 0) vcodec->width = num_val;
// else if(!strcmp(key, "height") && vcodec && num_val > 0) vcodec->height = num_val;
else if(!strcmp(key, "audiocodecid") && acodec && 0 <= (int)num_val)
flv_set_audio_codec(s, astream, (int)num_val << FLV_AUDIO_CODECID_OFFSET);
else if(!strcmp(key, "videocodecid") && vcodec && 0 <= (int)num_val)
flv_set_video_codec(s, vstream, (int)num_val);
else if(!strcmp(key, "audiosamplesize") && acodec && 0 < (int)num_val) {
acodec->bits_per_sample = num_val;
//we may have to rewrite a previously read codecid because FLV only marks PCM endianness.
if(num_val == 8 && (acodec->codec_id == CODEC_ID_PCM_S16BE || acodec->codec_id == CODEC_ID_PCM_S16LE))
acodec->codec_id = CODEC_ID_PCM_S8;
}
else if(!strcmp(key, "audiosamplerate") && acodec && num_val >= 0) {
//some tools, like FLVTool2, write consistently approximate metadata sample rates
if (!acodec->sample_rate) {
switch((int)num_val) {
case 44000: acodec->sample_rate = 44100 ; break;
case 22000: acodec->sample_rate = 22050 ; break;
case 11000: acodec->sample_rate = 11025 ; break;
case 5000 : acodec->sample_rate = 5512 ; break;
default : acodec->sample_rate = num_val;
}
}
}
}
return 0;
}
static int flv_read_metabody(AVFormatContext *s, unsigned int next_pos) {
AMFDataType type;
AVStream *stream, *astream, *vstream;
ByteIOContext *ioc;
int i, keylen;
char buffer[11]; //only needs to hold the string "onMetaData". Anything longer is something we don't want.
astream = NULL;
vstream = NULL;
keylen = 0;
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)) < 0 || strcmp(buffer, "onMetaData"))
return -1;
//find the streams now so that amf_parse_object doesn't need to do the lookup every time it is called.
for(i = 0; i < s->nb_streams; i++) {
stream = s->streams[i];
if (stream->codec->codec_type == CODEC_TYPE_AUDIO) astream = stream;
else if(stream->codec->codec_type == CODEC_TYPE_VIDEO) vstream = stream;
}
//parse the second object (we want a mixed array)
if(amf_parse_object(s, astream, vstream, buffer, next_pos, 0) < 0)
return -1;
return 0;
}
static AVStream *create_stream(AVFormatContext *s, int is_audio){
AVStream *st = av_new_stream(s, is_audio);
if (!st)
return NULL;
st->codec->codec_type = is_audio ? CODEC_TYPE_AUDIO : CODEC_TYPE_VIDEO;
av_set_pts_info(st, 24, 1, 1000); /* 24 bit pts in ms */
return st;
}
Garrick Meeker
committed
static int flv_read_header(AVFormatContext *s,
AVFormatParameters *ap)
{
Michael Niedermayer
committed
int offset, flags;
Garrick Meeker
committed
url_fskip(s->pb, 4);
flags = get_byte(s->pb);
/* old flvtool cleared this field */
/* FIXME: better fix needed */
if (!flags) {
flags = FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO;
av_log(s, AV_LOG_WARNING, "Broken FLV file, which says no streams present, this might fail\n");
}
Garrick Meeker
committed
if((flags & (FLV_HEADER_FLAG_HASVIDEO|FLV_HEADER_FLAG_HASAUDIO))
!= (FLV_HEADER_FLAG_HASVIDEO|FLV_HEADER_FLAG_HASAUDIO))
s->ctx_flags |= AVFMTCTX_NOHEADER;
if(flags & FLV_HEADER_FLAG_HASVIDEO){
return AVERROR(ENOMEM);
}
if(flags & FLV_HEADER_FLAG_HASAUDIO){
return AVERROR(ENOMEM);
}
offset = get_be32(s->pb);
url_fseek(s->pb, offset, SEEK_SET);
Garrick Meeker
committed
Garrick Meeker
committed
return 0;
}
static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{
Baptiste Coudurier
committed
int ret, i, type, size, flags, is_audio, next, pos;
unsigned pts;
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);
pts |= get_byte(s->pb) << 24;
// av_log(s, AV_LOG_DEBUG, "type:%d, size:%d, pts:%d\n", type, size, pts);
if (url_feof(s->pb))
return AVERROR(EIO);
url_fskip(s->pb, 3); /* stream id, always 0 */
Garrick Meeker
committed
flags = 0;
next= size + url_ftell(s->pb);
if (type == FLV_TAG_TYPE_AUDIO) {
flags = get_byte(s->pb);
} else if (type == FLV_TAG_TYPE_VIDEO) {
flags = get_byte(s->pb);
Garrick Meeker
committed
} else {
if (type == FLV_TAG_TYPE_META && size > 13+1+4)
flv_read_metabody(s, next);
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);
Garrick Meeker
committed
}
/* now find stream */
for(i=0;i<s->nb_streams;i++) {
st = s->streams[i];
Garrick Meeker
committed
}
av_log(NULL, AV_LOG_ERROR, "invalid stream\n");
Michael Niedermayer
committed
s->ctx_flags &= ~AVFMTCTX_NOHEADER;
// av_log(NULL, AV_LOG_DEBUG, "%d %X %d \n", is_audio, flags, st->discard);
if( (st->discard >= AVDISCARD_NONKEY && !((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY || is_audio))
||(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);
if ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY)
av_add_index_entry(st, pos, pts, size, 0, AVINDEX_KEYFRAME);
Michael Niedermayer
committed
// if not streamed and no duration from metadata then seek to end to find the duration from the timestamps
if(!url_is_streamed(s->pb) && s->duration==AV_NOPTS_VALUE){
Michael Niedermayer
committed
int size;
const int pos= url_ftell(s->pb);
const int fsize= url_fsize(s->pb);
url_fseek(s->pb, fsize-4, SEEK_SET);
size= get_be32(s->pb);
url_fseek(s->pb, fsize-3-size, SEEK_SET);
if(size == get_be24(s->pb) + 11){
s->duration= get_be24(s->pb) * (int64_t)AV_TIME_BASE / 1000;
Michael Niedermayer
committed
}
url_fseek(s->pb, pos, SEEK_SET);
Michael Niedermayer
committed
}
if(!st->codec->sample_rate || !st->codec->bits_per_sample || (!st->codec->codec_id && !st->codec->codec_tag)) {
st->codec->channels = (flags & FLV_AUDIO_CHANNEL_MASK) == FLV_STEREO ? 2 : 1;
if((flags & FLV_AUDIO_CODECID_MASK) == FLV_CODECID_NELLYMOSER_8HZ_MONO)
Michael Niedermayer
committed
st->codec->sample_rate= 8000;
st->codec->sample_rate = (44100 << ((flags & FLV_AUDIO_SAMPLERATE_MASK) >> FLV_AUDIO_SAMPLERATE_OFFSET) >> 3);
st->codec->bits_per_sample = (flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;
flv_set_audio_codec(s, st, flags & FLV_AUDIO_CODECID_MASK);
size -= flv_set_video_codec(s, st, flags & FLV_VIDEO_CODECID_MASK);
ret= av_get_packet(s->pb, pkt, size - 1);
Garrick Meeker
committed
if (ret <= 0) {
return AVERROR(EIO);
Garrick Meeker
committed
}
/* note: we need to modify the packet size here to handle the last
packet */
pkt->size = ret;
Garrick Meeker
committed
pkt->stream_index = st->index;
if (is_audio || ((flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY))
pkt->flags |= PKT_FLAG_KEY;
Garrick Meeker
committed
return ret;
}
AVInputFormat flv_demuxer = {
Garrick Meeker
committed
"flv",
"flv format",
Garrick Meeker
committed
flv_probe,
flv_read_header,
flv_read_packet,
.extensions = "flv",
.value = CODEC_ID_FLV1,
};