typedef struct RTMPPacket
{
uint8_t m_headerType; // chunk type id (2bit)fmt 对应message head {0,3,7,11} + (6bit)chunk stream id /*大部分情况是一个字节
uint8_t m_packetType; // Message type ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)
uint8_t m_hasAbsTimestamp; /* timestamp absolute or relative? */
int m_nChannel; //chunk stream id(chunk basic header)字段
uint32_t m_nTimeStamp; /* timestamp */
int32_t m_nInfoField2; /* last 4 bytes in a long header */
uint32_t m_nBodySize; //经过AMF编码组包后,message的大小 (如果是音视频数据 即FLV格式一个Tag中Tag Data 大小)
uint32_t m_nBytesRead; // 需要发送一个Tag Data 的数据大小或者是已读取数据大小
RTMPChunk *m_chunk;
// body 指向数据的起始处。创建 body 空间时,也把 chunk header 的空间也创立了,且在 body 前面,故可以将指针指向 body 前面
char* m_body; // Tag Data 数据 Tag data的起始指针因为会循环读取,给m_body所在内存赋值,直到一个Tag Data 读完
//当chunk中的内容为命令时,m_body为chunk body(data)的起始指针
} RTMPPacket;
int
RTMP_Write(RTMP* r, const char* buf, int size, int streamIdx, int channel)
{
RTMPPacket* pkt = &r->m_write;
char* pend, * enc;
int s2 = size;
int ret = 0;
int num = 0;
int bodySize = 0;
int bytesRead = 0;
pkt->m_nChannel = channel; /* source channel */
pkt->m_nInfoField2 = r->Link.streams[streamIdx].id;
while (s2)
{
if (!pkt->m_nBytesRead)
{
if (size < 11)
{
/* FLV pkt too small */
return 0;
}
if (buf[0] == 'F' && buf[1] == 'L' && buf[2] == 'V')
{
buf += 13;
s2 -= 13;
}
// 获取 tag 类型
pkt->m_packetType = *buf++;
// 获取 data size
pkt->m_nBodySize = AMF_DecodeInt24(buf);
bodySize = pkt->m_nBodySize;
buf += 3;
// 获取时间戳
pkt->m_nTimeStamp = AMF_DecodeInt24(buf);
buf += 3;
// 加上扩展时间戳
pkt->m_nTimeStamp |= *buf++ << 24;
// 跳过 streamID
buf += 3;
// 总长度跳过 11
s2 -= 11;
// RTMP_PACKET_TYPE_INFO 代表 script
if (((pkt->m_packetType == RTMP_PACKET_TYPE_AUDIO
|| pkt->m_packetType == RTMP_PACKET_TYPE_VIDEO) &&
!pkt->m_nTimeStamp) || pkt->m_packetType == RTMP_PACKET_TYPE_INFO)
{
// 设置 basic header chunk type id = 0 chunk stream id = 0
// cs id = 0 表示 2字节头
pkt->m_headerType = RTMP_PACKET_SIZE_LARGE;
if (pkt->m_packetType == RTMP_PACKET_TYPE_INFO)
{
pkt->m_nBodySize += 16;
bodySize += 16;
}
}
else
{
pkt->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
}
// 分配 chunk 空间,既分配了 data 空间,也分配了 chunk header 最大的空间
if (!RTMPPacket_Alloc(pkt, pkt->m_nBodySize))
{
RTMP_Log(RTMP_LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__);
return FALSE;
}
enc = pkt->m_body; // 指向数据
pend = enc + pkt->m_nBodySize; // 指向数据结尾
if (pkt->m_packetType == RTMP_PACKET_TYPE_INFO)
{
enc = AMF_EncodeString(enc, pend, &av_setDataFrame);
pkt->m_nBytesRead = enc - pkt->m_body;
}
}
else
{
enc = pkt->m_body + pkt->m_nBytesRead;
}
// num 为还未发送的数据个数
num = pkt->m_nBodySize - pkt->m_nBytesRead;
if (num < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s error %d,%d", __FUNCTION__, pkt->m_nBodySize, pkt->m_nBytesRead);
return FALSE;
}
if (num > s2)
num = s2;
if (num < 0)
{
RTMP_Log(RTMP_LOGERROR, "%s error %d", __FUNCTION__, s2);
return FALSE;
}
memcpy(enc, buf, num); // 将 buf 拷贝到 pkt->m_body
pkt->m_nBytesRead += num;
bytesRead += num;
s2 -= num;
buf += num;
if (pkt->m_nBytesRead == pkt->m_nBodySize)
{
// 把 pkt 发送出去
ret = RTMP_SendPacket(r, pkt, FALSE);
RTMPPacket_Free(pkt);
pkt->m_nBytesRead = 0;
bytesRead = 0;
if (!ret)
return -1;
// 跳过 previousTagSize
// 所以 previousTagSize 作用是什么?
// 快速向前 seek 数据
buf += 4;
s2 -= 4;
if (s2 < 0)
break;
}
}
return size + s2;
}
int
RTMP_SendPacket(RTMP* r, RTMPPacket* packet, int queue)
{
const RTMPPacket* prevPacket;
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 (packet->m_nChannel >= r->m_channelsAllocatedOut)
{
int n = packet->m_nChannel + 10;
RTMPPacket** packets = realloc(r->m_vecChannelsOut, sizeof(RTMPPacket*) * n);
if (!packets)
{
free(r->m_vecChannelsOut);
r->m_vecChannelsOut = NULL;
r->m_channelsAllocatedOut = 0;
return FALSE;
}
r->m_vecChannelsOut = packets;
memset(r->m_vecChannelsOut + r->m_channelsAllocatedOut, 0, sizeof(RTMPPacket*) * (n - r->m_channelsAllocatedOut));
r->m_channelsAllocatedOut = n;
}
// 获取该通道,上一次的数据
prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
{
/* compress a bit by using the prev packet's attributes */
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) /* sanity */
{
RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.",
(unsigned char)packet->m_headerType);
return FALSE;
}
// 块头初始大小 = 基本头(1字节) + 块消息头大小(11/7/3/0) = [12, 8, 4, 1]
// 块基本头是1-3字节,因此用变量cSize来表示剩下的0-2字节
// nSize 表示块头初始大小, hSize表示块头大小, cSize来表示剩下的0-2字节
// hSize = nSize + cSize + 4
nSize = packetSize[packet->m_headerType];
hSize = nSize;
cSize = 0;
if (packet->m_nTimeStamp < last)
{
RTMP_Log(RTMP_LOGWARNING, "timestamp not increase [%u] [%u] type[%d]", packet->m_nTimeStamp, last, packet->m_packetType);
}
// 时间戳增量
t = packet->m_nTimeStamp - last;
if (packet->m_body)
{
// 块头的首指针 向前平移了 基本头(1字节) + 块消息头大小(11/7/3/0) 字节
header = packet->m_body - nSize;
// 块头的尾指针
hend = packet->m_body;
}
else
{
header = hbuf + 6;
hend = hbuf + sizeof(hbuf);
}
// 块基本头是1-3字节,因此用变量cSize来表示剩下的0-2字节
if (packet->m_nChannel > 319) // 块流id(cs id)大于319,则块基本头占3个字节
cSize = 2;
else if (packet->m_nChannel > 63) // 块流id(cs id)在64与319之间,则块基本头占2个字节
cSize = 1;
if (cSize)
{
// 向前平移了 块基本头是1-3字节,因此用变量cSize来表示剩下的0-2字节 个字节
header -= cSize;
hSize += cSize;
}
// 如果 t>0xffffff, 则需要使用 extended timestamp
if (nSize > 1 && t >= 0xffffff)
{
// 向前平移了 extended timestamp
header -= 4;
hSize += 4;
}
// 确定好 Header 的位置后,就可以开始赋值了
hptr = header;
// 设置 basic header
// 0 1 2 3 4 5 6 7
// + -+-+-+-+-+-+-+-+
// | fmt | cs id |
// +-+-+-+-+-+-+-+-+
// 一字节头
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// + -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | fmt | 0 | cs id - 64 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 两字节头
// 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 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 三字节头
// 把ChunkBasicHeader的Fmt类型左移6位
// cSize 表示块基本头剩下的0-2字节
c = packet->m_headerType << 6;
switch (cSize)
{
case 0:
c |= packet->m_nChannel; // chunk stream ID
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;
}
// 设置 message header
//消息类型 0, fmt = 0,字节长度 = 11:
// 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) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 消息类型 1, fmt = 1,字节长度 = 7:
//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 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 消息类型 2, fmt = 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
// + -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | timestamp delta |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// 消息类型 3, fmt = 3,字节长度 = 0:
//没有消息头,消息头长度是 0
// nSize 块头初始大小 = 基本头(1字节) + 块消息头大小(11/7/3/0) = [12, 8, 4, 1]
if (nSize > 1)
{
// 写入 timestamp 或 timestamp delta
hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
}
if (nSize > 4)
{
// 写入 message length
hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);
// 写入 message type id
*hptr++ = packet->m_packetType;
}
if (nSize > 8)
// 写入 message stream id
// 还原Chunk为Message的时候都是根据这个ID来辨识是否是同一个消息的Chunk的
hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
if (nSize > 1 && t >= 0xffffff)
// 写入 extended timestamp
hptr = AMF_EncodeInt32(hptr, hend, t);
// 后面的 nSize 表示 data 的长度
nSize = packet->m_nBodySize;
// buffer 表示 data 的指针
buffer = packet->m_body;
// Chunk大小,默认是128字节
nChunkSize = r->m_outChunkSize;
RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, (int)r->m_sb.sb_socket,
nSize);
/* send all chunks in one HTTP request */
if (r->Link.protocol & RTMP_FEATURE_HTTP)
{
// 需要将 data 切分成几次发送
int chunks = (nSize + nChunkSize - 1) / nChunkSize;
if (chunks > 1)
{
// 注意:ChunkBasicHeader的长度 = cSize + 1
// 消息分n块后总的开销:
// n个ChunkBasicHeader,1个ChunkMsgHeader,1个Message负载
// 实际上只有第一个Chunk是完整的,剩下的只有ChunkBasicHeader
// ? 感觉应该是 (chunks-1) * (cSize + 1) + nSize + hSize?
tlen = chunks * (cSize + 1) + nSize + hSize; // tlen 正真发送需要的字节数
tbuf = malloc(tlen);
if (!tbuf)
return FALSE;
toff = tbuf;
}
}
// chunk 大小 chunk header + data
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);
// tbuf 不为 0,代表一个 chunkSize 发送不完全部数据
if (tbuf)
{
memcpy(toff, header, nChunkSize + hSize);
toff += nChunkSize + hSize;
}
else
{
// 一次发送完全部数据
wrote = WriteN(r, header, nChunkSize + hSize);
if (!wrote)
return FALSE;
}
// nSize 为剩下的 data 长度
nSize -= nChunkSize;
// buffer 往后移动 chunksize
buffer += nChunkSize;
// chunk header 设为 0
hSize = 0;
if (nSize > 0)
{
header = buffer - 1;
// basic header 字节
hSize = 1;
if (cSize)
{
header -= cSize;
hSize += cSize;
}
// c 为 basic header 第一个字节
*header = (0xc0 | c); // 将 fmt 设置为 3,表示 message header 和 extended timestamp 都为空
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;
}
/* we invoked a remote method */
if (packet->m_packetType == RTMP_PACKET_TYPE_INVOKE)
{
AVal method;
char* ptr;
ptr = packet->m_body + 1;
AMF_DecodeString(ptr, &method);
RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);
/* keep it in call queue till result arrives */
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;
}
参考文献
https://blog.youkuaiyun.com/qq_39683826/article/details/114656972