ffplay如何支持多协议
来源:互联网 发布:网络摄像头用户名密码 编辑:程序博客网 时间:2024/06/08 02:26
分析过一个简化版的,现在开始搞新版本ffmpeg-3.0.2。ffmpeg的代码量太大了,必须得找准点,并且不能急躁,弄不懂的先放着,后面一步一步的解决。
插一个流程图的例子,这个例子足够可以画流程图啦.用markdown来画流程图,就简单了.平常一个流程图基本解决问题.
有一个网址有markdown的语法: https://www.zybuluo.com/mdeditor?url=https://www.zybuluo.com/static/editor/md-help.markdown
好,言归正传.
主要有两种测试,一种播放flv file文件,一个是播放rtmp。编解码是H264和AAC。
ffplay test.flv //这个是来自于磁盘的flv fileffplay rtmp://127.0.0.1:1935/live/test //这个是来自于网络rtmp协议
这一篇blog主要是分析ffplay如来应对下面三种逻辑的:
- 数据来源的不同,也是IO的不同,ffmpeg中叫protocol。譬如:file,rtmp.
- 数据的格式不同,也是封装的不同,ffmpeg中叫formt,譬如flv.
- 编解码的不同,ffmpeg中叫codec
针对上面三种结构体定义了很多对象.
1 如何确定IO和fmt
这两个的判断逻辑是揉在一块的,所以不得不在一起来分析.有一个重要函数:avformat_open_input ,在这个函数中进行了初始化,确定了proto(数据来源)和fmt(封装格式).
我添加了一些日志,分析一下播放test.flv的日志:
##########read_thread start //解析线程启动##########start avformat_open_input //准备初始化##########init_input start,pb:00000000,fmt:00000000 //初始化函数中,init_input最重要 ##########av_probe_input_format2 start //调用了av_probe_input_format3函数,开始探测fmt中,由于io没有开启,连Proto都没有定,探测没起到任何作用.##########io_open_default start //执行io_open_default,这个是先就定义好了的############ffurl_alloc,URLProtocol:file //在这里确定了URLProto,具体逻辑下面详细说.##########av_probe_input_buffer2 start //通过读取数据来确定fmt##########av_probe_input_format2 start //这是第二次探测,这一下有数据了##########av_probe_input_format3 start //这里会遍历所有fmt...Probing flv score:100 size:2048 //通过长度为2048的buf,flv检测为100分##########av_probe_input_format3 extensions:flv //并判断了后缀名....###############av_probe_input_buffer2 fmt:flv //这里就已经确定了FMT.
总结一下为,先通过文件名来确定是file proto(磁盘文件),之后读取到数据,解析2048个字节,判断出的fmt为flv.
分析一下播放rtmp://…的日志:逻辑基本一样.
##########read_thread start##########start avformat_open_input##########init_input start,pb:00000000,fmt:00000000##########av_probe_input_format2 start##########av_probe_input_format3 start//第一次探测,没用....##########init_input start io_open##########io_open_default start############ffurl_alloc,URLProtocol:rtmp //确定了RTMP##########ffurl_open_whitelist start############ffurl_alloc,URLProtocol:tcp //又来一个有一个PROTO???[rtmp @ 00d9f400] Handshaking... //在读取数据前,需要走rtmp的一些协议:握手,play命令的发送##########ffio_fdopen start //open##########init_input start2 av_probe_input_buffer2 //第二次探测##########av_probe_input_buffer2 start##########av_probe_input_format2 start##########av_probe_input_format3 start....##########av_probe_input_format3 traver fmt:flv,nodat:0Probing flv score:100 size:2048##########av_probe_input_format3 traver result,fmt:flv,score:100,max_score:100...###############av_probe_input_buffer2 fmt:flv
两个逻辑差不多,但是有两个震惊的是:
- rtmp的fmt想不到是FLV
- rtmp有两个PROTO,rmtp和tcp.相当于有两层
这两个逻辑要好好分析一下,留在下一篇吧.这一篇的重点不是如何获取数据,而是获取到数据后如来来判断.
有这么多PROTO,是通过链表来组织的,肯定在初始化中.
void av_register_all(void){ ... REGISTER_PROTOCOL(FILE, file); //file也是一种protocol. REGISTER_PROTOCOL(RTMP, rtmp);}//定义一个宏,所以要在search代码时,经常找不到,这就是写成了宏.对查询代码造成了一定的麻烦.#define REGISTER_PROTOCOL(X, x) \ { \ extern URLProtocol ff_##x##_protocol; \ if (CONFIG_##X##_PROTOCOL) \ ffurl_register_protocol(&ff_##x##_protocol); \ }//first_protocol就是一个链表的头,register proto就是插入到链表中.int ffurl_register_protocol(URLProtocol *protocol){ URLProtocol **p; p = &first_protocol; while (*p) p = &(*p)->next; *p = protocol; protocol->next = NULL; return 0;}//列出几个重要函数,open,read,write,这是操作IO的函数typedef struct URLProtocol { const char *name; int (*url_open)( URLContext *h, const char *url, int flags); int (*url_read)( URLContext *h, unsigned char *buf, int size); int (*url_write)(URLContext *h, const unsigned char *buf, int size);} URLProtocol;
fmt的初始化和Proto的类似.fmt又叫muxer和demuxer.用链表串起来,通过name来区别
void av_register_all(void){ REGISTER_MUXDEMUX(FLV, flv);}#define REGISTER_MUXER(X, x) \ { \ extern AVOutputFormat ff_##x##_muxer; \ if (CONFIG_##X##_MUXER) \ av_register_output_format(&ff_##x##_muxer); \ }#define REGISTER_DEMUXER(X, x) \ { \ extern AVInputFormat ff_##x##_demuxer; \ if (CONFIG_##X##_DEMUXER) \ av_register_input_format(&ff_##x##_demuxer); \ }#define REGISTER_MUXDEMUX(X, x) REGISTER_MUXER(X, x); REGISTER_DEMUXER(X, x)static const AVClass flv_class = { .class_name = "flvdec", .item_name = av_default_item_name, .option = options, .version = LIBAVUTIL_VERSION_INT,};AVInputFormat ff_flv_demuxer = { .name = "flv", .long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"), .priv_data_size = sizeof(FLVContext), .read_probe = flv_probe, .read_header = flv_read_header, .read_packet = flv_read_packet, .read_seek = flv_read_seek, .read_close = flv_read_close, .extensions = "flv", .priv_class = &flv_class,};
不同的URLProtocol就有了不同的读写方式,那重点就在于如何找到URLProtocol.
初始化完成后就得进入read,流程是:stream_open->read_thread->avformat_open_input->init_input,好了,到了init_input就要细看了,删掉了一些不关注的东西.
static int init_input(AVFormatContext *s, const char *filename, AVDictionary **options){ int ret; AVProbeData pd = { filename, NULL, 0 }; int score = AVPROBE_SCORE_RETRY; //av_probe_input_format2,这是第一次探测,io没打开,为什么要有这一步呢? if (( (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score)))) return score; //所以必须得走到这里来io_open. //io_open在avformat_open_input前面初始化 AVFormatContext *s 直接赋值的. if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0) return ret; //找到了Proto,并open,可以进行第二次探测. //av_probe_input_buffer2确定了fmt return av_probe_input_buffer2(s->pb, &s->iformat, filename, s, 0, s->format_probesize);}
重点要分析一下 s->io_open .先从它的赋值开始.
// s = avformat_alloc_context(),在alloc时调用 avformat_get_context_defaults .static void avformat_get_context_defaults(AVFormatContext *s){ memset(s, 0, sizeof(AVFormatContext)); s->av_class = &av_format_context_class; s->io_open = io_open_default; /////////就是它 s->io_close = io_close_default; av_opt_set_defaults(s);}
那就得直接跳到 io_open_default. ffmpeg用到了很多函数赋值,用C语言来实现面向对象的搞法.
io_open_default直接调用了 ffio_open_whitelist. ff应该是ffmpeg的前面两个ff的意思吧.
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist ){ URLContext *h; int err; // 1 url open,这一步是重点 err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist); // 2 io open,没有真正的open操作,而是赋值 err = ffio_fdopen(s, h); return 0;}int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist){ AVDictionary *tmp_opts = NULL; AVDictionaryEntry *e; // 又是在alloc,在alloc中有很多操作. int ret = ffurl_alloc(puc, filename, flags, int_cb); ret = ffurl_connect(*puc, options);}
在ffurl_alloc函数中进行了选择.很绕的,从 io_open_defualt->ffio_open_whitelist->ffurl_open_whitelist->ffurl_alloc
int ffurl_alloc(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb){ URLProtocol *p = NULL; p = url_find_protocol(filename); if (p) return url_alloc_for_protocol(puc, p, filename, flags, int_cb);}#define URL_SCHEME_CHARS \ "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "0123456789+-."static struct URLProtocol *url_find_protocol(const char *filename){ URLProtocol *up = NULL; char proto_str[128], proto_nested[128], *ptr; //若strspn()返回的数值为n,则代表字符串s 开头连续有n 个字符都是属于字符串accept内的字符。 // 如果是rtmp://...,则找到了rtmp size_t proto_len = strspn(filename, URL_SCHEME_CHARS); if (filename[proto_len] != ':' && (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) || is_dos_path(filename)) strcpy(proto_str, "file"); //如果没有提出协议,则是file else av_strlcpy(proto_str, filename, FFMIN(proto_len + 1, sizeof(proto_str))); //这一步是rtmp av_strlcpy(proto_nested, proto_str, sizeof(proto_nested)); //遍历proto,通过proto的name来进行判断 while (up = ffurl_protocol_next(up)) { if (!strcmp(proto_str, up->name)) break; if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME && !strcmp(proto_nested, up->name)) break; } return up;}
找到了proto后, ffurl_connect 做了啥?
int ffurl_connect(URLContext *uc, AVDictionary **options){ ... //重点是不是在这里: err = uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) : uc->prot->url_open(uc, uc->filename, uc->flags); uc->is_connected = 1; return 0;}
uc->prot 是 找到的proto,在 ffurl_alloc->url_alloc_for_protocol 进行了赋值. 调用了URLProtocol url_open.
在init_input中,确定了proto,开始探测出format.init_input->av_probe_input_format2->av_probe_input_format3.
//av_probe_input_format2 中获取到了数据int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size){ AVProbeData pd = { filename ? filename : "" }; uint8_t *buf = NULL; int ret = 0, probe_size, buf_offset = 0; int score = 0; int ret2; //PROBE_BUF_MIN = 2048 for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt; probe_size = FFMIN(probe_size << 1,FFMAX(max_probe_size, probe_size + 1))) { /* Read probe data. */ if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0) goto fail; // 1 读数据 if ((ret = avio_read(pb, buf + buf_offset, probe_size - buf_offset)) < 0) { } // 2 Guess file format. *fmt = av_probe_input_format2(&pd, 1, &score); if (*fmt) { } }}// av_probe_input_format2调用了av_probe_input_format3, 这个函数是比较慢的AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret){ fmt = NULL; //遍历fmt while ((fmt1 = av_iformat_next(fmt1))) { score = 0; if (fmt1->read_probe) { //如果有探测函数 score = fmt1->read_probe(&lpd); //开始探测,得分 if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) { //扩展名一样,扩展名也是一个重要参考 switch (nodat) { case NO_ID3: score = FFMAX(score, 1); break; ... } } } else if (fmt1->extensions) { //无探测函数,有扩展名 if (av_match_ext(lpd.filename, fmt1->extensions)) score = AVPROBE_SCORE_EXTENSION; } //比较 mime_type 类型 if (av_match_name(lpd.mime_type, fmt1->mime_type)) score = FFMAX(score, AVPROBE_SCORE_MIME); //找打分最高的fmt if (score > score_max) { score_max = score; fmt = fmt1; } else if (score == score_max) fmt = NULL; } return fmt;}
在init_input中,确定了proto和format.
总结一下有一个逻辑,列出了几个重要的函数
下一步就得知道codec的确定啦.
2 如何确定codec
先看初始化,也是用列表来把多个codec串起来.
av_register_all->avcodec_register_all
void avcodec_register_all(void){ static int initialized; if (initialized) return; initialized = 1; //编码器 REGISTER_ENCDEC (AAC, aac); //解码器 REGISTER_DECODER(H264, h264);}//注册编码器#define REGISTER_ENCODER(X, x) \ { \ extern AVCodec ff_##x##_encoder; \ if (CONFIG_##X##_ENCODER) \ avcodec_register(&ff_##x##_encoder); \ }//注册解码器#define REGISTER_DECODER(X, x) \ { \ extern AVCodec ff_##x##_decoder; \ if (CONFIG_##X##_DECODER) \ avcodec_register(&ff_##x##_decoder); \ }//注册编码器和解码器#define REGISTER_ENCDEC(X, x) REGISTER_ENCODER(X, x); REGISTER_DECODER(X, x)
ffmpeg是不支持H264的编码的,主要看一下H264的解码对象 ff_h264_decoder
//结构体这么赋值,也蛮好的.static const AVClass h264_class = { .class_name = "H264 Decoder", .item_name = av_default_item_name, .option = h264_options, .version = LIBAVUTIL_VERSION_INT,};AVCodec ff_h264_decoder = { .name = "h264", .long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"), .type = AVMEDIA_TYPE_VIDEO, .id = AV_CODEC_ID_H264, .priv_data_size = sizeof(H264Context), .init = ff_h264_decode_init, .close = h264_decode_end, .decode = h264_decode_frame, .capabilities = /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS | AV_CODEC_CAP_FRAME_THREADS, .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE, .flush = flush_dpb, .init_thread_copy = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy), .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context), .profiles = NULL_IF_CONFIG_SMALL(ff_h264_profiles), .priv_class = &h264_class,};
里面有一个name和id,任意一个都可以找到.初始化后,确定codec的逻辑要直接到fmt的里面去.以FLV为例子,ff_live_flv_demuxer ,这可能要设计到FLV的封装,这里不详解了.如何解析不是这一篇的该讲的.
//肯定在这里两个函数中//.read_header = flv_read_header, //read header只是解析了flv的header,没有解析到tag hader//.read_packet = flv_read_packet, //这个函数很复杂,挑着看.static int flv_read_packet(AVFormatContext *s, AVPacket *pkt){retry: //先解析,重点在于type pos = avio_tell(s->pb); type = (avio_r8(s->pb) & 0x1F); //有三种类型script,vedio,audio ... if (type == FLV_TAG_TYPE_VIDEO) { stream_type = FLV_STREAM_TYPE_VIDEO; flags = avio_r8(s->pb); //flags可以找到codec id size--; } if (stream_type == FLV_STREAM_TYPE_VIDEO) { size -= flv_set_video_codec(s, st, flags & FLV_VIDEO_CODECID_MASK, 1); } return ret;}static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream, int flv_codecid, int read){ AVCodecContext *vcodec = vstream->codec; switch (flv_codecid) { .... case FLV_CODECID_H264: // FLV_CODECID_H264 = 7, vcodec->codec_id = AV_CODEC_ID_H264; //这就和ff_h264_decoder.id对应上了 vstream->need_parsing = AVSTREAM_PARSE_HEADERS; return 3; ... } return 0;}
在封装逻辑(fmt,demux,有多个解释.)中找到了 codec_id,哪里有用到? read_thread->avformat_find_stream_info->find_decoder
static const AVCodec *find_decoder(AVFormatContext *s, AVStream *st, enum AVCodecID codec_id){ if (st->codec->codec) return st->codec->codec; switch (st->codec->codec_type) { case AVMEDIA_TYPE_VIDEO: if (s->video_codec) return s->video_codec; break; case AVMEDIA_TYPE_AUDIO: if (s->audio_codec) return s->audio_codec; break; case AVMEDIA_TYPE_SUBTITLE: if (s->subtitle_codec) return s->subtitle_codec; break; } return avcodec_find_decoder(codec_id);}AVCodec *avcodec_find_decoder(enum AVCodecID id){ return find_encdec(id, 0);}static AVCodec *find_encdec(enum AVCodecID id, int encoder){ AVCodec *p, *experimental = NULL; p = first_avcodec; id= remap_deprecated_codec_id(id); //遍历 所有codec while (p) { if ((encoder ? av_codec_is_encoder(p) : av_codec_is_decoder(p)) && p->id == id) { //找id的 if (p->capabilities & AV_CODEC_CAP_EXPERIMENTAL && !experimental) { experimental = p; } else return p; } p = p->next; } return experimental;}
每个封装都有不同的解析包的逻辑,只有把包解析了才知道codec.
这一篇可以终结了.还多没明白的地方,譬如Proto如何file和rtmp读写. 先不用急. 把这个点搞明白之后,再下一个点.
下一篇来分析ffplay test.flv的整体逻辑. 之后再分析file和rtmp的读写.
- ffplay如何支持多协议
- ffplay
- 如何让NAT支持PPTP协议
- 如何让IE支持自定义协议
- Weblogic 服务器如何支持https协议
- 负载均衡支持哪些协议,如何选择协议?
- [网络协议]:如何验证DNS服务器是否支持dnssec协议
- 如何将ffplay移植到vs2015
- ffmpeg2.5.3 ubuntu下编译 支持x265的ffplay
- xmpp协议如何支持发送/接受离线文件思路之一
- Silverlight如何支持多语言
- NETSH 如何扩展其功能?如何适配netsh使其支持SCTP协议?
- LoadRunner支持的协议
- Loadrunner支持https协议
- ActiveMQ支持协议
- Jexus支持HTTPS协议
- TOMCAT8支持HTTPS协议
- springcloud 支持hessian协议
- hashCode()的作用
- SSH配置
- 关于调用三方地图实现路线查询的分享!
- 混合开发之webView加载html,android 和 html之间进行数据交互
- Android 实现 遮罩动画效果
- ffplay如何支持多协议
- @SerializedName注解
- Kafka:Kafka的生产和消费(Java版本)
- Linux mount Windows目录遇到 write-protected 问题
- failed to load the jni shared library
- Servlet的线程安全问题
- LeetCode 12. Integer to Roman
- C语言const:禁止修改变量的值
- 第十九节 隐式转换与隐式参数(二)