最近在研究OBS源码,里面有一个很重要的模块是推流模块,OBS是使用RTMP进行推流的,源码里面也有RTMP的源码,翻了一下目前网上没有详细的RTMP源码注释,所以这里基于OBS项目,来详细讲一下RTMP源码包括内核数据结构、公共函数接口功能。关于具体的RTMP协议,网上有很多RTMP协议可以找到这里只做简单介绍,重点是代码的注释分析。关于RTMP源码的内核结构体,在代码中涉及的我会有标注,在另一个博文中具体分析了核心结构体注释。
接口比较多写的比较细,文章比较长,有些函数体中无效的代码(例如 log日志、容错代码我会省略)耐心看哈哈哈。
小弟写的比较辛苦给个关注吧
这里所有的实际测试推流操作均为向斗鱼上推流(因为我一直用它看直播哈)
这一博文主要讲rtmp读取服务端数据并处理,在上一个博文中提到当RTMP连接网络并握手后发送av_connect命令,服务器发送反馈消息后,客户端需要响应。RTMP_ConnectStream 主要作用为读取反馈命令并调用响应的接口进行处理。
int RTMP_ConnectStream(RTMP *r, int seekTime)
{
RTMPPacket packet = {
0 }; //重新初始化的packet
/* seekTime was already set by SetupStream / SetupURL.
* This is only needed by ReconnectStream.
*/
if (seekTime > 0)
r->Link.seekTime = seekTime;
r->m_mediaChannel = 0;
//循环读取套接字中缓存内容
while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet))
{
if (RTMPPacket_IsReady(&packet))//message大小是否等于已读大小 即 m_nBytesRead是否等于m_nBodySize
{
if (!packet.m_nBodySize)
continue;
//音视频数据跳过
if ((packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) ||
(packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) ||
(packet.m_packetType == RTMP_PACKET_TYPE_INFO))
{
RTMP_Log(RTMP_LOGWARNING, "Received FLV packet before play()! Ignoring.");
RTMPPacket_Free(&packet);
continue;
}
RTMP_ClientPacket(r, &packet);
RTMPPacket_Free(&packet);
}
}
return r->m_bPlaying;
}
重要接口哈
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
//读取一个chunk内容,有时message被分成很多chunk,所以需要循环调用RTMP_ReadPacket
//RTMPPacket是根据chunk定义的 一个chunk相当于一个RTMPPacket
//r->m_vecChannelsIn二级指针记录读取上一个chunk的信息(当收到一个message被分成多个chunk,需要循环读取 RTMP_ReadPacket)
//因为当message被分成多个chunk时,只有第一个chunk的信息是完整的(message head为11字节),所以就需要在一个message中记录上一个chunk的基本信息,如msg大小、msg type等
//memcpy C语言拷贝内存函数
int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)
{
uint8_t hbuf[RTMP_MAX_HEADER_SIZE] = {
0 };
char *header = (char *)hbuf;//chunk header 头部指针变量
int nSize;//message header 的大小
int hSize;//chunk basic + message head 大小
int nToRead;//总的message数据减去已读数据等于还需要读取数据
int nChunk;//chunk输入大小(chunk data大小)
// int didAlloc = FALSE;
int extendedTimestamp = 0;
RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d", __FUNCTION__, (int)r->m_sb.sb_socket);
if (ReadN(r, (char *)hbuf, 1) == 0) //先读一个字节,看看socket缓存是否为空
{
RTMP_Log(RTMP_LOGDEBUG, "%s, failed to read RTMP packet header", __FUNCTION__);
return FALSE;
}
packet->m_headerType = (hbuf[0] & 0xc0) >> 6; //第一个字节向前移6bit,得出fmt
packet->m_nChannel = (hbuf[0] & 0x3f);//chunk stream id
header++; //chunk header 头部指针加一
if (packet->m_nChannel == 0)//Chunk basic header有2个字节
{
if (ReadN(r, (char *)&hbuf[1], 1) != 1)//读第2个字节失败
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte",
__FUNCTION__);
return FALSE;
}
packet->m_nChannel = hbuf[1];
packet->m_nChannel += 64;
header++;
}
else if (packet->m_nChannel == 1)//Chunk basic header有3个字节
{
int tmp;
if (ReadN(r, (char *)&hbuf[1], 2) != 2)//读第3个字节失败
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte",
__FUNCTION__);
return FALSE;
}
tmp = (hbuf[2] << 8) + hbuf[1];
packet->m_nChannel = tmp + 64;
RTMP_Log(RTMP_LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);
header += 2;
}
//小Tips: static const int packetSize[] = { 12, 8, 4, 1 }数组定义message header 的大小
//这里message head 在原本{ 11, 7, 3, 0 }上加一,但是我不明白为什么会加一,因为后边在传输时会减掉一,这里注意一下
nSize = packetSize[packet->m_headerType];//message header 的大小
if (packet->m_nChannel >= r->m_channelsAllocatedIn)//r->m_channelsAllocatedIn为0
{
int n = packet->m_nChannel + 10; //n 为10
int *timestamp = realloc(r->m_channelTimestamp, sizeof(int) * n);
//r->m_vecChannelsIn二级指针记录读取上一个chunk的信息(当收到一个message被分成多个chunk,需要循环读取 RTMP_ReadPacket)
//因为当message被分成多个chunk时,只有第一个chunk的信息是完整的(message head为11字节),所以就需要在一个message中记录上一个chunk的基本信息,如msg大小、msg type等
RTMPPacket **packets = realloc(r->m_vecChannelsIn, sizeof(RTMPPacket*) * n);
if (!timestamp)
free(r->m_channelTimestamp);
if (!packets)
free(r->m_vecChannelsIn);
r->m_channelTimestamp = timestamp;
r->m_vecChannelsIn = packets;
if (!timestamp || !packets)
{
r->m_channelsAllocatedIn = 0;
return FALSE;
}
//n 为10,初始化m_channelTimestamp和m_vecChannelsIn后的10个指针为0
memset(r->m_channelTimestamp + r->m_channelsAllocatedIn, 0, sizeof(int) * (n - r->m_channelsAllocatedIn));
memset(r->m_vecChannelsIn + r->m_channelsAllocatedIn, 0, sizeof(RTMPPacket*) * (n - r->m_channelsAllocatedIn));
r->m_channelsAllocatedIn = n;
}
if (nSize == RTMP_LARGE_HEADER_SIZE) /* if we get a full header the timestamp is absolute */
packet->m_hasAbsTimestamp = TRUE; //nSize为18有绝对时间戳
else if (nSize < RTMP_LARGE_HEADER_SIZE)//nSize小于18
{
/* using values from the last message of this channel */
//取上一个chunk的基本信息,(当收到一个message被分成多个chunk,需要循环读取 RTMP_ReadPacket)
if (r->m_vecChannelsIn[packet->m_nChannel])
//取上一个chunk的基本信息,拷贝到packet
memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel],
sizeof(RTMPPacket));
}
nSize--; //因为初始化packetSize时给每个元素自动加一在这要减一,这里就是我上面讲的不知道为什么packetSize每个元素要加一
if (nSize > 0 && ReadN(r, header, nSize) != nSize) //读取socket缓存nSize字节
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header. type: %x",
__FUNCTION__, (unsigned int)hbuf[0]);
return FALSE;
}
hSize = nSize + (header - (char *)hbuf);// message head + chunk basic head 的大小
if (nSize >= 3)
{
packet->m_nTimeStamp = AMF_DecodeInt24(header);
if (nSize >= 6)
{
//经过AMF编码组包后,message的大小 (如果是音视频数据 即FLV格式一个Tag中Tag Data大小),大端转小端
packet->m_nBodySize = AMF_DecodeInt24(header + 3); //经过AMF编码组包后,message的大小
packet->m_nBytesRead = 0;
RTMPPacket_Free(packet);
if (nSize > 6)
{
packet->m_packetType = header[6];// Message type ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)/*一个字节
if (nSize == 11)
packet->m_nInfoField2 = DecodeInt32LE(header + 7);//message stream id
}
}
extendedTimestamp = (packet->m_nTimeStamp == 0xffffff); //绝对时间戳
if (extendedTimestamp)
{