龙空技术网

SRS流媒体服务器之RTMP协议分析(1)

antonio 2459

前言:

此时咱们对“nginx配置流媒体rmtp输入”大约比较珍视,同学们都想要知道一些“nginx配置流媒体rmtp输入”的相关内容。那么小编同时在网上收集了一些关于“nginx配置流媒体rmtp输入””的相关内容,希望同学们能喜欢,兄弟们快快来了解一下吧!

0.引言

阅读本文前,可以先阅读前面文章,能够帮助你更好理解本篇文章。文章列表如下:

SRS流媒体框架分析(1)

SRS流媒体之RTMP推流框架分析(2)

SRS流媒体之RTMP拉流框架分析(3)

手把手搭建流媒体服务器详细步骤

简述SRS流媒体服务器相关技术

流媒体服务器架构与应用分析

手把手搭建FFmpeg的Windows环境

流媒体推拉流实战之RTMP协议分析(BAT面试官推荐)

HTTP实战之Wireshark抓包分析

1.SRS源码常用类介绍

SrsCommonMessage:封装rtmp消息。

SrsPacket:读取到的数据,先存到数据包中。对应的派生类如下:

SrsConnectAppPacketSrsConnectAppResPacketSrsCallPacketSrsCallResPacketSrsCreateStreamPacketSrsCreateStreamResPacket SrsCloseStreamPacket SrsFMLEStartPacket SrsFMLEStartResPacket SrsPublishPacket SrsPausePacket SrsPlayPacket SrsPlayResPacket SrsOnBWDonePacket SrsOnStatusCallPacket SrsBandwidthPacket SrsOnStatusDataPacket SrsSampleAccessPacket SrsOnMetaDataPacket SrsSetWindowAckSizePacket SrsAcknowledgementPacket SrsSetChunkSizePacketSrsSetPeerBandwidthPacket SrsUserControlPacket

2.断点调试

输入命令

gdb ./objs/srs

界面如下:

界面如下:

输入命令:

set args -c ./conf/srs.conf

界面如下:

运行

输入命令:

c

r

界面如下:

开启推流命令:

ffmpeg -re -i xxx.flv -t 20 -vcodec copy -acodec copy -f flv -y rtmp://xxx.xxx.xxx.xxx/live/livestream -loglevel 66

断点调试

输入命令

b srs_app_rtmp_conn.cpp:183

b SrsRtmpServer::connect_app(SrsRequest*)

界面如下:

tcp传送的是以chunk数据块的流式数据,到达服务器后把chunk组成message形式,对应更上层的就是packet,这些message都是存放到Srs流媒体服务器中的SrsCommonMessage。获取到message后,Srs流媒体服务器会调用decode_message去解码。源码如下:

根据不同的消息类型,使用不同的处理。

在Srs流媒体服务器源码中,在SrsRtmpConn::service_cycle()函数中,Srs流媒体服务器会发送回复的消息(就是WireShark抓包回应消息)客户端,源码如下:

在客户端会有处理服务器发过来的消息,源码如下:

Srs流媒体服务器通过_result()返回消息,客户端读取stream_id,然后调用gen_publish()给服务器发送消息,客户端ffmpeg源码如下:

可以看到这里,发送的消息,就与WireShark抓包得到的信息是一样。

3.源码介绍

ffmpeg源码中关于RTMP协议的源码在Rtmpproto.c。

还有个是Rtmppkt.h,也是要重点分析。

握手函数rtmp_handshake在Rtmpproto.c文件中,对应源码如下:

在rtmp_open中会调用rtmp_handshake。

创建对应的连接,调用gen_connect(推流端给服务器发送参数)。源码如下:

在前面文章有讲过,收到一定数据后,回应给服务器,使用gen_bytes_read()。源码如下:

ffmpeg源码中,释放码流连接的资源。源码如下:

ffmpeg源码中,客户端向服务器发送一个连接包,源码如下:

对应SRS流媒体服务器,在握手成功后,客户端肯定会发送“connect相关信息”。SRS流媒体服务器源码如下:

SRS流媒体服务器流媒体服务器读取的信息都会存放到SrsRequest* req所指向的内存中。

根据调试消息,输入命令:

print *req

可以看到客户端一些信息,如下界面:

4.抓包分析

通过WireShark抓取拉流客户端数据包,如下界面:

也可以使用tcpdump来抓取服务器回应的数据。

sudo tcpdump -i any port 1935 -XX and dst xxx.xxx.xxx.xxx

Srs流媒体服务器发送一个Publish命令后,就可以开启推流。Srs流媒体服务器,源码如下:

在ffmpeg的源码中,客户端一般处理服务器的消息的函数都是handle_XXX形式的,客户端发送给服务器的函数都是gen_xxx形式。

上面举例的这些函数,在wireshark中都是能抓包看到。如下图:

客户端与SRS流媒体服务器端,handshake握手解决了RTMP一致性的问题。如下界面:

客户端发送数据到SRS流媒体服务器端,使用connect命令连接。如下界面:

Chunk stream Id:接收端根据相同的chunk stream id拼装出message,stream id=3,对应ffmpeg的枚举RTMP_SYSTEM_CHANNEL。

Type ID (即是message type id):如8表示音频,9表示视频,20表示命令消息。命令消息对应ffmpeg源码中的枚举为RTMP_PT_INVOKE。

Stream ID:建立连接后,由服务器返回给客户端。

握⼿之后先发送⼀个connect 命令消息,这些信息是以AMF格式发送的,消息的结构如下:

第三个字段中的Command Object中会涉及到很多键值对,可以参考协议的官⽅⽂档。服务器回应客户端的消息有两种,_result表示接受连接,_error表示连接失败。以下是连接命令对象中使⽤的名称-值对的描述。

SRS流媒体服务端回应客户端消息,Window Acknowledgement Size。客户端在收到一定字节数后,需要回应服务器。

Type Id:0x05,对应ffmpeg源码中,RTMP_PT_WINDOW_ACK_SIZE。

Window Acknowledgement Size:⽤于设置窗⼝确认⼤⼩,如这⾥是服务器发送给客户端,每次客户端收到该size就要Acknowledgement(0x3)。

注意:对于ffmpeg源码而言,充当的客户端,在接收到Window Acknowledgement Size的⼀半后发送确认包(Acknowledgement),ffmpeg对应的枚举是RTMP_PT_BYTES_READ。,以确保对等⽅可以继续发送⽽不等待确认。源码如下所示:

 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 si ze %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 waiti ng  // for acknowledgements.  rt->receive_report_size >>= 1; // 收到⼀半⼤⼩就确认    return 0; }

在实际使用情况,与spec协议中,有些不一样的,如下:

注意:

当客户端作为推流端时,一般即使没有收到服务器的ack,客户端也不会停止码流的推送。

当客户端作为拉流端时,一般即使拉流端不回应ack,服务器也不会停止码流的发送。

如果客户端和服务端都是接收方,收到1/2的Windows size后,需要发送ack给对方。

SRS流媒体服务端给客户端发送Set Peer Bandwidth。

客户端或服务器端发送此消息更新对端(谁发送谁就是这⾥的对端)的输出带宽。和Window Acknowledgement Size相⽐,重点是更新。

Set Peer Bandwidth(Message Type ID=6):目的是为了限制对端的输出带宽。接收端接收到该消息后会通过设置消息中的Window ACK Size,来限制已发送但未接受到反馈的消息的⼤⼩,最终限制发送端的发送带宽。如果消息中的Window ACK Size与上⼀次发送给发送端的size不同的话,要回馈⼀个WindowAcknowledgement Size的控制消息。spec协议如下图所示:

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。

Srs流媒体服务端发送客户端 Set Chunk Size

如下图所示:

Set Chunk Size(Message Type ID=1):设置chunk中Data字段所能承载的最大字节数,默认为128Bytes,通信过程中可以通过发送该消息来设置chunk Size的⼤⼩(不得⼩于128Bytes),⽽且通信双⽅会各⾃维护⼀个chunkSize,两端的chunkSize是独⽴的。如当A想向B发送⼀个200B的Message,但默认的chunkSize是128Bytes,因此就要将该消息拆分为Data分别为128Bytes和72Bytes的两个chunk发送,如果此时已经先发送⼀个设置chunkSize为256Bytes的消息,再发送Data为200Bytes的chunk,本地不再划分Message,B接收到SetChunk Size的协议控制消息时,会调整接受的chunk的Data的⼤⼩,也不⽤再将两个chunk组成为⼀个Message。

注意:在实际工程中,一般会把chunk size设置的很大,如4096Bytes,FFmpeg推流的时候一般设置60*1000,好处就是避免频繁拆包和组包,占用过多的CPU。

在spec协议中,代表Set Chunk Size消息的chunk的Data:

其中第⼀位必须为0,chunk Size占31个位,最⼤可代表2147483647=0x7FFFFFFF=231-1,但实际上所有⼤于16777215=0xFFFFFF的值都⽤不上,因为chunk size不能⼤于Message的⻓度,表示Message的⻓度字段是⽤3个字节表示,最⼤只能为0xFFFFFF。

客户端发送Set Chunk Size给SRS服务端

抓包过程,如下图所示:

客户端发送服务端 releaseStream

抓包过程,如下图所示:

其中liveStream是要处理的流。

客户端发送服务端FCPublish

抓包过程,如下图所示:

客户端发送服务端createStream

抓包过程,如下图所示:

Create Stream:创建传递具体信息的通道,从⽽可以在这个流中传递具体信息,传输信息单元为Chunk。

当发送完createStream消息之后,解析服务器返回的消息会得到⼀个stream ID, 这个ID也就是以后和服务器通信的 message stream ID, ⼀般返回的是1,不固定。

客户端发送服务端_checkbw

服务器并没有做任何处理。

服务端发送客户端_result

服务端向客户端发送_result的应答消息。

客户端发送服务端publish

推流

服务端发送客户端onFCPublish

响应客户端,是否准备好接收流。

查看Srs流媒体服务器的log信息,与WireShark的抓包信息一致。如下。

[2020-07-31 19:48:19.032][Trace][1761][412] RTMP client ip=175.0.54.116, fd=10[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=livestream- 1.ts, dur=0.00, dva=0p[2020-07-31 19:48:28.567][Trace][1761][412] -> HLS time=86059973ms, sno=2, ts=livestream- 1.ts, dur=0.00, dva=9120p[2020-07-31 19:48:38.606][Trace][1761][412] -> HLS time=96062310ms, sno=3, ts=livestream- 2.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

5.总结

关于源码和抓包分析就讲解到这里,深入分析RTMP协议在实际工程的流程。本篇文章就分析到这里,欢迎关注,转发,点赞,收藏,分享,评论区讨论。

后期关于项目的知识,会在微信公众号上更新,如果想要学习项目,可以关注微信公众号“记录世界 from antonio”

标签: #nginx配置流媒体rmtp输入