本系列 以 ffmpeg4.4 源码为准,主要讲解 ffplay 的 RTMP 协议解析,播放。本文使用的命令如下:
ffplay -i rtmp://192.168.0.122/live/livestream
本文主要讲解 rtmp_open()
函数的实现,RTMP 协议的代码都在 libavformat/rtmpproto.c
文件里面,请看下面截图:
RTMP 经常使用的函数,rtmp_open
(建立链接),rtmp_read
(读取数据),rtmp_write
,rtmp_seek
,这些函数,都在这个文件 rtmpproto.c
里面实现。
rtmp_open()
函数的流程图如下:
上面的流程图有以下重点:
重点一:
上面的流程图是第二次执行 ffurl_open_whitelist()
函数,第一次是处理RTMP链接,第二次是处理 TCP链接。第二次的 parent 参数是有值的。
重点二:
ff_connect_parallel()
函数会尝试重试,参考 RFC 8305 的标准。大概应该是 200ms后 TCP 握手还没完成,就会立即新建第二个链接,最后哪一个成了就用哪一个TCP链接。
重点三:
rtmp_handshake()
函数就是处理握手交互的,推荐阅读《RTMP协议分析-handshake》
重点四:
ff_url_join()
是 ffmpeg 的 URL utility functions,也就是 工具函数,可以很方便的 拼接 协议相关的字符串。
还有 get_packet()
是个非常重点的函数。里面主要干了3件事
1,调用 rtmp_parse_result()
处理各种RTMP相关的AM,INVOKE,跟交互。
2,调 handle_metadata()
函数 处理 flv 的 metadata
3,调 append_flv_data()
处理 RTMP 音视频包,加入队列。
在上面流程图, get_packet()
这个函数调用了两次,第一次是为了把 RTMP 链接的状态从 STATE_HANDSHAKED
转成 STATE_PLAYING
,第二次会阻塞直到读取到任何一个音视频包,或者flv 的metadata。
append_flv_data()
这个函数需要详细讲解一下,因为里面是处理音视频包的。代码如下:
实际上这里面的操作,就是把 一个 RTMP 音视频包(RTMP 是直接传的 H264 裸流),转成 flv 的tag 格式,然后丢给 flv 的解复用器。这个其实跟读取本地文件的 flv 文件一样。
下图是 flv 的格式,可以参考着看上面代码,上面的变量 old_flv_size
有时候是 13 个字节,就是 flv header(9字节) + flv body 的 previous tagsize (4字节)。
总结,rtmp_open()
函数做了以下事情:
1,建立 RTMP 链接。
2,读取到第一个音视频包,丢进去 RTMPContext *rt
里面
struct RTMPContext
这个结构体非常重要,掌控了 rtmp 会话的整个周期。先看代码:
从上面可以看到 变量 RTMPContext *rt
是套在 URLContext
里面的。下面讲一下 rtmp_open()
执行完之后,变量 rt
主要有哪些字段被改变了。如下图:
最后提一点,FFplay 对播放 RTMP 做了很多兼容的处理,例如即使服务器推的RTMP 数据,flv 头部数据某些字段缺少或者不对,FFplay 内部也会纠正过来。估计是早期有些人不按标准实现 RTMP 服务器
扩展知识:
1,struct RTMPContext
有两个比较有趣的字段,has_audio
,has_video
,代表有没收到音频或视频的 RTMP 包。
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,或者希望交流音视频技术的,可以加我微信 Loken1。