最近在研究OBS源码,里面有一个很重要的模块是推流模块,OBS是使用RTMP进行推流的,源码里面也有RTMP的源码,翻了一下目前网上没有详细的RTMP源码注释,所以这里基于OBS项目,来详细讲一下RTMP源码包括内核数据结构、公共函数接口功能。关于具体的RTMP协议,网上有很多RTMP协议可以找到这里只做简单介绍,重点是代码的注释分析。关于RTMP源码的内核结构体,在代码中涉及的我会有标注,在另一个博文中具体分析了核心结构体注释。
接口比较多写的比较细,文章比较长,有些函数体中无效的代码(例如 log日志、容错代码我会省略)耐心看哈哈哈。
小弟写的比较辛苦给个关注吧
这里所有的实际测试推流操作均为向斗鱼上推流(因为我一直用它看直播哈)
这一博文主要讲的是,RTMP推送数据(命令+音视频数据)的过程,因为RTMP推流采用的是FLV格式,所以所有数据都必须封装成FLV格式,也就是我们平时说的复用过程,顺便说一下拉流就是解复用的过程。FLV格式协议网上有很多,可以自行查找,这里只做接口介绍哈
typedef struct RTMPPacket //一个Tag Data的数据结构(这里是包含了由flv封装的Tag Data head)或者是"命令"数据,根据chunk结构定义的
{
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? */// 是否含有Extend timeStamp字段
int m_nChannel; //chunk stream id(chunk basic header)字段
uint32_t m_nTimeStamp; /* timestamp */
int32_t m_nInfoField2; //message stream id /* 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;
char *m_body;//Tag Data数据 Tag data的起始指针因为会循环读取,给m_body所在内存赋值,直到一个Tag Data读完
//当chunk中的内容为命令时,m_body为chunk body(data)的起始指针
} RTMPPacket
//发送一个Tag Data数据 FLV格式 重新封装成chunk数据
int RTMP_Write(RTMP *r, const char *buf/*flv封装的数据*/, int size/*发送数据大小*/, int streamIdx)
{
//将buf重新封装到RTMPPacket *pkt中
RTMPPacket *pkt = &r->m_write;
char *enc;//chunk数据(chunk data) chunk data的起始指针
char *pend;
int s2 = size;//写入大小剩余缓存
int ret;
int num;
pkt->m_nChannel = 0x04; //音视频数据的chunk stream id /* source channel */
pkt->m_nInfoField2 = r->Link.streams[streamIdx].id; //根据RTMP_AddStream接口增加id,在RTMP_Connect博文中有介绍
while (s2)
{
if (!pkt->m_nBytesRead)//需要发送一个Tag Data的数据大小或者是已读取数据大小
{
if (size < 11)
{
/* FLV pkt too small */
return 0;
}
//把flv head找出来
if (buf[0] == 'F' && buf[1] == 'L' && buf[2] == 'V')
{
buf += 13; //FLV(3个)+version(1)+Flag(1)+flv head 大小(4)+第一个tag的previous 大小(4)=13
s2 -= 13;
}
pkt->m_packetType = *buf++;//message type 音频0x08 视频0x09 元数据 0x12
pkt->m_nBodySize = AMF_DecodeInt24(buf);//FLV格式一个Tag中Tag Data大小包括(Tag Data head)
buf += 3;
pkt->m_nTimeStamp = AMF_DecodeInt24(buf);//时间戳
buf += 3;
pkt->m_nTimeStamp |= *buf++ << 24;
buf += 3; //stream id为0
s2 -= 11;
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)
{
//对于音视频数据来说每一个Tag都会写入pkt->m_packetType
pkt->m_headerType = RTMP_PACKET_SIZE_LARGE; // chunk type id (2bit)fmt 为0
if (pkt->m_packetType == RTMP_PACKET_TYPE_INFO)
pkt->m_nBodySize += 16;
}
else
{
pkt->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
}
//重新分配m_body指向存储空间 一定要分配要不然memcpy会报错
if (!RTMPPacket_Alloc(pkt, pkt->m_nBodySize))
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to allocate packet", __FUNCTION__);
return FALSE;
}
enc = pkt->m_body;
pend = enc + pkt->m_nBodySize;//m_body 尾指针
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 = pkt->m_nBodySize - pkt->m_nBytesRead;
if (num > s2)
num = s2;
//将编码好的数据赋值给m_body也是就是enc
memcpy(enc, buf, num);
pkt->m_nBytesRead += num;
s2 -= num;
buf += num;
if (pkt->m_nBytesRead == pkt->m_nBodySize)
{
ret = RTMP_SendPacket(r, pkt, FALSE); //发送一个message若需要自动分成多个chunk
RTMP_Log(RTMP_LOGERROR, "%s, Finshed", __FUNCTION__);
RTMPPacket_Free(pkt);
pkt->m_nBytesRead = 0;
if (!ret)
return -1;
buf += 4; //FLV 格式 previous Tag Size 大小,准备发送下一个Tag Data
s2 -= 4;
if (s2 < 0)
break;
}
}
return size+s2;
}
int RTMPPacket_Alloc(RTMPPacket *p, int nSize) //重新分配m_body指向存储空间
{
char *ptr = calloc(1, nSize + RTMP_MAX_HEADER_SIZE);//message 大小+18
if (!ptr)
return FALSE;
//chunk的头指针后移18(因为有时m_body需要减去头部大小,RTMP_Write接口中有体现),配之后m_body就不会变了
p->m_body = ptr + RTMP_MAX_HEADER_SIZE;
p->m_nBytesRead = 0;//已读字节数
return TRUE;
}
重要接口哈
//若是发送音视频数据,一次发送一个Tag Data数据也就是一个message,这点要注意哈
//r->m_vecChannelsOut二级指针记录写入上一个chunk的信息,当写一个Tag Data(message),要是Tag Data很大就需要被分成多个chunk发送
//因为当message被分成多个chunk时,只有第一个chunk的信息是完整的(message head为11字节),所以就需要在一个message中记录上一个chunk的基本信息,如msg大小、msg type等
int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue) //发送一个message若需要自动分成多个chunk
{
//queue 是否将发送的命令加入队列,也就是说当收到回复时,是否需要做出相应
const RTMPPacket *prevPacket;//记录写入上一个chunk的信息
uint32_t last = 0; //记录写入上一个时间戳
int nSize;//只是一个变量,在接口中被不同变量赋值(但最后用作message大小)
int hSize;//message header 大小
int cSize;//fmt + chunk stream id 大小 1到3个字节
char *header;//chunk header 头部头指针变量
char *hptr;//chunk header 头部指针变量 hptr=header
char *hend;//chunk header 头部尾指针变量
char c;//chunk stream id
char * hbuf[RTMP_MAX_HEADER_SIZE];
uint32_t t;
char *buffer, *tbuf = NULL, *toff = NULL;
int nChunkSize; //一个chunk大小
int tlen;
if (packet->m_nChannel >= r->m_channelsAllocatedOut)//m_channelsAllocatedOut为0
{
int n = packet->m_nChannel + 10; //n为10
//r->m_vecChannelsOut二级指针记录写入上一个chunk的信息,当写一个Tag Data(message),要是Tag Data很大就需要被分成多个chunk发送
//因为当message被分成多个chunk时,只有第一个chunk的信息是完整的(message head为11字节),所以就需要在一个message中记录上一个chunk的基本信息,如msg大小、msg type等
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;
//n为10,初始化r->m_vecChannelsOut后10个指针为0
memset(r->m_vecChannelsOut + r->m_channelsAllocatedOut, 0, sizeof(RTMPPacket*) * (n - r->m_channelsAllocatedOut));
r->m_channelsAllocatedOut = n;
}
//记录写入上一个chunk的信息(但是一般每个Tag Data packet->m_headerType == RTMP_PACKET_SIZE_LARGE )
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;
}
//小Tips: static const int packetSize[] = { 12, 8, 4, 1 }数组定义message header 的大小
//这里message head 在原本{ 11, 7, 3, 0 }上加一,但是我不明白为什么会加一,因为后边在传输时会减掉一,这里注意一下
nSize = packetSize[packet->m_headerType]; //message head 大小 初始化时多加一
hSize = nSize; //一个chunk的 message head 大小
cSize = 0;//fmt + chunk stream id 大小 1到3个字节
t = packet->m_nTimeStamp - last;
if (packet->m_body)
{
header = packet->m_body - nSize;//(nSize=packetSize原本就多加了一个,所以剪完后直接指向chunk basic head)
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; //chunk stream id
switch (cSize)
{
case 0:
c |= packet->m_nChannel; //chunk head 为一个字节
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; //nSize为一个变量
buffer = packet->m_body;
nChunkSize = r->m_outChunkSize;//chunk一次输出大小(chunk data大小)
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)
{
int chunks = (nSize+nChunkSize-1) / nChunkSize;
if (chunks > 1)
{
tlen = chunks * (cSize + 1) + nSize + hSize;
tbuf = malloc(tlen);
if (!tbuf)
return FALSE;
toff = tbuf;
}
}
//将一个Tag Data分成多个chunk,除了第一个chunk head是完整的以外,剩下的chunk都只有时间戳
while (nSize + hSize) //nSize 在这里代表message 大小,hSize为chunk head 大小 = 整个Tag Data 的大小
{
int wrote;
if (nSize < nChunkSize)//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);//将数据写入socket缓存中,通过TCP发送
if (!wrote)
return FALSE;
}
nSize -= nChunkSize;
buffer += nChunkSize;
hSize = 0; //重置chunk head 大小
if (nSize > 0)
{
header = buffer - 1;
hSize = 1;
if (cSize)
{
header -= cSize;
hSize += cSize;
}
*header = (0xc0 | c);//packet->m_headerType = 3(11000000) message head只有时间戳
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 */
//这里用到的是类似于RPC的远程调用原理,若发送的是"命令"的消息,如:av_connect,将命令写入r->m_methodCalls中
//当收到回复"命令"的消息时,匹配r->m_methodCalls中的命令,来调用相应的函数进行处理
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));
//将当前的packet赋值给r->m_vecChannelsOut,作为上一个Tag Data的信息
memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
return TRUE;
}
//写入socket 缓存
static int WriteN(RTMP *r, const char *buffer, int n)
{
const char *ptr = buffer;
.....
while (n > 0)
{
int nBytes;
if (r->Link.protocol & RTMP_FEATURE_HTTP)
nBytes = HTTP_Post(r, RTMPT_SEND, ptr, n);
else if(r->m_bCustomSend && r->m_customSendFunc)
nBytes = r->m_customSendFunc(&r->m_sb, ptr, n, r->m_customSendParam);
else
nBytes = RTMPSockBuf_Send(&r->m_sb, ptr, n);
/*RTMP_Log(RTMP_LOGDEBUG, "%s: %d\n", __FUNCTION__, nBytes); */
if (nBytes < 0)
{
int sockerr = GetSockError();
....
}
...
n -= nBytes;
ptr += nBytes;
}
....
return n == 0;
}
int RTMPSockBuf_Send(RTMPSockBuf *sb, const char *buf, int len)
{
int rc;
...
#if defined(CRYPTO) && !defined(NO_SSL)
if (sb->sb_ssl)
{
rc = TLS_write(sb->sb_ssl, buf, len);
}
else
#endif
{
rc = send(sb->sb_socket, buf, len, 0); //windows API 发送数据
}
return rc;
}