我们首先来看大致一个格式是怎么用到的。
lavf 公共格式:
AVFormatContext {
AVInputFormat *iformat; //输入格式
AVOutputFormat *oformat; //或者输出格式
void *priv_data; //私有上下文
AVIOContext *pb; //输入输出上下文
unsigned int nb_streams; //几路流
AVStream *streams[]; //流
char filename[1024]; //文件名字
AVPacketList *packet_buffer; //换成没有解码的包,比如用作AVFMT_FLAG_GENPTS
};
AVInputFormat{
const char *name; //格式的名字
const AVClass *priv_class; //格式的参数option等静态信息
int (*read_header)(struct AVFormatContext *);//读取文件头来初始化AVFormatContext
int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);
int (*read_close)(struct AVFormatContext *);
};
AVStream{
int index; //反向索引,AVFormatContext.streams[]的下标
AVCodecContext *codec; //文件头里面的编解码信息
void *priv_data;
AVRational time_base; //时间单位,比如1/29.4秒
int64_t duration; //从比特率和文件大小来估计
int skip_to_keyframe; //read_frame_internal用来只返回关键帧
};
AVPacket{
AVBufferRef *buf; //指向存放数据的buffer, 不为空表示引用计数
int64_t pts; //解码呈现给用户的时间
int64_t dts; //包被解码的时间
uint8_t *data;
int size;
int stream_index; //反向索引
int duration; //等于下一个pts - 当前pts
int64_t pos; //在流里面第几个字节
};
输入设备被当成demuxer, demuxer又被表示成AVInputFormat。
pkt.duration? probe pes?
上述两个问题是想直接把PES作为AVPacket传给ffmpeg要解决的。
如果支持pes探测,则pes自带pts,dts, 则
duration=next_pts - this_pts.
如果只给ffmpeg ES,不带pts,dts,我测试的效果是音频和视频完全不同步。
测试probe audio.pes可以,但是video.pes不行。
假设我们写了一个ff_pes_demuxer,
"-f pes" 对应
AVInputFormat *file_iformat = av_find_input_format("pes");
省去了格式探测过程。
在compute_pkt_fields里面,
如果pkt->duration 为零,则通过r_frame_rate,time_base, pkt->size来估计;
否则用它去更新PacketList里面的dts/pts/duration。
用ffmpeg生成的hls.ts,分析packet的duration也是零,
按理说能正常播的话,就侧面证明这个字段在demuxing时,可以置为零。
增加格式pes
====
在libavformat/Makefile加一行:
OBJS-$(CONFIG_PES_DEMUXER) += pes.o
在libavformat/allformats.c加一行:
REGISTER_DEMUXER (PES, pes);
然后重新configure就可以了。
参数传递
====
AVFormat.OputputFormat.Class.Option
obj 必须指向顶层结构比如AVFormat等。
AVFormatContext *ofmt_ctx;
av_opt_set_int(obj = ofmt_ctx, "hls_wrap", 5, AV_OPT_SEARCH_CHILDREN);
另一个相关的是把参数集放到词典里:
AVInputFormat
*file_iformat;
AVDictionary *format_opts = NULL;
av_dict_set(&format_opts, "codec_id", "0x15000", 0); // ./ffprobe
audio.pes -codec_id 0x15000
file_iformat = av_find_input_format("pes"); // ./ffprobe -f pes
avformat_open_input(&ic, filename, file_iformat, &format_opts);
常用的codec_id有:
AV_CODEC_ID_MPEG2VIDEO = 2
AV_CODEC_ID_H264 = 28
AV_CODEC_ID_MP2 = 0x15000
公共接口:
avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
主要是打开文件,探测格式:
avio_open2(&s->pb, filename);
av_probe_input_buffer2();
s->iformat->read_header(s);
avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
st = ic->streams[i]; //i \in [0, ic->nb_streams)
在ffm_read_header,pmt_cb里面,mpegts_set_stream_info-->
avpriv_set_pts_info(st, 33, 1, 90000);设置st->time_base,然后赋给
st->codec->time_base = st->time_base;
read_frame_internal(ic, &pkt1)-->ff_read_packet
对于一些没有文件头的格式,比如mpeg, 会动态的avformat_new_stream,
比如在pmt_cb和mpegts_push_data时。
codec = avcodec_find_decoder(st->codec->codec_id);
通常这些没有:
frame_size, sample_fmt, samples_rate, channels
width, pix_fmt,
打开avcodec_open2(st->codec, codec, options)来解码这些参数,
比如mpegaudiodec_template.c:decode_frame-->avpriv_mpegaudio_decode_header
mpeg12dec.c:decode_chunks-->
mpeg1_decode_sequence
mpeg_decode_postinit -->ff_set_dimensions
mpeg音频只需要读文件头就可以了,mpeg视频稍微复杂些,要去找SEQ_START_CODE,
h264就更复杂了,不是两句话就说的清楚的,请参考[2]
int
av_read_frame(AVFormatContext *s, AVPacket *pkt)
返回一个流里面的下一帧,并猜测包的pts/dts/duration。
通常情形下,视频pkt包含完整一帧数据,音频pkt包含连续几帧数据。
异常情况下,pkt可能包含部分帧,帧的后面部分,帧间的无效数据。
比如mpegts.c:mpegts_push_data, pes头里面没有指定大小。
附我为ffmpeg写的第一个扩展:
测试
录制的流可以播放,音视频正常同步, 不过场景变化快时有些许马赛克。
TODO
[mpeg @ 0x1a5ca10] buffer underflow st=1 bufi=3440 size=6912
[mpeg @ 0x1a5ca10] packet too large, ignoring buffer limits to mux it
[mpeg @ 0x1a5ca10] buffer underflow st=1 bufi=3440 size=6912
参考
[2] http://stackoverflow.com/questions/6394874/fetching-the-dimensions-of-a-h264video-stream