基于obs的rtmp源码分析之RTMP_Write(rtmp发送音视频数据或命令)

3 篇文章 1 订阅
3 篇文章 2 订阅

最近在研究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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值