RTMP之RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)流程理解

来源:互联网 发布:学编程学校 编辑:程序博客网 时间:2024/06/03 18:21
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;
/*
fmt 字节
case 0:chunk msg header length:11byte(timestamp:3byte;msg length:3byte;msg type id:1byte;msg stream id:4byte)
case 1:chunk msg header length:7byte(timestamp:3byte;msg length:3byte;msg type id:1byte;)
case 2:chunk msg header length:3byte(timestamp:3byte;)
case 3:chunk msg header length:0byte
*/
    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];
//前一个 packet 存在,且不是完整的chunk msg header 因此有可能调整块消息头的类型
    if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
    {
        /* compress a bit by using the prev packet's attributes */
// 获取ChunkMsgHeader类型,前一个Chunk与当前Chunk比较
// 如果前后两个块的大小、包类型及块头类型都相同,则将块头类型fmt设为2,
// 即可省略消息长度、消息类型id、消息流id
        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;//case 2


// 前后两个块的时间戳相同,且块头类型fmt为2,则相应的时间戳也可省略,因此将块头类型置为3
        if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp
                && packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
            packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;//case 3
        last = prevPacket->m_nTimeStamp;//前一个packet的时间戳
    }


// 块头类型fmt取值0、1、2、3,超过3就表示出错(fmt占二个字节)
    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;
    }


/*
块头初始大小 = 块基本头(1byte)+ 块消息头大小(11/7/3/0) = [12,8,4,1]
块基本头是1-3字节,因此用变量cSize来表示剩下的0-2字节
nSize 表示块头初始大小, hSize表示块头大小
*/
    nSize = packetSize[packet->m_headerType];//[12,8,4,1]
    hSize = nSize;
    cSize = 0;
    t = packet->m_nTimeStamp - last;//时间戳增量


//m_body是指向负载数据首地址的指针;“ - ”号用于指针前移
    if (packet->m_body)
    {
        header = packet->m_body - nSize;//块头的首指针
        hend = packet->m_body;//块头的尾指针
    }
    else
    {
        header = hbuf + 6;
        hend = hbuf + sizeof(hbuf);
    }


    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)
    {
        header -= cSize;//header指向块头
        hSize += cSize;//hSize加上ChunkBasicHeader的长度(比初始长度多出来的长度)
    }
//nSize>1 意味着块消息头至少有3byte,即timestamp段
//相对TimeStamp大于0xffffff,此时需要使用ExtendTimeStamp
    if (nSize > 1 && t >= 0xffffff)
    {
        header -= 4;
        hSize += 4;
    }


    hptr = header;
    c = packet->m_headerType << 6;//把ChunkBasicHeader的Fmt类型左移6位
    switch (cSize)
    {
    case 0:
        c |= packet->m_nChannel;// 把ChunkBasicHeader的低6位设置成ChunkStreamID( cs id )
        break;
    case 1:// 同理,但低6位设置成000000
        break;
    case 2:// 同理,但低6位设置成000001
        c |= 1;
        break;
    }
    *hptr++ = c;// 可以拆分成两句*hptr=c; hptr++,此时hptr指向第2个字节
    if (cSize)//设置basic header的第二(三)个字节值
    {
        int tmp = packet->m_nChannel - 64;//将要放到第2字节的内容tmp
        *hptr++ = tmp & 0xff;//获取低位存储与第2字节
        if (cSize == 2)//ChunkBasicHeader是最大的3字节时,获取高位存储于最后1个字节(注意:排序使用大端序列,和主机相反)
            *hptr++ = tmp >> 8;
    }


    if (nSize > 1)//ChunkMsgHeader长度为11、7、3, 都含有timestamp(3字节)
    {//将时间戳(相对或绝对)转化为3个字节存入hptr,如果时间戳超过0xffffff,则后面还要填入Extend Timestamp
        hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
    }


    if (nSize > 4)// ChunkMsgHeader长度为11、7,都含有 msg length + msg type id
    {
        hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize);//// 将消息长度(msg length)转化为3个字节存入hptr
        *hptr++ = packet->m_packetType;
    }


    if (nSize > 8)//含有msg stream id( 小端)
        hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);


    if (nSize > 1 && t >= 0xffffff)//如果时间戳大于0xffffff,则需要写入Extend Timestamp
        hptr = AMF_EncodeInt32(hptr, hend, t);


//到此为止,已经将块头填写好了
// 此时nSize表示负载数据的长度, buffer是指向负载数据区的指针
    nSize = packet->m_nBodySize;
    buffer = packet->m_body;
    nChunkSize = r->m_outChunkSize;//Chunk大小,默认是128字节


    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)
    {
//nSize:Message负载长度;nChunkSize:Chunk长度;
//例nSize:307,nChunkSize:128;
// 可分为(307 + 128 - 1)/128 = 3个
// 为什么加 nChunkSize - 1?因为除法会只取整数部分!
        int chunks = (nSize+nChunkSize-1) / nChunkSize;
        if (chunks > 1)// Chunk个数超过一个
        {
// 注意:ChunkBasicHeader的长度 = cSize + 1
// 消息分n块后总的开销:
// n个ChunkBasicHeader,1个ChunkMsgHeader,1个Message负载
// 实际上只有第一个Chunk是完整的,剩下的只有ChunkBasicHeader
            tlen = chunks * (cSize + 1) + nSize + hSize;
            tbuf = malloc(tlen);
            if (!tbuf)
                return FALSE;
            toff = tbuf;
        }
    }
// 消息的负载 + 头
    while (nSize + hSize)
    {
        int wrote;


if (nSize < nChunkSize) //消息负载大小 < Chunk大小(不用分块)
            nChunkSize = nSize;//Chunk可能小于设定值


        RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)header, hSize);
        RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)buffer, nChunkSize);
// 如果r->Link.protocol采用Http协议,则将RTMP包数据封装成多个Chunk,然后一次性发送。
// 否则每封装成一个块,就立即发送出去
        if (tbuf)
        {
// 将从Chunk头开始的nChunkSize + hSize个字节拷贝至toff中,
// 这些拷贝的数据包括块头数据(hSize字节)和nChunkSize个负载数据
            memcpy(toff, header, nChunkSize + hSize);
            toff += nChunkSize + hSize;
        }
        else// 负载数据长度不超过设定的块大小,不需要分块,因此tbuf为NULL;或者r->Link.protocol不采用Http
        {
            wrote = WriteN(r, header, nChunkSize + hSize);// 直接将负载数据和块头数据发送出去
            if (!wrote)
                return FALSE;
        }
nSize -= nChunkSize; //消息负载长度 - Chunk负载长度
        buffer += nChunkSize;//buffer指针前移1个Chunk负载长度
        hSize = 0;// 重置块头大小为0,后续的块只需要有基本头(或加上扩展时间戳)即可


        if (nSize > 0)// 如果消息负载数据还没有发完,准备填充下一个块的块头数据
        {
            header = buffer - 1;
            hSize = 1;
            if (cSize)
            {
                header -= cSize;
                hSize += cSize;
            }
            *header = (0xc0 | c);
            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;
}
原创粉丝点击