音视频流媒体开发【六十】RTMP/HLS/HTTP-FLV流媒体
音视频流媒体开发-目录
iOS知识点-目录
Android-目录
Flutter-目录
数据结构与算法-目录
uni-pp-目录
开发环境
推流:Windows通过FFmpeg命令⾏推流
服务器:使⽤ubuntu上的srs流媒体服务器
协议交互分析:使⽤wireshark抓包分析
srs服务器细节:使⽤gdb进⾏分析
tcpdump:
sudo tcpdump -i any port 1935 -XX and dst 175.0.54.116 抓取服务器回应的数据
具体步骤分析:
- 以前台的⽅式运⾏srs流媒体服务器
srs.3.0-20200720/trunk$ ./objs/srs -c ./conf/srs.conf
以及使⽤gdb去调试 - 启动wireshark,过滤rtmp协议,设置的是rtmpt关键字
- 使⽤ffmpeg推流,推流20秒后断开
ffmpeg -re -i source.200kbps.768x320.flv -t 20 -vcodec copy -acodec copy -f flv -y rtmp://111.229.231.225/live/livestream
ffmpeg -re -i source.200kbps.768x320.flv -vcodec copy -acodec copy -f flv -y rtmp://111.229.231.225/live/livestream
- 分析wireshark抓的包
- 分析srs流媒体服务器的log
ubuntu 16.04 直接安装ffmpeg命令,如果已经安装过了不⽤安装,命令安装的版本⽐较⽼,主要是⽤来 测试命令。
sudo add-apt-repository ppa:djcj/hybrid
sudo apt-get update
sudo apt-get -y install ffmpeg
srs.3.0-20200720/trunk/doc⽬录下有source.200kbps.768x320.flv
srs流媒体log
[2020-07-31 19:48:19.058][Trace][1761][412] complex handshake success
[2020-07-31 19:48:19.079][Trace][1761][412] connect app, tcUrl=rtmp://111.229.231.225:1935/live, pageUrl=, swfUrl=, schema=rtmp,vhost=111.229.231.225, port=1935, app=live, args=null
[2020-07-31 19:48:19.079][Trace][1761][412] protocol in.buffer=0, in.ack=0, out.ack=0, in.chunk=128, out.chunk=128
[2020-07-31 19:48:19.221][Trace][1761][412] client identified, type=fmle-publish,vhost=111.229.231.225, app=live, stream=livestream, param=, duration=0ms
[2020-07-31 19:48:19.221][Trace][1761][412] connected stream,tcUrl=rtmp://111.229.231.225:1935/live, pageUrl=, swfUrl=, schema=rtmp,vhost=__defaultVhost__, port=1935, app=live, stream=livestream, param=, args=null
[2020-07-31 19:48:19.221][Trace][1761][412] source url=/live/livestream, ip=175.0.54.116,cache=1, is_edge=0, source_id=-1[-1]
[2020-07-31 19:48:19.361][Trace][1761][412] hls: win=60000ms, frag=10000ms, prefix=,path=./objs/nginx/html, m3u8=[app]/[stream].m3u8, ts=[app]/[stream]-[seq].ts, aof=2.00,floor=0, clean=1, waitk=1, dispose=0ms, dts_directly=1
[2020-07-31 19:48:19.361][Trace][1761][412] ignore disabled exec for vhost=__defaultVhost__
[2020-07-31 19:48:19.361][Trace][1761][412] start publish mr=0/350, p1stpt=20000, pnt=5000,tcp_nodelay=0
[2020-07-31 19:48:19.501][Trace][1761][412] got metadata, width=1920, height=832, vcodec=7,acodec=10
[2020-07-31 19:48:19.501][Trace][1761][412] 50B video sh, codec(7, profile=High, level=4,1920x832, 0kbps, 0.0fps, 0.0s)
[2020-07-31 19:48:19.501][Trace][1761][412] 4B audio sh, codec(10, profile=LC, 2channels,0kbps, 48000HZ), flv(16bits, 2channels, 44100HZ)
[2020-07-31 19:48:19.514][Trace][1761][412] -> HLS time=76056186ms, sno=2, ts=livestream1.ts, dur=0.00, dva=0p
[2020-07-31 19:48:28.567][Trace][1761][412] -> HLS time=86059973ms, sno=2, ts=livestream1.ts, dur=0.00, dva=9120p
[2020-07-31 19:48:38.606][Trace][1761][412] -> HLS time=96062310ms, sno=3, ts=livestream2.ts, dur=0.00, dva=6320p
[2020-07-31 19:48:39.449][Warn][1761][412][11] VIDEO: stream not monotonically increase,please open mix_correct.
[2020-07-31 19:48:39.450][Trace][1761][412] cleanup when unpublish
[2020-07-31 19:48:39.450][Warn][1761][412][104] client disconnect peer. ret=1007
wireshark
推流
stream id何时由服务器返回来:客户端发送createStream后返回stream id
拉流
SRS常⽤类说明
tcp -> chunk - message - packet
SrsCommonMessage
封装rtmp消息
SrsPacket基类
解读取到的数据先暂存到包⾥。
对应的派⽣类:
SrsConnectAppPacket
SrsConnectAppResPacket
SrsCallPacket
SrsCallResPacket
SrsCreateStreamPacket
SrsCreateStreamResPacket
SrsCloseStreamPacket
SrsFMLEStartPacket
SrsFMLEStartResPacket
SrsPublishPacket
SrsPausePacket
SrsPlayPacket
SrsPlayResPacket
SrsOnBWDonePacket
SrsOnStatusCallPacket
SrsBandwidthPacket
SrsOnStatusDataPacket
SrsSampleAccessPacket
SrsOnMetaDataPacket
SrsSetWindowAckSizePacket
SrsAcknowledgementPacket
SrsSetChunkSizePacket
SrsSetPeerBandwidthPacket
SrsUserControlPacket
C->S S->C handshake握⼿
解决RTMP版本⼀致性的问题
C->S connect客户端连接服务器端
客户端连接服务器端
- Chunk stream Id:接收端根据相同的chunk stream id拼装出message, 这⾥是3,对应ffmpeg的枚举RTMP_SYSTEM_CHANNEL
- Type ID (即是message type id) ⽐如8⾳频,9视频,20命令消息,这⾥是20命令消息,对应ffmpeg的枚举RTMP_PT_INVOKE
- Stream ID:
这⾥属于connect,⽤于客户端向服务器发送连接请求
握⼿之后先发送⼀个connect 命令消息,这些信息是以AMF格式发送的,消息的结构如下:
第三个字段中的Command Object中会涉及到很多键值对,使⽤时可以参考协议的官⽅⽂档。
消息的回应有两种,_result表示接受连接,_error表示连接失败。
以下是连接命令对象中使⽤的名称-值对的描述:
S->C Window Acknowledgement Size
通知对⽅收到 ⼀定的字节数后需要回应发送⽅。
Type Id:0x5 对应ffmepg RTMP_PT_WINDOW_ACK_SIZE
Window Acknowledgement Size⽤于设置窗⼝确认⼤⼩,⽐如这⾥是服务器发送给客户端的,当客户端收到每次该size就要Acknowledgement (0x3)。
对于ffmpeg,在接收到Window Acknowledgement Size的⼀半后发送确认包(Acknowledgement),以确保对等⽅可以继续发送⽽不等待确认。
static int handle_window_ack_size(URLContext *s, RTMPPacket *pkt)
{
RTMPContext *rt = s->priv_data;
if (pkt->size < 4) {
av_log(s, AV_LOG_ERROR,
"Too short window acknowledgement size packet (%d)\n",
pkt->size);
return AVERROR_INVALIDDATA;
}
rt->receive_report_size = AV_RB32(pkt->data);
if (rt->receive_report_size <= 0) {
av_log(s, AV_LOG_ERROR, "Incorrect window acknowledgement size %d\n",
rt->receive_report_size);
return AVERROR_INVALIDDATA;
}
av_log(s, AV_LOG_DEBUG, "Window acknowledgement size = %d\n", rt->receive_report_size);
// Send an Acknowledgement packet after receiving half the maxim um
// size, to make sure the peer can keep on sending without waiting
// for acknowledgements.
rt->receive_report_size >>= 1; // 收到⼀半⼤⼩就确认
return 0;
}
ffmpeg在命名的Acknowledgement 的时候使⽤枚举RTMP_PT_BYTES_READ。
注:
- 客户端作为推流端时,⼀般即使没有收到服务器的ack,客户端也不会停⽌码流的推送。
- 当客户端作为拉流端时,⼀般即使拉流端不回应ack,服务器也不会停⽌码流的发送。
- 但彼此如果作为接收⽅时,收到1/2Windows size的数据后对会ack对⽅。
S->C Set Peer Bandwidth
客户端或服务器端发送此消息更新对端(谁发送谁就是这⾥的对端)的输出带宽。
和Window Acknowledgement Size相⽐,重点是更新。
Type ID:0x6 对应ffmpeg RTMP_PT_SET_PEER_BW,
Set Peer Bandwidth(Message Type ID=6):限制对端的输出带宽。接收端接收到该消息后会通过设置消息中的Window ACK Size来限制已发送但未接受到反馈的消息的⼤⼩来限制发送端的发送带宽。如果消息中的Window ACK Size与上⼀次发送给发送端的size不同的话要回馈⼀个Window Acknowledgement Size的控制消息。
- Hard(Limit Type=0):接收端应该将Window Ack Size设置为消息中的值
- Soft(Limit Type=1):接收端可以讲Window Ack Size设为消息中的值,也可以保存原来的值(前提是原来的Size⼩与该控制消息中的Window Ack Size)
- Dynamic(Limit Type=2):如果上次的Set Peer Bandwidth消息中的Limit Type为0,本次也按Hard处理,否则忽略本消息,不去设置Window Ack Size。
实际是⽤来告诉对端,当你如果不ack时,收到的最⼤size。但其重在更新。
S->C Set Chunk Size
Set Chunk Size(Message Type ID=1):设置chunk中Data字段所能承载的最⼤字节数,默认为128B,通信过程中可以通过发送该消息来设置chunk Size的⼤⼩(不得⼩于128B),⽽且通信双⽅会各⾃维护⼀个chunkSize,两端的chunkSize是独⽴的。⽐如当A想向B发送⼀个200B的Message,但默认的chunkSize是128B,因此就要将该消息拆分为Data分别为128B和72B的两个chunk发送,如果此时先发送⼀个设置chunkSize为256B的消息,再发送Data为200B的chunk,本地不再划分Message,B接受到SetChunk Size的协议控制消息时会调整的接受的chunk的Data的⼤⼩,也不⽤再将两个chunk组成为⼀个Message。在实际写代码的时候⼀般会把chunk size设置的很⼤,有的会设置为4096,FFMPEG推流的时候设置的是 60*1000,这样设置的好处是避免了频繁的拆包组包,占⽤过多的CPU。
以下为代表Set Chunk Size消息的chunk的Data:
其中第⼀位必须为0,chunk Size占31个位,最⼤可代表2147483647=0x7FFFFFFF=2 -1,但实际上所有⼤于16777215=0xFFFFFF的值都⽤不上,因为chunk size不能⼤于Message的⻓度,表示Message的⻓度字段是⽤3个字节表示的,最⼤只能为0xFFFFFF。
C->S Set Chunk Size
以下:客户端需要发送 releaseStream , FCPublish 和 CreateStream 消息,具体可以参考ffmpeg
handle_invoke_result函数
C->S releaseStream
对应的值 liveStream是要处理的流
C->S FCPublish
C->S createStream
FFmpeg
Create Stream:创建传递具体信息的通道,从⽽可以在这个流中传递具体信息,传输信息单元为Chunk。
当发送完createStream消息之后,解析服务器返回的消息会得到⼀个stream ID, 这个ID也就是以后和服务器通信的 message stream ID, ⼀般返回的是1,不固定。
C->S _checkbw
服务器并没有做任何处理。