增加一个 AVFormat 格式:PES

我们首先来看大致一个格式是怎么用到的。

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

你可能感兴趣的:(ffmpeg,Codec)