接下来我们分析RTMP_SendPacket函数。我们先了解一下rtmp的消息格式chunk。
RTMP的head组成
RTMP的head在协议中的表现形式是chunk head,前面已经说到一个Message + head可以分成一个和多个chunk,为了区分这些chunk,肯定是需要一个chunk head的,具体的实现就把Message head的信息和chunk head的信息合并在一起以chunk head的形式表现。
一个完整的chunk的组成如下图所示
![](https://img-blog.csdn.net/20170204154702257?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
Chunk basic header:
该字段包含chunk的stream ID和 type 。chunk的Type决定了消息头的编码方式。该字段的长度完全依赖于stream ID,该字段是一个可变长的字段。
![](https://img-blog.csdn.net/20170204155921773?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20170204160021762?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
Chunk Msg Header:0, 3 ,7, 11
该字段包含了将要发送的消息的信息(或者是一部分,一个消息拆成多个chunk的情况下是一部分)该字段的长度由chunk basic header中的type决定。
![](https://img-blog.csdn.net/20170204160054590?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
Extend Timestamp: 0 ,4 bytes
该字段发送的时候必须是正常的时间戳设置成0xffffff时,当正常时间戳不为0xffffff时,该字段不发送。当时间戳比0xffffff小该字段不发送,当时间戳比0xffffff大时该字段必须发送,且正常时间戳设置成0xffffff。
Chunk Data
实际数据(Payload),可以是信令,也可以是媒体数据。
总结如下图所示:
![](https://img-blog.csdn.net/20170204155645348?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20170204160243279?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
6.1.2 块消息头
有四种格式的块消息ID,供块流基本头中的fmt 字段选择。一个实现应该使用最紧致的方式来表示块消息头。
6.1.2.1 类型0
0 类型的块长度为11 字节。在一个块流的开始和时间戳返回的时候必须有这种块。
![](https://img-blog.csdn.net/20170204161950569?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
时间戳:3 字节
对于0 类型的块。消息的绝对时间戳在这里发送。如果时间戳大于或等于16777215(16 进制0x00ffffff),该值必须为16777215,并且扩展时间戳必须出现。否则该值就是整个的时间戳。
6.1.2.2. 类型1
类型1 的块占7 个字节长。消息流ID 不包含在本块中。块的消息流ID 与先前的块相同。具有可变大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
![](https://img-blog.csdn.net/20170204162120148?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
6.1.2.3. 类型2
类型2 的块占3 个字节。既不包含流ID 也不包含消息长度。本块使用的流ID 和消息长度与先前的块相同。具有固定大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
![](https://img-blog.csdn.net/20170204162208462?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
6.1.2.4 类型3
类型3 的块没有头。流ID,消息长度,时间戳都不出现。这种类型的块使用与先前块相同的数据。当一个消息被分成多个块,除了第一块以外,所有的块都应使用这种类型。示例可参考6.2.2 节中的例2 。由相同大小,流ID,和时间间隔的流在类型2 的块之后应使用这种块。示例可参考6.2.1 节中的例1 。如果第一个消息和第二个消息的时间增量与第一个消息的时间戳相同,那么0类型的块之后必须是3 类型的块而,不需要类型2 的块来注册时间增量。如果类型3 的块在类型0 的块之后,那么类型3 的时间戳增量与0 类型的块的时间戳相同。
时间戳增量:3 字节
对于类型1 的块和类型2 的块,本字段表示先前块的时间戳与当前块的时间戳的差值。如果增量大于等于1677215(16 进制0x00ffffff),这个值必须是16777215 ,并且扩展时间戳必须出现。否则这个值就是整个的增量。
消息长度:3 字节
对于类型0 或类型1 的块本字段表示消息的长度。注意,这个值通常与负载长度是不相同的。The chunk payload length is the maximum chunk size for all but the last chunk, and the remainder (which may be the entire length, for small messages) for the last chunk.
消息类型ID:1 字节
对于0 类型和1 类型的块,本字段发送消息类型。
消息流ID:4 字节
对于0 类型的块,本字段存储消息流ID。通常,在一个块流中的消息来自于同一个消息流。虽然,由于不同的消息可能复用到一个块流中而使头压缩无法有效实施。但是,如果一个消息流关闭而另一个消息流才打开,那么通过发送一个新的0 类型的块重复使用一个存在的块流也不是不可以。
6.1.3. 扩展时间戳
只有当块消息头中的普通时间戳设置为0x00ffffff 时,本字段才被传送。如果普通时间戳的值小于0x00ffffff,那么本字段一定不能出现。如果时间戳字段不出现本字段也一定不能出现。类型3 的块一定不能含有本字段。本字段在块消息头之后,块时间之前。
![](https://img-blog.csdn.net/20170204162424477?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHdqYXpqeDEzMTQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
代码分析如下:
- int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)
- {
- const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
- uint32_t last = 0;
- int nSize;
- int hSize, cSize;
- char *header, *hptr, *hend, hbuf[RTMP_MAX_HEADER_SIZE], c;
- uint32_t t;
- char *buffer, *tbuf = NULL, *toff = NULL;
- int nChunkSize;
- int tlen;
-
-
-
-
-
-
-
-
- if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
- {
-
-
-
-
-
- if (prevPacket->m_nBodySize == packet->m_nBodySize&& prevPacket->m_packetType == packet->m_packetType
- && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)
- packet->m_headerType = RTMP_PACKET_SIZE_SMALL;
-
-
-
- if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp && packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
- packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;
- last = prevPacket->m_nTimeStamp;
- }
-
-
- if (packet->m_headerType > 3)
- {
- RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.", (unsigned char)packet->m_headerType);
- return FALSE;
- }
-
-
-
-
- nSize = packetSize[packet->m_headerType];
- hSize = nSize;
- cSize = 0;
-
- t = packet->m_nTimeStamp - last;
-
- if (packet->m_body)
- {
-
- header = packet->m_body - nSize;
-
- hend = packet->m_body;
-
- }
- else
- {
- header = hbuf + 6;
- hend = hbuf + sizeof(hbuf);
- }
-
- if (packet->m_nChannel > 319)
- cSize = 2;
- else if (packet->m_nChannel > 63)
- cSize = 1;
-
- if (cSize)
- {
- header -= cSize;
- hSize += cSize;
- }
-
-
-
- if (nSize > 1 && t >= 0xffffff)
- {
- header -= 4;
- hSize += 4;
- }
-
- hptr = header;
- c = packet->m_headerType << 6;
-
-
- switch (cSize)
- {
- case 0:
- c |= packet->m_nChannel;
- break;
- case 1:
- break;
- case 2:
- c |= 1;
- break;
- }
- *hptr++ = c;
-
-
- if (cSize)
- {
- int tmp = packet->m_nChannel - 64;
- *hptr++ = tmp & 0xff;
- if (cSize == 2)
- *hptr++ = tmp >> 8;
- }
-
- if (nSize > 1)
- {
-
- hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
- }
-
- if (nSize > 4)
- {
-
- hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);
- *hptr++ = packet->m_packetType;
- }
-
- if (nSize > 8)
- hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
-
- if (nSize > 1 && t >= 0xffffff)
- hptr = AMF_EncodeInt32(hptr, hend, t);
-
-
-
- nSize = packet->m_nBodySize;
- buffer = packet->m_body;
- nChunkSize = r->m_outChunkSize;
-
- RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket, nSize);
-
- if (r->Link.protocol & RTMP_FEATURE_HTTP)
- {
-
-
-
-
- int chunks = (nSize + nChunkSize - 1) / nChunkSize;
- if (chunks > 1)
- {
-
-
-
-
- tlen = chunks * (cSize + 1) + nSize + hSize;
- tbuf = malloc(tlen);
- if (!tbuf)
- return FALSE;
- toff = tbuf;
- }
- }
-
-
- while (nSize + hSize)
- {
- int wrote;
-
- if (nSize < nChunkSize)
- nChunkSize = nSize;
-
- RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)header, hSize);
- RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)buffer, nChunkSize);
-
-
- if (tbuf)
- {
-
-
- memcpy(toff, header, nChunkSize + hSize);
- toff += nChunkSize + hSize;
- }
- else
- {
-
- wrote = WriteN(r, header, nChunkSize + hSize);
- if (!wrote)
- return FALSE;
- }
- nSize -= nChunkSize;
- buffer += nChunkSize;
- hSize = 0;
-
-
- if (nSize > 0)
- {
- header = buffer - 1;
- hSize = 1;
- if (cSize)
- {
- header -= cSize;
- hSize += cSize;
- }
- *header = (0xc0 | c);
- if (cSize)
- {
- int tmp = packet->m_nChannel - 64;
- header[1] = tmp & 0xff;
- if (cSize == 2)
- header[2] = tmp >> 8;
- }
- }
- }
- if (tbuf)
- {
- int wrote = WriteN(r, tbuf, toff - tbuf);
- free(tbuf);
- tbuf = NULL;
- if (!wrote)
- return FALSE;
- }
-
-
- if (packet->m_packetType == 0x14)
- {
- AVal method;
- char *ptr;
- ptr = packet->m_body + 1;
- AMF_DecodeString(ptr, &method);
- RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);
-
- if (queue)
- {
- int txn;
- ptr += 3 + method.av_len;
- txn = (int)AMF_DecodeNumber(ptr);
- AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);
- }
- }
-
- if (!r->m_vecChannelsOut[packet->m_nChannel])
- r->m_vecChannelsOut[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
- memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
- return TRUE;
- }