RTMP 协议

本文介绍了RTMP(实时消息协议)的基本概念,包括协议简介和RTMP Chunk(消息块)的处理,特别是消息分块的接收过程。此外,还探讨了AMF(Action Message Format)编码,包括AMF0和AMF3两种编码格式,强调其在大端法存储中的应用。通过阅读,读者将能够理解RTMP协议的握手过程和AMF编码的原理。

RTMP 协议 (Real-Time Messaging Protocol)

协议简介

RTMP协议是一个互联网TCP/IP五层体系结构中应用层的协议.
RTMP协议中基本的数据单元称为消息(Message).
当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块(Chunk)

RTMP有几个变种版本
1.原始版本
    基于TCP,默认端口1935
2.RTMPS     RTMP over TLS/SSL
3.RTMPE
4.RTMPT     封装于HTTP请求中
5.RTMFP     RTMP over UDP


RTMP 将传输的流(stream)分成片段(fragment),其大小由服务器和客户端之间动态协商,
默认的fragment大小为: 
    音频数据:   64 bytes
    视频数据:   128 bytes

PS:以下内容如无特殊说明 数据 均为 大端表示法

RTMP Chunk(RTMP消息块)

数据的发送并不是以message为单位的,而是将message拆分成chunk(>=1),chunk按序发送,接收端再根据 MsgStreamID 还原成1个message
在网络上传输数据时,消息需要被拆分成数据块(Chunk).
    |                           ChunkHeader                             |
    +------------------------------------------------------------------------------------+
    |   ChunkBasicHeader    |   ChunkMsgHeader  |   ExtendedTimeStamp   |   ChunkData   |
    +-----------------------------------------------------------------------------------+
解释:
    ChunkBasicHeader:   块基本头, 包含 『块流ID』和『块类型』(4中类型),每种类型的块**必须包含**
        块基本头示意:
            +-----------------------------------+
            |   fmt(2 bits)     |   csid(变长)   |
            +-----------------------------------+
        ftm: 表示块类型  
        csid: chunk stream id (小端表示法)   块流ID    范围[3, 65599]    [0,2]为RTMP协议保留表示特殊信息
        块基本头长度可能为1、2、3字节

        1). ChunkBasicHeader 为 1 字节时, fmt占2 bits,csid占6 bits
            csid in [0, 63]
        2). ChunkBasicHeader 为 2 字节时, 第一字节除去fmt外全置0, 剩下 1 字节用来表示csid
            csid in [64, 2^8 + 64 = 319]
        3). ChunkBasicHeader 为 3 字节时,第一字节除去fmt外全置1, 剩下2字节用来表示csid
            csid in [64, 2^16 + 64 = 65599]

            对于出现重叠的情况,应当使用使header**尽可能小**的实现

        代码逻辑上可以这样处理:
        1).先接收1字节,分别取高两位和低六位保存起来,
        2).判断低六位是全0还是全1,如果是全0,表示ChunkBasicHeader长度为2字节,那么再接收1个字节,
        并且csid需要加上64,如果全为1,则表示长度为3字节,需要在接收2字节

    ChunkMsgHeader:     块消息头, 发送消息的信息,依据块基本头中的『块类型』分为4种:
        块类型0: 11 bytes  流开始的第一个块必须使用这种类型,流时间戳回退时也必须使用这种类型头(backward seek)
                +------------------------------------------------------------------------+
                |   TimeStamp(3 bytes)  |   MsgLength(3 bytes)  |   MsgTypeID(1 byte)    |
                +------------------------------------------------------------------------+
                |   MsgStreamID(4 bytes)|
                +-----------------------+
        块类型1: 7 bytes   省去了MsgStreamID 表示此chunk和上一次发送的chunk属于同一个流
             message大小变化的流的第一个消息块之后的每一个消息的第一个块应该使用这种头
                +-------------------------------------------------------------------------------+
                |   TimeStampDelta(3 bytes) |   MsgLength(3 bytes)  |   MsgTypeID(1 byte)   |
                +-------------------------------------------------------------------------------+
        块类型2: 3 bytes 表示此chunk和上一次发送的chunk的 MsgTypeID, MsgLength, MsgStreamID 相同,
               message大小不变的流的第一条message之后的每条message的第一个chunk
                +-------------------------------+
                |   TimeStampDelta(3 bytes)     |
                +-------------------------------+
        块类型3:   0 byte
            没有头 表示此chunk的 ChunkMsgHeader 和上一个完全一样, 表示此chunk时上一个chunk的分块

        TimeStamp:          时间戳
        MsgLength:          此chunk所属Message总长度,而非 ChunkData 长度
        MsgTypeID:          消息类型
        MsgStreamID:        消息流ID    (小端表示法)
        TimeStampDelta: 时间戳增量,溢出时该字段全置1并使用  ExtendedTimeStamp 字段 存储的是完整值
        ExtendedTimeStamp:  扩展时间戳,TimeStamp溢出时使用
        ChunkData:  块数据 大小在 [0, chunksize] 之间 chunksize由控制消息 set chunk size 决定(见后面描述)

消息分块

    消息过长时需要进行分块,消息负载(PayLoad)部分被分割成大小固定的数据块(Chunk)(128 by default),并在其首部加上消息块首部(ChunkHeader).
        1.分块前
                        |                               307                                 |
        +-----------------------------------------------------------------------------------+
        |   MsgHeader   |                           MsgBody                                 |
        +-----------------------------------------------------------------------------------+
        2.分块后
                        |       128     |                       |       128     |
        +-------------------------------+       +-------------------------------+
        |ChunkBasicHeader|  MsgBody     |       |ChunkBasicHeader|  MsgBody     |
        +-------------------------------+       +-------------------------------+

                        |       51      |
        +-------------------------------+
        |ChunkBasicHeader|  MsgBody     |
        +-------------------------------+
    RTMP传输媒体数据的过程中,发送端首先把媒体数据封装成消息,然后把消息分割成消息块,最后将分割后的消息块通过TCP协议发送出去.
    接收端在通过TCP协议收到数据后,首先把消息块重新组合成消息,然后通过对消息进行解封装处理就可以恢复出媒体数据.

接收RTMP Chunk的代码处理可以参考如下:

  //|                           ChunkHeader                             |
  //  +------------------------------------------------------------------------------------+
  //  |   ChunkBasicHeader    |   ChunkMsgHeader  |   ExtendedTimeStamp   |   ChunkData   |
  //  +-----------------------------------------------------------------------------------+
BOOL RTMP_SOCKET::RecvChunk(LPRTMP_Packet& pPacket)
{
// ChunkHeader
    int nChannel           = 0;             // 低6位
    unsigned char nHeadFMT = 0;             // 高2位

    // 接收Chunk Basic Header的第一个字节
//  +-----------------------------------+
//  |   fmt(2 bits)     |   csid(变长)    |
//  +-----------------------------------+
    char BasicHeaderOneByte;
    if(!RecvBlock(&BasicHeaderOneByte, sizeof(BasicHeaderOneByte)))
    {
        return FALSE;
    }

    nHeadFMT = (BasicHeaderOneByte & 0xC0) >> 6;    //C0: 1100 0000;
    nChannel = (BasicHeaderOneByte & 0x3F);         //3F: 0011 1111;

    switch(nChannel)
    {
    case 0: 
        {
            // 全0
            // 接收 csid 额外1字节
            char CsidExt;
            if(!RecvBlock(&CsidExt, sizeof(CsidExt)))
            {
                RTMP_DEBUG_ERR(WSAGetLastError());
                return FALSE;
            }
            nChannel = CsidExt + 64;
            break;
        }
    case 1: 
        {
            // 全1
            // 接收 csid 额外2字节
            char Csid[2] = { 0 };
            if(!RecvBlock(Csid, sizeof(Csid)))
            {
                RTMP_DEBUG_ERR(WSAGetLastError());
                return FALSE;
            }
            nChannel = (Csid[1] << 8) + Csid[0] + 64;
        }
        break;
    default:
        break;
    }
    // 使用该通道上一个消息的值,因为存在消息分块,需要整合
    pPacket                      = &m_ChannelsInMap[nChannel];
    pPacket->ChunkStreamID      = nChannel;
    pPacket->ChunkMsgHeaderType = nHeadFMT;

    // Chunk Message Header
    unsigned char PacketHeader[12] = { 0 };
    // const unsigned char RTMP_PACKET_SIZE[4] = {12, 8, 4, 1};
    unsigned char PacketSize       = RTMP_PACKET_SIZE[nHeadFMT];

    PacketSize--;

    // 块类型为0,则为TimeStamp而非TimeStampDelta
    pPacket->HasAbsTimestamp = (nHeadFMT == 0);

    if(!RecvBlock((char*)PacketHeader, PacketSize))
    {
        RTMP_DEBUG_ERR(WSAGetLastError());
        return FALSE;
    }
    int nTimeStamp   = 0;
    unsigned char *p = PacketHeader;

    if(PacketSize >= 3)
    {   // 块类型 0,1,2
        nTimeStamp = _RTMP_INT24(p);    // TimeStamp/TimeStampDelta 3字节
        p         += 3;

        if(PacketSize >= 6)
        {   // 块类型0,1
            pPacket->MsgBodySize = _RTMP_INT24(p);  // MsgLength 3字节
            pPacket->BytesRead   = 0;
            p                 += 3;

            if(pPacket->MsgBody != NULL)
            {
                _SAFE_DELETE_ARRAY(pPacket->MsgBody);
            }

            if(PacketSize > 6)
            {   // 块类型0,1
                pPacket->MsgTypeID = p[0];  // MsgTypeID 1字节
                p++;

                if(PacketSize == 11)
                {   // 块类型0     
                    unsigned int MsgStreamID = 0;   
                    ::memcpy(&MsgStreamID, p, 4);
                    pPacket->MsgStreamID = MsgStreamID; // MsgStreamID 4字节
                    p                  += 4;

                }
            }
        }
        // ChunkHeader接收完毕
        // 接收ExtendedTimeStamp
        if(nTimeStamp >= 0xffffff)
        {
            // ExtendedTimeStamp 4字节
            int nTimestampExtends;
            if(!RecvBlock((char*)&nTimestampExtends, sizeof(nTimestampExtends)))
            {
                RTMP_DEBUG_ERR(WSAGetLastError());
                return FALSE;
            }
            nTimeStamp = ::ntohl(nTimestampExtends);
        }
    }

    if(pPacket->HasAbsTimestamp)
    {
        pPacket->TimeStamp = nTimeStamp;
    }
    else
    {
        pPacket->TimeStamp += nTimeStamp;
    }

    if(pPacket->MsgBodySize>0 && pPacket->MsgBody == NULL)
    {
        RTMP_ALLOC_BYTE(pPacket->MsgBody, pPacket->MsgBodySize + 1);
    }

    int nReadChunkSize = m_nInChunkSize;
    int nToRead        = pPacket->MsgBodySize - pPacket->BytesRead;

    if(nToRead < nReadChunkSize)
    {
        nReadChunkSize = nToRead;
    }

    if(pPacket->ChunkData)
    {
         /* Does the caller want the raw chunk? */
    }
    if(!RecvBlock(pPacket->MsgBody + pPacket->BytesRead, nReadChunkSize))
    {
        ASSERT(FALSE);
        RTMP_DEBUG_ERR(WSAGetLastError());
        return FALSE;
    }

    pPacket->BytesRead += nReadChunkSize;

    return TRUE;
}

RTMP流媒体播放过程:
// ToDo
    1.握手:

握手流程示意图

          C0 & S0:  1 byte 仅包含rtmp版本信息
                +-------------+
                |   version   |
                +-------------+
          C1 & S1: 1536 bytes
            1).Simple HandShake
                +---------------------------+
                |       time(4 bytes)       |
                +---------------------------+
                |       zero(4 bytes)       |
                +---------------------------+
                |  random bytes(1528 bytes) |
                +---------------------------+

                time:   时间戳, 可以为0,用作流起始时间标志
                zero;   全为0
                Simple HandShake:
                random bytes: 随机数据

            2).Complex HandShake(随机部分需要进行摘要和加密)
                两种情况:
                a).
                +---------------------------+
                |       time(4 bytes)       |
                +---------------------------+
                |       version(4 bytes)    |
                +---------------------------+
                |       key(764 bytes)      |
                +---------------------------+
                |     digest(764 bytes)     |
                +---------------------------+
                b).
                +---------------------------+
                |       time(4 bytes)       |
                +---------------------------+
                |       version(4 bytes)    |
                +---------------------------+
                |   digest(764 bytes)       |
                +---------------------------+
                |       key(764 bytes)      |
                +---------------------------+

                key结构:
                    +-------------------------------------+
                    |           offset(offset bytes)      |
                    +-------------------------------------+
                    |       public key(128 bytes)         |
                    +-------------------------------------+
                    | random data(764-offset-128-4 bytes) |
                    +-------------------------------------+
                    |           offset(4 bytes)           |
                    +-------------------------------------+
                digest结构:
                    +-------------------------------------+
                    |           offset(4 bytes)           |
                    +-------------------------------------+
                    |       random data(offset bytes)     |
                    +-------------------------------------+
                    |         digest data(32 bytes)       |
                    +-------------------------------------+
                    | random data(764-4-offset-32 bytes)  |
                    +-------------------------------------+ 

          C2 & S2:  1536 bytes 同 C1 S1 长度
            1).Simple HandShake
                +---------------------------+
                |       time(4 bytes)       |
                +---------------------------+
                |       time2(4 bytes)      |
                +---------------------------+
                |  random echo(1528 bytes)  |
                +---------------------------+

                time:   为 C1(对S2来说)的time值, 或 S1(对C2来说)的time值
                time2:  接收到C1/S1时的时间

            2).Complex HandShake 提供对C1 / S1的验证
                +-------------------------------------+
                |       random data(1504 bytes)       |
                +-------------------------------------+
                |       digest data(32 bytes)         |
                +-------------------------------------+
     C1/S1 C2/S2 生成算法
    可以参考这篇博客,讲的很清

http://blog.youkuaiyun.com/win_lin/article/details/13006803/
这里也简单说一下(PS,服务端的握手流程):
为了减少发送次数,一般流程为客户端发送c0c1,服务端接收处理后发送s0s1s2,客户端处理后发送c2,服务端处理c2后握手完成
一.简单握手:
1.接收c0c1,并记录接收到的时间,判断rtmp版本,判断zero区是否全0(非全0表示复杂握手)
2.生成s0s1s2,生成方法如上图所示,发送s0s1s2
3.接收c2,验证c2的time是否与s1的time相等,time2字段是否与接收c0c1时的时间相等,随机数据是否与s1的随机数据相等
4.完成握手

//////////////////////////////////////////////////////////////////////////
//  简单握手

BOOL RTMP_SimpleHandshake::ShakeHandWithClient(RTMP_SOCKET* pSvrSocket)
{
    unsigned int recvC1Time = 0;
    C0C1C2 c0_c1_c2;
    S0S1S2 s0_s1_s2;

    char* pC0     = NULL;
    char* pC1     = NULL;
    char* pC2     = NULL;
    char* pS0S1S2 = NULL;

    RTMP_ALLOC_BYTE(c0_c1_c2.ptr, 1 + RTMP_SIG_SIZE * 2);
    pC0 = c0_c1_c2.ptr;
    pC1 = c0_c1_c2.ptr  + 1;
    pC2 = pC1 + RTMP_SIG_SIZE;
    // 1.recv c0c1
    if (!pSvrSocket->RecvBlock(c0_c1_c2.ptr, 1 + RTMP_SIG_SIZE))
    {
        return FALSE;
    }

    recvC1Time       = htonl(timeGetTime());
    c0_c1_c2.version = pC0[0];
    // 暂时只提供 原始版本 rtmp 支持
    if (c0_c1_c2.version != RTMP_VERSION)
    {
        return FALSE;
    }
    // 处理c1
    ::memcpy(&c0_c1_c2.time, pC1, 4);
    int nZero = 0;
    if (::memcmp(pC1, &nZero, 4) != 0)
    {   // zero区非全0,代表使用复杂握手
        return FALSE;
    }
    // 2.send s0s1s2
    pS0S1S2    = s0_s1_s2.MakeS0S1S2(&c0_c1_c2, recvC1Time);
    if (!pSvrSocket->SendBlock(pS0S1S2, RTMP_SIG_SIZE * 2 + 1))
    {
        return FALSE;
    }
    // 3.rcv c2
    if (!pSvrSocket->RecvBlock(pC2, RTMP_SIG_SIZE))
    {
        return FALSE;
    }
    // Time 字段必须为S1的Time字段的值
    if (::memcmp(pC2, pS0S1S2 + 1, 4) != 0)
    {
        return FALSE;
    }
    // Time2 字段必须为接收到来自对端的上一个包的时间
    if (::memcmp(pC2 + 4, &recvC1Time, 4) != 0)
    {
        return FALSE;
    }
    // random echo 必须与S1的random data相等
    if (::memcmp(pC2 + 8, pS0S1S2 + 1 + RTMP_SIG_SIZE + 8, RTMP_SIG_SIZE - 8) != 0)
    {
        return FALSE;
    }

    return TRUE;
}

char* s0s1s2::MakeS0S1S2( LPC0C1C2 pC0C1C2 , unsigned int c1upTime)
{
    RTMP_ALLOC_BYTE(ptr, 1 + RTMP_SIG_SIZE * 2);
    char* pS0           = ptr;
    char* pS1           = pS0 + 1;
    char* pS2           = pS1 + RTMP_SIG_SIZE;
    unsigned int Time = htonl(timeGetTime());
    // s0
    *pS0                = pC0C1C2->version;
    // s1
    ::memcpy(pS1, &Time, 4);
    WriteRandomBytes(pS1 + 4, RTMP_SIG_SIZE - 4);
    // s2
    ::memcpy(pS2, &pC0C1C2->time, 4);
    ::memcpy(pS2 + 4, &c1upTime, 4);
    ::memcpy(pS2 + 8, pC0C1C2->ptr + 1537 + 8, RTMP_SIG_SIZE - 8);  // C1 random data

    return ptr;
}
二.复杂握手:
    1.接收c0c1,记录接收到的时间,判断rtmp版本0x03表示使用明文,0x06表示使用openssl加密
    2.生成s0s1s2,s0写入rtmp版本,s1先全部写入随机数据,再拷贝接收到c0c1的时间到s1的time字段,写入version字段一般为0x04050001
    3.分别对c1尝试两种格式,确定采用的哪一种格式,c1的摘要偏移量以及c1的key偏移量,然后通过c1的key计算得到s1的key,分别将key和key偏移量写入s1
    4.计算得到摘要偏移量,用被分割的两部分随机数据计算得到s1摘要,写入编译量和摘要,验证s1
    5.写入随机数据到s2,通过c1的key创建临时key,再通过临时key得到s2的摘要,写入s2,验证s2,到此可以发送s0s1s2了
    6.接收c2,验证c2
//////////////////////////////////////////////////////////////////////////
// 复杂握手

BOOL RTMP_ComplexHandShake::ShakeHandWithClient(RTMP_SOCKET* pSvrSocket)
{
    // 1.recv c0c1
    unsigned int recvC1Time = 0;
    C0C1C2 c0_c1_c2;
    S0S1S2 s0_s1_s2;

    BOOL bEncrypted        = FALSE; // 是否使用openssl加密
    char* pC0              = NULL;
    char* pC1              = NULL;
    char* pC2              = NULL;
    char* pS0              = NULL;
    char* pS1              = NULL;
    char* pS2              = NULL;
    int offalg             = 0;
    getoff* getkeyoffset   = NULL;
    getoff* getdigoffset   = NULL;

    RTMP_ALLOC_BYTE(c0_c1_c2.ptr, 1 + RTMP_SIG_SIZE * 2);
    RTMP_ALLOC_BYTE(s0_s1_s2.ptr, 1 + RTMP_SIG_SIZE * 2);

    pC0 = c0_c1_c2.ptr;
    pC1 = pC0 + 1;
    pC2 = pC1 + RTMP_SIG_SIZE;

    pS0 = s0_s1_s2.ptr;
    pS1 = pS0 + 1;
    pS2 = pS1 + RTMP_SIG_SIZE;
    // 1.读取 c0c1
    if (!pSvrSocket->RecvBlock(c0_c1_c2.ptr, 1 + RTMP_SIG_SIZE))
    {
        return FALSE;
    }
    recvC1Time       = htonl(timeGetTime());
    c0_c1_c2.version = pC0[0];
    // 暂时只提供 原始版本 rtmp 支持
    if (c0_c1_c2.version == RTMP_VERSION)
    {   // 使用明文
        bEncrypted = FALSE; 
    }
    else if (c0_c1_c2.version == RTMP_VERSION_OPENSSL)
    {   // 使用openssl加密
        bEncrypted = TRUE;
    }
    else
    {
        return FALSE;
    }

    ::memset(&pSvrSocket->m_Link.rc4keyIn, 0, sizeof(RC4_KEY));
    ::memset(&pSvrSocket->m_Link.rc4keyOut, 0, sizeof(RC4_KEY));

    ::memcpy(&c0_c1_c2.time, pC1, 4);

    // S0
    *pS0 = c0_c1_c2.version;        

    // S1
    WriteRandomBytes(pS1, RTMP_SIG_SIZE);
    // S1 time
    ::memcpy(pS1, &recvC1Time, 4);  

    int nZero = 0;
    if (::memcmp(pC1 + 4, &nZero, 4) == 0)
    {   // zero区全0,代表使用简单握手
        return FALSE;
    }
    else
    {
        // version 0x04050001
        char* pTmp = pS1 + 4;
        pTmp[0] = 0x4;
        pTmp[1] = 0x5;
        pTmp[2] = 0x0;
        pTmp[3] = 0x1;
        // schema1
        getdigoffset    = digoff[offalg];
        getkeyoffset    = keyoff[offalg];
    }

    int nC1KeyOffset    = 0;
    int nC1DigestOffset = 0;
    int nS1KeyOffset    = 0;
    int nS1DigestOffset = 0;

    // 检测C1采用的schema格式
    // 先尝试schema1
    nC1DigestOffset = getdigoffset((unsigned char*)pC1, RTMP_SIG_SIZE);
    if (!VerifyDigest(nC1DigestOffset, (unsigned char*)pC1, GenuineFPKey, 30))
    {   // 再尝试schema0
        offalg ^= 1;
        getdigoffset = digoff[offalg];
        getkeyoffset = keyoff[offalg];

        nC1DigestOffset = getdigoffset((unsigned char*)pC1, RTMP_SIG_SIZE);
        if (!VerifyDigest(nC1DigestOffset, (unsigned char*)pC1, GenuineFPKey, 30))
        {
            ASSERT(FALSE);
            return FALSE;
        }
    }

    if (bEncrypted)
    {
        int nS1KeyLength             = 0;
        unsigned char S1KeyData[128] = { 0 };       // S1 key

        if ((pSvrSocket->m_Link.dh   = DHInit(1024)) == NULL)
        {
            RTMP_DEBUG_LOG("%s: Couldn't initialize Diffie-Hellmann!",__FUNCTION__);
            return FALSE;
        }
        // 计算相对于C1的key_offset
        nC1KeyOffset = getkeyoffset((unsigned char*)pC1, RTMP_SIG_SIZE);    
        // 由C1 key计算得到S1 key
        nS1KeyLength = DHComputeSharedSecretKey((DH*)pSvrSocket->m_Link.dh, 
                                                      (unsigned char*)pC1 + nC1KeyOffset, 
                                                      128, 
                                                      S1KeyData);
        if (nS1KeyLength < 0 || nS1KeyLength > 128)
        {
            RTMP_DEBUG_LOG("Caculate S1 Key Data Failed %s", __FUNCTION__);
            return FALSE;
        }

        if (offalg == 1)
        {   //schema 0
            int nTmp = htonl(nS1KeyOffset);
            ::memcpy(pS1 + 8 + 764 - 4, &nTmp, 4);
            nS1KeyOffset = getkeyoffset((unsigned char*)pS1, RTMP_SIG_SIZE);
            ::memcpy(pS1 + nS1KeyOffset, S1KeyData, sizeof(S1KeyData));
        }
        else if (offalg == 0)
        {   //schema 1
            int nTmp = htonl(nS1KeyOffset);
            ::memcpy(pS1 + RTMP_SIG_SIZE - 4, &nTmp, 4);
            nS1KeyOffset = getkeyoffset((unsigned char*)pS1, RTMP_SIG_SIZE);
            ::memcpy(pS1 + nS1KeyOffset, S1KeyData, sizeof(S1KeyData));
        }
        else
        {
            ASSERT(FALSE);
        }

    }

    // 获取S1 digest_offset
    nS1DigestOffset = getdigoffset((unsigned char*)pS1, RTMP_SIG_SIZE);
    // 用S1的randomdata1和randomdata2计算得到S1 digest
    CalculateDigest(
                    nS1DigestOffset, 
                    (unsigned char*)pS1, 
                    GenuineFMSKey, 36, 
                    (unsigned char*)(pS1 + nS1DigestOffset));

    // 3.验证 s1 digest 算法相同
        // 用S1的randomdata1和randomdata2计算得到digest

    // 4.生成s2
    // s2结构: randomdata(1504 bytes) digest(32 bytes)
    WriteRandomBytes(pS2, RTMP_SIG_SIZE);

    unsigned char TempKey[32] = { 0 };

    // 根据c1的digest创建临时key
    HMACsha256(
                (unsigned char*)pC1 + nC1DigestOffset, 
                32, 
                GenuineFMSKey, 
                sizeof(GenuineFMSKey), 
                TempKey);

    HMACsha256(
                (unsigned char*)pS2,
                RTMP_SIG_SIZE - 32,
                TempKey,
                sizeof(TempKey),
                (unsigned char*)pS2 + RTMP_SIG_SIZE - 32);
    // 验证s2 digest
    // ...


    // 5.send s0s1s2
    if (!pSvrSocket->SendBlock(pS0, 1 + 2 * RTMP_SIG_SIZE))
    {
        return FALSE;
    }
    // 6.rcv c2
    if (!pSvrSocket->RecvBlock(pC2, RTMP_SIG_SIZE))
    {
        return FALSE;
    }

    // verify c2
    // never verify c2, for ffmpeg will failed.
    // it's ok for flash.

    return TRUE;
}

##  RTMP 协议

    RTMP 报文结构:

                RTMP 消息(基本数据单元)
|                               MsgHeader                                            |
+------------------------------------------------------------------------------------+
| MsgTypeID(1 byte) | PayloadLength(3 bytes) | TimeStamp(4 bytes) | StreamID(3 bytes)|
+------------------------------------------------------------------------------------+
|   MsgBody(PayloadLength bytes)  |
+---------------------------------+

    解释:
        MsgTypeID:      消息类型ID 1 byte,标志不同种类的消息. MsgTypeID in [1,7] 用于 控制消息,
                        MsgTypeID=8 用于传输音频, MsgTypeID=9 用于传输视频
                        MsgTypeID in [15,20] 用于 发送AMF编码的命令,负责用户与服务器间的交互(播放,暂停etc.)  

        PayloadLength:  载荷长度(MsgBody的长度)    3 bytes
        TimeStamp:      时间戳 4 bytes
        MsgBody:        载荷      *控制消息类型的载荷长度一定*
        MsgStreamID:    消息流ID   4 bytes (小端法表示) 当MsgTypeID in [1,7]时,MsgStreamID 恒为 0, 称为 控制流

        控制消息类型解释:
            ***『控制消息的 MsgStreamID=0, chunk stream id=2』***
            MsgTypeID 1:
                设置块大小(set chunk size)   设置新的块最大值
                载荷示意:
                +---------------------------+
                |   chunk size(4 bytes)     |
                +---------------------------+

            MsgTypeID 2:
                取消消息(abort message)     发送端将 块流ID(chunk stream id)发送给接收端,告知余下部分不再发送
                载荷示意:
                +-------------------------------+
                |   chunk stream id(4 bytes)    |
                +-------------------------------+
            MsgTypeID 3;
                ACK 确认消息        对等端(peer)收到的字节等同于窗口大小(window size)字节时,发送 消息序列号 给对等端
                载荷示意:
                +-------------------------------+
                |   sequence number(4 bytes)    |
                +-------------------------------+
            MsgTypeID 4:
                用户控制消息      
                载荷示意:
                +---------------------------------------+
                |   event type(2 byte)  |   event data  |
                +---------------------------------------+
                    event data 变长字节
            MsgTypeID 5:
                设置确认窗口大小(acknowledgment window size)
                载荷示意:
                +---------------------------------------+
                |   acknowledgment window size(4 bytes) |
                +---------------------------------------+
            MsgTypeID 6:
                设置对等端带宽 带宽值与窗口大小值相同,如果对等端接收此消息时收到的值与窗口大小不同,则发回 消息5(设置确认窗口大小)
                载荷示意:
                +-------------------------------------------------------+
                |   acknowledgment window size(4 bytes) |   limit type  |
                +-------------------------------------------------------+
                limit type: Hard(0) Soft(1) Dynamic(2)


##  RTMP 命令消息
    PS: 均为AMF编码

    1.网络连接命令(Net Connection Command):

connect
客户端–>服务器
CommandName “connect”
TransactionID 1
CommandObject
Optional UserArg

        服务器-->客户端
        CommandName         "_result" / "_error"
        TransactionID       1
        Properties          "fmsver" 等字段.
        Information         "code" "level" "description" 等字段

    call
    close
    createStream
        客户端-->服务器
        CommandName         "createStream"
        TransactionID       ID
        CommandObject       

        服务器-->客户端
        CommandName         "_result" / "_error"
        TransactionID       ID
        CommandObject       
        StreamID            流ID

    2.网络流命令(Net Stream Command):
    play                播放流, 多次使用可以创建播放列表
        客户端-->服务器
        CommandName         "play"
        TransactionID       0
        CommandObject       null
        StreamName          eg:     xxx(for flv) or mp3:xxx(for mp3) mp4:xxx.m4v(for H.264/AAC)
        Start               开始时间(s) 默认-2
        Duration            默认-1
        Reset               是否刷新之前的播放列表

        服务器-->客户端

        CommandName         "onStatus"
        Description         NetStream.Play.Start / NetStream.Play.StreamNotFound 
    play2
    deleteStream
        客户端-->服务器
        CommandName         "deleteStream"
        TransactionID       0
        CommandObject       null
        StreamID            要被销毁的流ID
        服务器-->客户端       none
    closeStream
    receiveAudio
    receiveVideo
    publish
    seek
    pause

“`

AMF 编码 (AMF0 AMF3) 大端法存储

AMF0

U8                              unsigned 8-bit data
U16                             unsigned 16-bit data
S16                             signed 16-bit data
U32                             unsigned 32-bit data
DOUBLE                          8-byte 双精度浮点型
KB                              1024-byte
GB                              1024*1024-KB
UTF-8                           U16 * (UTF8-char)
UTF-8-long                      U32 * (UTF8-char)
UTF-8-empty                     U16

PS: UTF8-char                   = UTF8-1 | UTF8-2 | UTF8-3 | UTF8-4

Marker:         U8
number-marker                   = 0x00 
boolean-marker                  = 0x01 
string-marker                   = 0x02
object-marker                   = 0x03 
movieclip-marker                = 0x04
null-marker                     = 0x05 
undefined-marker                = 0x06 
reference-marker                = 0x07 
ecma-array-marker               = 0x08 
object-end-marker               = 0x09 
strict-array-marker             = 0x0A 
date-marker                     = 0x0B 
long-string-marker              = 0x0C 
unsupported-marker              = 0x0D 
recordset-marker                = 0x0E 
xml-document-marker             = 0x0F
typed-object-marker             = 0x10
avmplus-object-marker           = 0x11

number-type                     = number-marker DOUBLE      (1 + 8 字节)
boolean-type                    = boolean-marker U8         (0 false, !0 true)
string-type                     = string-marker UTF-8

AMF3

// To Do

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值