srs源码分析
本文分析的srs版本是0.6.0
以下正在写作中。。。
srs源码分析7-create stream
srs源码分析8-推流-publish
srs源码分析9-推流-unpublish
srs源码分析10-拉流-play
srs源码分析11-拉流-pause
srs源码分析12-转发-forward
使用到的网络抓包数据可以从这里下载:https://gitee.com/qiuguolu1108/blog
。
现在根据抓包信息,分析connect的过程。
int SrsClient::do_cycle()
{
...
/*handshake成功后,进行connect。*/
if ((ret = rtmp->connect_app(req)) != ERROR_SUCCESS) {
srs_error("rtmp connect vhost/app failed. ret=%d", ret);
return ret;
}
srs_verbose("rtmp connect app success");
...
}
int SrsRtmp::connect_app(SrsRequest* req)
{
int ret = ERROR_SUCCESS;
SrsCommonMessage* msg = NULL;
SrsConnectAppPacket* pkt = NULL;
/*读取connect数据包*/
if ((ret = srs_rtmp_expect_message<SrsConnectAppPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
srs_error("expect connect app message failed. ret=%d", ret);
return ret;
}
SrsAutoFree(SrsCommonMessage, msg, false);
srs_info("get connect app message");
SrsAmf0Any* prop = NULL;
/* tcUrl:服务器url,例如:rtmp://192.168.30.17/live */
if ((prop = pkt->command_object->ensure_property_string("tcUrl")) == NULL) {
ret = ERROR_RTMP_REQ_CONNECT;
srs_error("invalid request, must specifies the tcUrl. ret=%d", ret);
return ret;
}
/*将解析出的tcUrl赋值给SrsRequest*/
req->tcUrl = srs_amf0_convert<SrsAmf0String>(prop)->value;
if ((prop = pkt->command_object->ensure_property_string("pageUrl")) != NULL) {
req->pageUrl = srs_amf0_convert<SrsAmf0String>(prop)->value;
}
/* swfUrl:进行当前连接的swf文件源地址*/
if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) {
req->swfUrl = srs_amf0_convert<SrsAmf0String>(prop)->value;
}
/*AMF编码方法:AMF0、AMF3*/
if ((prop = pkt->command_object->ensure_property_number("objectEncoding")) != NULL) {
req->objectEncoding = srs_amf0_convert<SrsAmf0Number>(prop)->value;
}
srs_info("get connect app message params success.");
return req->discovery_app(); /*解析tcUrl*/
}
SrsRtmp::connect_app
-->srs_rtmp_expect_message
-->SrsProtocol::recv_message
-->SrsProtocol::recv_interlaced_message
调用逻辑
int SrsProtocol::recv_interlaced_message(SrsCommonMessage** pmsg)
{
...
/*解析chunk basic header*/
if ((ret = read_basic_header(fmt, cid, bh_size)) != ERROR_SUCCESS) {
...
}
...
/*根据chunk stream id查找对应的stream*/
if (chunk_streams.find(cid) == chunk_streams.end()) {
...
}
...
/*解析message header*/
if ((ret = read_message_header(chunk, fmt, bh_size, mh_size)) != ERROR_SUCCESS) {
...
}
...
/*解析message payload*/
if ((ret = read_message_payload(chunk, bh_size, mh_size, payload_size, &msg)) != ERROR_SUCCESS) {
...
}
...
/*返回接收的message*/
*pmsg = msg;
}
Chunk Stream是对传输RTMP Chunk流的逻辑上的抽象,客户端和服务器之间有关RTMP的信息都在这个流上通信。
RTMP在收发数据的时候,并不是以Message为单位,而是把Message拆分成Chunk发送,每个Chunk中带有MessageID代表属于哪个Message,接收端按照这个id来将Chunk组装成Message。
Chunk格式:
+-------------+----------------+-------------------+--------------+
| Basic header|Chunk Msg Header|Extended Time Stamp| Chunk Data |
+-------------+----------------+-------------------+--------------+
Basic header
Basic header的变长的,长度可能是1、2或3字节,其长度取决于cs id的大小,在足够存储这两个字段的前提下最好用尽量少的字节,从而减少由于引入Header增加的数据量。
RTMP协议最多支持65597个用户自定义的chunk stream ID,范围为[3, 65599],ID 0,1,2被协议规范直接使用:
- cs id值为0:表示Basic Header占用2个字节,cs id在[64, 319]之间。
- cs id值为1:表示Basic Header占用3个字节,cs id在[64, 65599]之间。
- cs id值为2:表示chunk是控制信息和一些命令信息。
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|fmt| cs id |
+-+-+-+-+-+-+-+-+
Chunk basic header 1
当Basic Header为1个字节时,cs id占6位,6位最多可以表示64个id,cs id在[0,63],其中用户可以自定义的范围为[3,63]。
0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 0 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk basic header 2
当Basic Header为2个字节时,cs id占8位,第一个字节除了chunk type占用bit外,其余的全部置为0,第二个字节用来表示cs id,8位可以表示[0,255],cs id的计算方式为第二个字节+64,所以cs id的范围为[64,319]。
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|fmt| 1 | cs id - 64 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk basic header 3
当Basic Header为3字节时,cs id占16位,第一个字节除了chunk type占用bit外,其余的全部置为1,第二、三字节用来表示cs id,16位可以表示[0,65535],加上64就是cs id的范围,[64,65599]。
可以看到2字节和3字节的Basic Header所能表示的cs id是有交集[64,319],但实际使用时还应该秉着最少字节的原则,使用2字节的表示方式来表示[64,319]的cs id。
/*解析chunk basic header*/
int SrsProtocol::read_basic_header(char& fmt, int& cid, int& bh_size)
{
int ret = ERROR_SUCCESS;
int required_size = 1;
/*从网络读取required_size大小的数据*/
if ((ret = buffer->ensure_buffer_bytes(skt, required_size)) != ERROR_SUCCESS) {
if (ret != ERROR_SOCKET_TIMEOUT) {
srs_error("read 1bytes basic header failed. required_size=%d, ret=%d", required_size, ret);
}
return ret;
}
/*先解析basic header的第一个字节*/
char* p = buffer->bytes();
fmt = (*p >> 6) & 0x03; /*获取fmt*/
cid = *p & 0x3f; /*获取cs id*/
bh_size = 1; /*basic header大小*/
/*cs id大于1,则cs id大小只有一个字节,解析完毕,返回。*/
if (cid > 1) {
srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid);
return ret;
}
/*cs id等于0,表示basic header大小为2字节。*/
if (cid == 0) {
/*从网络读取2字节大小的basic header*/
required_size = 2;
if ((ret = buffer->ensure_buffer_bytes(skt, required_size)) != ERROR_SUCCESS) {
if (ret != ERROR_SOCKET_TIMEOUT) {
srs_error("read 2bytes basic header failed. required_size=%d, ret=%d", required_size, ret);
}
return ret;
}
cid = 64;
cid += *(++p); /*64+[0,255]*/
bh_size = 2; /*basic header的大小为2字节*/
srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid);
} else if (cid == 1) { /*cs id为1,表示basic header的大小为3个字节。*/
/*从网络读取3字节大小的basic header*/
required_size = 3;
if ((ret = buffer->ensure_buffer_bytes(skt, 3)) != ERROR_SUCCESS) {
if (ret != ERROR_SOCKET_TIMEOUT) {
srs_error("read 3bytes basic header failed. required_size=%d, ret=%d", required_size, ret);
}
return ret;
}
cid = 64;
cid += *(++p);
cid += *(++p) * 256; /*计算实际的cs id值*/
bh_size = 3; /*basic header的大小*/
srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid);
} else {
srs_error("invalid path, impossible basic header.");
srs_assert(false);
}
return ret;
}
/*如果buffer中的数据长度不够,则"阻塞"的从socket读取,直到数据长度满足要求。*/
int SrsBuffer::ensure_buffer_bytes(SrsSocket* skt, int required_size)
{
int ret = ERROR_SUCCESS;
if (required_size < 0) {
ret = ERROR_SYSTEM_SIZE_NEGATIVE;
srs_error("size is negative. size=%d, ret=%d", required_size, ret);
return ret;
}
/*如果已读取的数据长度小于required_size则继续读取*/
while (size() < required_size) {
char buffer[SOCKET_READ_SIZE];
ssize_t nread;
/*通过st-thread从socket中"阻塞"读取*/
if ((ret = skt->read(buffer, SOCKET_READ_SIZE, &nread)) != ERROR_SUCCESS) {
return ret;
}
srs_assert((int)nread > 0);
append(buffer, (int)nread); /*追加读取的数据*/
}
return ret;
}
先从网络读取一个字节的Basic Header,然后根据cs id的值,判断出Basic Header的实际长度,再根据实际长度,将Basic Header读取完整。根据Basic Header实际数据,解析处fmt和csid。
Chunk Message Header
Message Header包含了要发送消息的描述信息。Message Header的格式和长度取决于Basic Header的chunk type,即fmt,共有4种不同的格式。其中第一种格式可以表示其他三种表示的所有数据,由于其他三种格式是基于之前chunk的差量化表示,因此可以更简洁的表示相同的数据,实际使用的时候应该采用尽量少的字节表示相同意义的数据。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |message length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| message length (cont) |message type id| msg stream id |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| message stream id (cont) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk Message Header – Type 0
Chunk Type(fmt) = 0:type=0时,msg header占用11个字节,其他三种能表示的数据它都能表示。
timestamp:时间戳,占用3个字节,它最多能表示2^24-1= 16777215,当它的值超过这个最大值时,这三个字节都置为1,而实际的timestamp会转存到Extended Timestamp字段中,接收端发现timestamp字段24位全为1时,就会去Extended Timestamp字段解析实际的时间戳。
message length:消息数据的长度,占用3个字节,表示实际发送消息数据的长度,单位是字节。这个长度是message的长度,也就是chunk所属message的总大小,而不是chunk本身Data的长度。(message在发送时,可能被会分割成多个chunk发送。)
message type id:消息类型id,占用1个字节,表示实际发送数据的类型,如,8代表音频数据,9代表视频数据。
message stream id:占用4个字节,表示该chunk所在流的id,采用小端存储。通常情况下,相同的chunk stream中的所有消息都来自同一个message stream。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp delta |message length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| message length (cont) |message type id|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk Message Header – Type 1
Chunk Type(fmt) = 1:type=1时,Message Header占用7个字节,省去了表示message stream id的4个字节,表示此chunk和上一次发送的chunk所在流相同。
timestamp delta:时间戳差,占用3个字节,注意这里和type=0时不同,存储的是和上一个chunk的时间差。类似timestamp,当它的值超过3个字节所能表示的最大值时,三个字节全置为1,实际的时间戳差值就会转存到Extended Timestamp字段中,接收端发现timestamp delta字段全为1时,就会去Extended timestamp中解析时间戳差值。
0 1 2
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp delta |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Chunk Message Header – Type 2
Chunk Type(fmt) = 2:type=2时,Message Header占用3个字节,相对于type=1又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此chunk和上一次发送的chunk所在的流、消息长度和消息的类型都相同。timestamp delta同type=1。
Chunk Type(fmt) = 3:type=3时,Message Header占用0个字节,表示这个chunk的Message Header和上一个完全相同。当它跟在type=0的chunk后面时,表示和上一个chunk的时间戳都是相同的,什么时候连时间戳都相同哪?就是一个Message拆分成了多个chunk,这个chunk和上一个chunk同属于一个Message。而当它跟在type=1或type=2的chunk后面时,表示和前一个chunk的时间戳差是相同的。比如第一个chunk的type=0,timestamp=1000,第二个chunk的type=2,timestamp delta=20,表示时间戳为1000+20=1020,第三个chunk的type=3,表示timestamp delta=20,时间戳为1000+20+20=1040。
Extended Timestamp
在chunk中会有时间戳timestamp和时间戳差值timestamp delta,并且它们不会同时存在,当这两者大于3个字节能表示的最大数值0xFFFFFF=16777215时,才会用这个字段来表示真正的时间戳,否则这个字段为0。当扩展时间戳启用时,timestamp字段或者timestamp delta字段需要全置为1,表示使用扩展时间戳字段。
int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_size, int& mh_size)
{
int ret = ERROR_SUCCESS;
/*是否是新的stream*/
bool is_fresh_packet = !chunk->msg;
/*新流的第一个包的fmt必须是0*/
if (chunk->msg_count == 0 && fmt != RTMP_FMT_TYPE0) {
ret = ERROR_RTMP_CHUNK_START;
srs_error("chunk stream is fresh, fmt must be %d, actual is %d. ret=%d", RTMP_FMT_TYPE0, fmt, ret);
return ret;
}
/*如果已经收到了部分msg,则fmt不能为0。*/
if (chunk->msg && fmt == RTMP_FMT_TYPE0) {
ret = ERROR_RTMP_CHUNK_START;
srs_error("chunk stream exists, fmt must not be %d, actual is %d. ret=%d", RTMP_FMT_TYPE0, fmt, ret);
return ret;
}
/*创建一条新的流*/
if (!chunk->msg) {
chunk->msg = new SrsCommonMessage();
srs_verbose("create message for new chunk, fmt=%d, cid=%d", fmt, chunk->cid);
}
static char mh_sizes[] = {11, 7, 3, 0};
/*根据fmt确定message header的大小*/
mh_size = mh_sizes[(int)fmt];
srs_verbose("calc chunk message header size. fmt=%d, mh_size=%d", fmt, mh_size);
/*basic header + message header 待读取数据的长度*/
int required_size = bh_size + mh_size;
if ((ret = buffer->ensure_buffer_bytes(skt, required_size)) != ERROR_SUCCESS) {
if (ret != ERROR_SOCKET_TIMEOUT) {
srs_error("read %dbytes message header failed. required_size=%d, ret=%d", mh_size, required_size, ret);
}
return ret;
}
/*指向message header*/
char* p = buffer->bytes() + bh_size;
if (fmt <= RTMP_FMT_TYPE2) { /*存在timestamp或timestamp delta*/
/*获取timestamp或timestamp delta*/
char* pp = (char*)&chunk->header.timestamp_delta;
pp[2] = *p++;
pp[1] = *p++;
pp[0] = *p++;
pp[3] = 0;
/*如果timestamp或timestamp delta全为1,则需要解析extended timestamp的值。*/
chunk->extended_timestamp = (chunk->header.timestamp_delta >= RTMP_EXTENDED_TIMESTAMP); /*是否使用extended timestamp*/
if (chunk->extended_timestamp) {
chunk->header.timestamp = RTMP_EXTENDED_TIMESTAMP;
} else {
if (fmt == RTMP_FMT_TYPE0) {
/*fmt = 0,就是实际的时间戳。*/
chunk->header.timestamp = chunk->header.timestamp_delta;
} else {
/*fmt=1、2,是时间戳差,需要之前的时间戳加上时间戳差。*/
chunk->header.timestamp += chunk->header.timestamp_delta;
}
}
if (fmt <= RTMP_FMT_TYPE1) {
/*解析保存message length*/
pp = (char*)&chunk->header.payload_length;
pp[2] = *p++;
pp[1] = *p++;
pp[0] = *p++;
pp[3] = 0;
if (chunk->msg->size > 0 && chunk->msg->size != chunk->header.payload_length) {
ret = ERROR_RTMP_PACKET_SIZE;
srs_error("msg exists in chunk cache, size=%d cannot change to %d, ret=%d", chunk->msg->size, chunk->header.payload_length, ret);
return ret;
}
/*解析保存message type*/
chunk->header.message_type = *p++;
if (fmt == 0) {
/*解析保存stream id*/
pp = (char*)&chunk->header.stream_id;
pp[0] = *p++;
pp[1] = *p++;
pp[2] = *p++;
pp[3] = *p++;
srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%d, payload=%d, type=%d, sid=%d",
fmt, mh_size, chunk->extended_timestamp, chunk->header.timestamp, chunk->header.payload_length, chunk->header.message_type, chunk->header.stream_id);
} else {
srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%d, payload=%d, type=%d",
fmt, mh_size, chunk->extended_timestamp, chunk->header.timestamp, chunk->header.payload_length, chunk->header.message_type);
}
} else {
srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%d", fmt, mh_size, chunk->extended_timestamp, chunk->header.timestamp);
}
} else { /*fmt = 3*/
if (is_fresh_packet && !chunk->extended_timestamp) {
/*和上一个时间戳之差相同,加上这个时间戳差。*/
chunk->header.timestamp += chunk->header.timestamp_delta;
}
srs_verbose("header read completed. fmt=%d, size=%d, ext_time=%d", fmt, mh_size, chunk->extended_timestamp);
}
/*如果存在extended timestamp,则解析。*/
if (chunk->extended_timestamp) {
mh_size += 4; /*message header的长度+4*/
required_size = bh_size + mh_size;
srs_verbose("read header ext time. fmt=%d, ext_time=%d, mh_size=%d", fmt, chunk->extended_timestamp, mh_size);
/*从网络读取extended timestamp*/
if ((ret = buffer->ensure_buffer_bytes(skt, required_size)) != ERROR_SUCCESS) {
if (ret != ERROR_SOCKET_TIMEOUT) {
srs_error("read %dbytes message header failed. required_size=%d, ret=%d", mh_size, required_size, ret);
}
return ret;
}
int32_t timestamp = 0x00;
/*解析extended timestamp的值*/
char* pp = (char*)×tamp;
pp[3] = *p++;
pp[2] = *p++;
pp[1] = *p++;
pp[0] = *p++;
// compare to the chunk timestamp, which is set by chunk message header type 0,1 or 2.
int32_t chunk_timestamp = chunk->header.timestamp;
if (chunk_timestamp > RTMP_EXTENDED_TIMESTAMP && chunk_timestamp != timestamp) {
mh_size -= 4;
srs_verbose("ignore the 4bytes extended timestamp. mh_size=%d", mh_size);
} else {
chunk->header.timestamp = timestamp; /*保存extended timestamp*/
}
srs_verbose("header read ext_time completed. time=%d", chunk->header.timestamp);
}
// valid message
if (chunk->header.payload_length < 0) {
ret = ERROR_RTMP_MSG_INVLIAD_SIZE;
srs_error("RTMP message size must not be negative. size=%d, ret=%d", chunk->header.payload_length, ret);
return ret;
}
// copy header to msg
chunk->msg->header = chunk->header;
// increase the msg count, the chunk stream can accept fmt=1/2/3 message now.
chunk->msg_count++;
return ret;
}
RTMP的消息类型可分为三大类:协议控制消息、用户控制消息、RTMP命令消息,它们的message stream id必须为0,cs id必须为2。
协议控制消息
Message Type ID=1:Set Chunk Size(设置块大小消息)
设置chunk中Data字段所能承载的最大字节数,默认为128Byte,通信过程中可以通过发送该消息设置Chunk Size的大小(不得小于128Byte),而且通信双方会各自维护一个chunk size,两端的chunk size是独立的。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| chunk size (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Pay load for the protocol message ‘Set Chunk Size’
其中第一位必须为0,Chunk Size占31位,最大可表示2^31-1,但实际上所有大于0xFFFFFF=16777215的值都用不上,因为chunk size不能大于Message的长度,表示Message长度字段是用3个字节表示的,最大只能为0xFFFFFF。
Message Type ID=2:Abort Message(终止消息)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| chunk stream id (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Pay load for the protocol message ‘Abort Message’.
当一个Message被切分为多个chunk,接收端只接收了部分chunk时,发送该控制消息表示发送端不再传输同Message的chunk,接收端收到这个消息后要丢弃这些不完整的chunk。Data数据中只需要一个cs id,表示丢弃该cs id的所有已接收到的chunk。应用程序可以在关闭的时候发送这个消息,以指示不需要进一步对这个消息处理了。
Message Type ID=3:Acknowledgement(确认消息)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sequence number (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Pay load for the protocol message ‘Acknowledgement’.
客户端或服务器在接收到等同于窗口大小的字节后,必须发送给对端一个确认消息。窗口大小是指发送者在没有收到接收者确认消息之前发送的最大字节数。sequence number表示到目前为止接收到的字节数。
Message Type ID=5:Window Acknowledgement Size(确认窗口大小消息)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size (4 bytes) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Pay load for ‘Window Acknowledgement Size’.
会话开始时,双方都要先向对端发送Window Acknowledgement Size,用于指明期望获得确认的大小。
Message Type ID=6:Set Peer Bandwidth(设置对端带宽消息)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgement Window size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Limit type |
+-+-+-+-+-+-+-+-+
Pay load for ‘Set Peer Bandwidth’
限制对端的输出带宽,接收端接收到该消息后会通过设置消息中的Window ACK Size来限制已发送但未接收到反馈的消息的大小来限制发送端的发送带宽。如果消息中的Window ACK Size与上一次发送给发送端的size不同的话,要回馈一个Window ACK Size的控制消息。
Hard(limit type=0):硬限制,对等端必须按提供的带宽发送数据。
Soft(limit type=1):软限制,对等端可以灵活决定带宽,发送端可以限制带宽。
Dynamic(limit type=2):动态限制,带宽既可以时硬限制,也可以是软限制。
用户控制消息
Message Type = 4
+------------------------------+-------------------------
| Event Type ( 2- bytes ) | Event Data
+------------------------------+-------------------------
Pay load for the ‘User Control Message’.
客户端或服务端发送该消息来通知对端一些用户控制事件。比如Stream Begin事件告知对方流信息开始传输。
和前⾯提到的协议控制信息(Protocol Control Message)不同,这是在RTMP协议层的,而不是在RTMP chunk流协议层的,这个很容易弄混。该信息在chunk流中发送时,Message Stream ID=0,Chunk Stream Id=2,Message Type Id=4。
RTMP命令消息
Message Type ID = 8:Audio Message(音频数据)
Message Type ID = 9:Video Message(视频数据)
Message Type ID = 15或18:Data Message(数据消息)
客户端或服务端向对方发送元数据(meta data)或用户自定义的数据。当信息使用AMF0编码时,Message Type ID=18,使用AMF3编码时,Message Type ID=15。
Message Type ID = 16或19:Shared Object Message(共享消息)
表示一个Flash类型对象,有键值对的集合组成,用于多客户端,多实例时使用。
当信息使用AMF0编码时,Message Type ID=19,使用AMF3编码时,Message Type ID=16。
Message Type ID = 22:Aggregate Message(聚合消息)
多个RTMP子消息的集合,包含一系列子消息的单个消息。
Message Type ID = 17或20:Command Message(命令消息)
在客户端和服务端间传递命令,这些命令消息用于在远端实现连接、创建流、发布、播放和暂停等操作。状态、结果等命令消息用于通知发送者请求命令的状态。
当信息使用AMF0编码时,Message Type ID=20,使用AMF3编码时,Message Type ID=17。
发送端在发送该消息时,会带有命令的名字,如connect。TransactionID表示此次命令的标识,Command Object表示相关参数。接收端收到命令后,会返回以下三种消息的一种:_result
消息表示接受该命令,对端可以继续往下执行;_error
消息代表拒绝该命令要执行的操作,method name
消息代表要在之前命令的发送端执行的函数名称。这三种回应消息都要带有收到的命令消息中的TransactionID来表示本次的回应作用于哪个命令。
NetConnection Command(连接层的命令)
用来管理双端之间的连接状态,同时也提供了异步远程方法调用在对端执行某方法,以下是常见的连接层的命令:
connect:用于客户端向服务器发送连接请求
握手之后先发送一个connect命令消息,这些消息是以AMF格式发送的,消息的结构如下:
消息的回应有两种:_result表示接受连接,_error表示连接失败。
第三个字段中的Command Object中会涉及到很多键值对,以下是连接命令对象中使用的描述:
call:用于在对端执行某函数,即常说的RPC,消息的结构如下:
如果消息中的TransactionID不为0的话,对端需要对该命令做出响应,响应的消息结构如下:
Create Stream:创建传递具体消息的通道,从而可以在这个流中传递具体信息,传输信息单位为chunk。
当发送完Create Stream消息后,解析服务器返回的消息会得到一个Stream ID,这个ID就是以后和服务器通信的Message Stream ID,一般返回的是1,不固定。
NetStream Command(流连接上的命令)
Netstream建⽴在NetConnection之上,通过NetConnection的createStream命令创建,用于传输具体
的音频、视频等信息。在传输层协议之上只能连接⼀个NetConnection,但⼀个NetConnection可以建立多个NetStream来建立不同的流通道传输数据。
onStatus:服务端收到命令后会通过onStatus的命令来响应客户端,表示当前NetStream的状态。命令的消息结构如下:
play(播放):由客户端向服务器发起请求从服务器端接受数据(如果传输的信息是视频的话就是请求
开始播流),可以多次调用,这样本地就会形成⼀组数据流的接收者。注意其中有⼀个reset字段,表示是覆盖之前的播流(设为true),还是重新开始⼀路播放(设为false)。
play命令的结构如下:
play2(播放):和上⾯的play命令不同的是,play2命令可以将当前正在播放的流切换到同样数据但不同比特率的流上,服务器端会维护多种比特率的⽂件来供客户端使⽤play2命令来切换。
deleteStream(删除流):用于客户端告知服务器端本地的某个流对象已被删除,不需要再传输此路流。
receiveAudio(接收音频):通知服务器端该客户端是否要发送音频。
receiveVideo(接收视频):通知服务器端该客户端是否要发送视频。
publish(推送数据):由客户端向服务器发起请求推流到服务器。
seek(定位流的位置):定位到视频或⾳频的某个位置,以毫秒为单位。
pause(暂停):客户端告知服务端停止或恢复播放。
如果Pause为true即表示客户端请求暂停的话,服务端暂停对应的流会返回NetStream.Pause.Notify的
onStatus命令来告知客户端当前流处于暂停的状态,当Pause为false时,服务端会返回
NetStream.Unpause.Notify的命令来告知客户端当前流恢复。如果服务端对该命令响应失败,返回_error信息。
Set Chunk Size消息的处理
int SrsProtocol::recv_message(SrsCommonMessage** pmsg)
{
*pmsg = NULL;
int ret = ERROR_SUCCESS;
while (true) {
SrsCommonMessage* msg = NULL;
/*从网络读取消息*/
if ((ret = recv_interlaced_message(&msg)) != ERROR_SUCCESS) {
if (ret != ERROR_SOCKET_TIMEOUT) {
srs_error("recv interlaced message failed. ret=%d", ret);
}
return ret;
}
srs_verbose("entire msg received");
if (!msg) { /*如果消息不完整,即msg=NULL,则继续。*/
continue;
}
if (msg->size <= 0 || msg->header.payload_length <= 0) {
srs_trace("ignore empty message(type=%d, size=%d, time=%d, sid=%d).", msg->header.message_type, msg->header.payload_length,
msg->header.timestamp, msg->header.stream_id);
srs_freep(msg);
continue;
}
/*处理收到的完整message*/
if ((ret = on_recv_message(msg)) != ERROR_SUCCESS) {
srs_error("hook the received msg failed. ret=%d", ret);
srs_freep(msg);
return ret;
}
srs_verbose("get a msg with raw/undecoded payload");
/*返回收到的消息*/
*pmsg = msg;
break;
}
return ret;
}
通过recv_interlaced_message函数从网络读取到一条完整的message后,先送至on_recv_message处理。
int SrsProtocol::on_recv_message(SrsCommonMessage* msg)
{
...
/*只能处理三种类型的信息*/
switch (msg->header.message_type) {
case RTMP_MSG_SetChunkSize:
case RTMP_MSG_UserControlMessage:
case RTMP_MSG_WindowAcknowledgementSize:
/*进行解码*/
if ((ret = msg->decode_packet(this)) != ERROR_SUCCESS) {
...
}
}
switch (msg->header.message_type) {
case RTMP_MSG_WindowAcknowledgementSize: {
...
}
case RTMP_MSG_SetChunkSize: { /*set chunk size*/
SrsSetChunkSizePacket* pkt = dynamic_cast<SrsSetChunkSizePacket*>(msg->get_packet());
srs_assert(pkt != NULL);
/*设置chunk size*/
in_chunk_size = pkt->chunk_size;
srs_trace("set input chunk size to %d", pkt->chunk_size);
break;
}
case RTMP_MSG_UserControlMessage: {
...
}
}
return ret;
}
服务器先接收到的是Set Chunk Size消息
int SrsCommonMessage::decode_packet(SrsProtocol* protocol)
{
...
} else if(header.is_set_chunk_size()) {
srs_verbose("start to decode set chunk size message.");
packet = new SrsSetChunkSizePacket(); /*生成对应的packet对象*/
return packet->decode(stream); /*解码数据流*/
} else {
...
}
bool SrsMessageHeader::is_set_chunk_size()
{
return message_type == RTMP_MSG_SetChunkSize; /*message type id=1*/
}
int SrsSetWindowAckSizePacket::decode(SrsStream* stream)
{
int ret = ERROR_SUCCESS;
/*chunk size的大小为4字节*/
if (!stream->require(4)) {
ret = ERROR_RTMP_MESSAGE_DECODE;
srs_error("decode ack window size failed. ret=%d", ret);
return ret;
}
/*从message中读取chunk size的大小*/
ackowledgement_window_size = stream->read_4bytes();
srs_info("decode ack window size success");
return ret;
}
先根据chunk中的message type id判断出是Set Chunk Size消息,然后解析出消息中的chunk size。
connect消息的处理
SrsProtocol::on_recv_message不能处理connect消息,此时读取的connect消息会在SrsProtocol::recv_message函数中通过*pmsg = msg;
返回srs_rtmp_expect_message函数。
template<class T>
int srs_rtmp_expect_message(SrsProtocol* protocol, SrsCommonMessage** pmsg, T** ppacket)
{
*pmsg = NULL;
*ppacket = NULL;
int ret = ERROR_SUCCESS;
while (true) {
SrsCommonMessage* msg = NULL;
/*读取到connect消息后返回*/
if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) {
srs_error("recv message failed. ret=%d", ret);
return ret;
}
srs_verbose("recv message success.");
/*解码connect消息*/
if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) {
delete msg;
srs_error("decode message failed. ret=%d", ret);
return ret;
}
/*强转成SrsConnectAppPacket*,如果转换失败则返回NULL。*/
T* pkt = dynamic_cast<T*>(msg->get_packet());
if (!pkt) {
delete msg;
srs_trace("drop message(type=%d, size=%d, time=%d, sid=%d).", msg->header.message_type, msg->header.payload_length,
msg->header.timestamp, msg->header.stream_id);
continue;
}
*pmsg = msg; /*返回读取的msg*/
*ppacket = pkt; /*返回读取的packet*/
break;
}
return ret;
}
客户端发送至服务端的connect命令
int SrsCommonMessage::decode_packet(SrsProtocol* protocol)
{
...
/*如果message type是command message或data message*/
if (header.is_amf0_command() || header.is_amf3_command() || header.is_amf0_data() || header.is_amf3_data()) {
...
std::string command; /*读取命令*/
if ((ret = srs_amf0_read_string(stream, command)) != ERROR_SUCCESS) {
srs_error("decode AMF0/AMF3 command name failed. ret=%d", ret);
return ret;
}
...
// decode command object.
if (command == RTMP_AMF0_COMMAND_CONNECT) { /*connect命令*/
srs_info("decode the AMF0/AMF3 command(connect vhost/app message).");
packet = new SrsConnectAppPacket(); /*生成对应的对象*/
return packet->decode(stream); /*解码*/
}
...
}
int SrsConnectAppPacket::decode(SrsStream* stream)
{
int ret = ERROR_SUCCESS;
/*读取命令的名字*/
if ((ret = srs_amf0_read_string(stream, command_name)) != ERROR_SUCCESS) {
srs_error("amf0 decode connect command_name failed. ret=%d", ret);
return ret;
}
if (command_name.empty() || command_name != RTMP_AMF0_COMMAND_CONNECT) {
ret = ERROR_RTMP_AMF0_DECODE;
srs_error("amf0 decode connect command_name failed. command_name=%s, ret=%d", command_name.c_str(), ret);
return ret;
}
/*读取transaction id*/
if ((ret = srs_amf0_read_number(stream, transaction_id)) != ERROR_SUCCESS) {
srs_error("amf0 decode connect transaction_id failed. ret=%d", ret);
return ret;
}
if (transaction_id != 1.0) { /*transaction id必须为1*/
ret = ERROR_RTMP_AMF0_DECODE;
srs_error("amf0 decode connect transaction_id failed. required=%.1f, actual=%.1f, ret=%d", 1.0, transaction_id, ret);
return ret;
}
/*读取command object*/
if ((ret = srs_amf0_read_object(stream, command_object)) != ERROR_SUCCESS) {
srs_error("amf0 decode connect command_object failed. ret=%d", ret);
return ret;
}
if (command_object == NULL) {
ret = ERROR_RTMP_AMF0_DECODE;
srs_error("amf0 decode connect command_object failed. ret=%d", ret);
return ret;
}
srs_info("amf0 decode connect packet success");
return ret;
}
int SrsRtmp::connect_app(SrsRequest* req)
{
int ret = ERROR_SUCCESS;
SrsCommonMessage* msg = NULL;
SrsConnectAppPacket* pkt = NULL;
/*读取connect数据包*/
if ((ret = srs_rtmp_expect_message<SrsConnectAppPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
srs_error("expect connect app message failed. ret=%d", ret);
return ret;
}
SrsAutoFree(SrsCommonMessage, msg, false);
srs_info("get connect app message");
SrsAmf0Any* prop = NULL;
/*connect命令中必须包含tcUrl*/
if ((prop = pkt->command_object->ensure_property_string("tcUrl")) == NULL) {
ret = ERROR_RTMP_REQ_CONNECT;
srs_error("invalid request, must specifies the tcUrl. ret=%d", ret);
return ret;
}
/*获取connect命令中的tcUrl, 例如:tcUrl:rtmp://192.168.30.17/live */
req->tcUrl = srs_amf0_convert<SrsAmf0String>(prop)->value;
/*如果存在pageUrl,则解析保存。*/
if ((prop = pkt->command_object->ensure_property_string("pageUrl")) != NULL) {
req->pageUrl = srs_amf0_convert<SrsAmf0String>(prop)->value;
}
/*如果存在swfUrl,则解析保存。*/
if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) {
req->swfUrl = srs_amf0_convert<SrsAmf0String>(prop)->value;
}
/*如果存在objectEncoding,则解析保存。*/
if ((prop = pkt->command_object->ensure_property_number("objectEncoding")) != NULL) {
req->objectEncoding = srs_amf0_convert<SrsAmf0Number>(prop)->value;
}
srs_info("get connect app message params success.");
/*分解 rtmp://192.168.30.17:1935/live/livestream */
return req->discovery_app();
}
解析connect命令
没写完,待补充。。。