基于obs的rtmp源码分析之RTMP_ReadPacket(rtmp读取服务端数据并处理)

最近在研究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)
        {
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值