• RTMPdump源码分析


    typedef struct RTMP里面有成员m_vecChannelsIn,原来是一个 指针数组

    RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS];
    RTMPPacket *m_vecChannelsOut[RTMP_CHANNELS];
    int m_channelTimestamp[RTMP_CHANNELS];    /* abs timestamp of last packet */
    其中#define RTMP_CHANNELS 65600

    int m_mediaChannel;//有个成员指定
    mediaChannel

    看到在int CRTMPStream::SendPacket(unsigned int nPacketType, unsigned int nHeaderType,

    char *data, unsigned int size, unsigned int nTimestamp)函数里packet.m_nChannel被设置为4:

    packet.m_nChannel = 0x04;

    SendConnectPacket(RTMP *r, RTMPPacket *cp)里

    char pbuf[4096], *pend = pbuf + sizeof(pbuf);
    packet.m_nChannel = 0x03; /* control channel (invoke) */ packet.m_headerType
    = RTMP_PACKET_SIZE_LARGE;

    packet.m_packetType = 0x14;   /* INVOKE */
    ……

    int  RTMP_SendCreateStream(RTMP *r) 里

    char pbuf[256], *pend = pbuf + sizeof(pbuf);
    packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;

    RTMPPacket类型的结构体定义如下,一个RTMPPacket对应RTMP协议规范里面的一个块(Chunk)

    //Chunk信息  
      typedef struct RTMPPacket  
      {  
        uint8_t m_headerType;//ChunkMsgHeader的类型(4种)  
        uint8_t m_packetType;//Message type ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)  
        uint8_t m_hasAbsTimestamp;  /* Timestamp 是绝对值还是相对值? */  
        int m_nChannel;         //块流ID  
        uint32_t m_nTimeStamp;  // Timestamp  
        int32_t m_nInfoField2;  /* last 4 bytes in a long header,消息流ID */  
        uint32_t m_nBodySize;   //消息长度  
        uint32_t m_nBytesRead;  
        RTMPChunk *m_chunk;  
        char *m_body;  
      } RTMPPacket;  

    RTMPPacket里除了有m_boy,还有m_chunk,不知道作用是什么:

    其中:
    typedef struct RTMPChunk
    {
    int c_headerSize;
    int c_chunkSize;
    char *c_chunk;
    char c_header[RTMP_MAX_HEADER_SIZE];
    } RTMPChunk;
    ChunkMsgHeader的类型
    //chunk包头大小;packetSize[] = { 12, 8, 4, 1 }  

     RTMP里面有一个成员RTMP_READ m_read

    typedef struct RTMP_READ
      {
        char *buf;
        char *bufpos;
        unsigned int buflen;
        uint32_t timestamp;
        uint8_t dataType;
        uint8_t flags;
    #define RTMP_READ_HEADER    0x01
    #define RTMP_READ_RESUME    0x02
    #define RTMP_READ_NO_IGNORE    0x04
    #define RTMP_READ_GOTKF        0x08
    #define RTMP_READ_GOTFLVK    0x10
    #define RTMP_READ_SEEKING    0x20
        int8_t status;
    #define RTMP_READ_COMPLETE    -3
    #define RTMP_READ_ERROR    -2
    #define RTMP_READ_EOF    -1
    #define RTMP_READ_IGNORE    0
    
        /* if bResume == TRUE */
        uint8_t initialFrameType;
        uint32_t nResumeTS;
        char *metaHeader;
        char *initialFrame;
        uint32_t nMetaHeaderSize;
        uint32_t nInitialFrameSize;
        uint32_t nIgnoredFrameCounter;
        uint32_t nIgnoredFlvFrameCounter;
      } RTMP_READ;

    RTMP传送的视音频数据的格式和FLV(FLash Video)格式是一样的,把接收下来的数据直接存入文件就可以了。但是这些视音频数据没有文件头,是纯视音频数据,因此需要在其前面加上FLV格式的文件头,这样得到的数据存成文件后才能被一般的视频播放器所播放。FLV格式的文件头是13个字节,如代码中所示。

    //FLV文件头  
    static const char flvHeader[] = { 'F', 'L', 'V', 0x01,  
      0x00,             /* 0x04代表有音频, 0x01代表有视频 */  
      0x00, 0x00, 0x00, 0x09,  
      0x00, 0x00, 0x00, 0x00  
    }; 
    m_headerType只有四种,但是类型是uint8_t,不知道是否在RTMP_SendPacket函数里会进行处理?查看RTMP_SendPacket函数相关内容发现:
      hptr = header;  
      //把ChunkBasicHeader的Fmt类型左移6位  
      c = packet->m_headerType << 6;  
      switch (cSize)  
        {  
        //把ChunkBasicHeader的低6位设置成ChunkStreamID  
        case 0:  
          c |= packet->m_nChannel;  
          break;  
        //同理,但低6位设置成000000  
        case 1:  
          break;  
        //同理,但低6位设置成000001  
        case 2:  
          c |= 1;  
          break;  
        } 

    其中,上面的cSize由ChunkStreamID的长度决定,这就是

    Basic Header(基本头,1—3字节):该字段编码了块流ID和块类型。块类型决定了Message Header(消息头)的编码格式。该字段长度完全取决于块流ID,因为块流ID是一个可变长度的字段
    块基本头对块类型(用fmt 字段表示,参见下图) 和块流ID(chunk stream ID)进行编码。
    fmt字段占2bits,取值范围时0—3。RTMP协议最多支持65597个流,流的ID范围是3—65599。
    ID值0、1和2被保留,0表示两字节形式,1表示三字节形式,2的块流ID被保留,用于下层协议控制消息和命令。
    //当ChunkStreamID大于319时  
      if (packet->m_nChannel > 319)  
        //ChunkBasicHeader是3个字节  
        cSize = 2;  
      //当ChunkStreamID大于63时  
      else if (packet->m_nChannel > 63)  
        //ChunkBasicHeader是2个字节  
        cSize = 1;  
      if (cSize)  
        {  
        //header指针指向ChunkMsgHeader  
          header -= cSize;  
        //hsize加上ChunkBasicHeader的长度  
          hSize += cSize;  
        } 

    上面四种类型的块消息头,具体的使用:

    ☆类型0
    由11个字节组成,必须用于块流的起始块或者流时间戳重置的时候。
    timestamp(3字节):消息的绝对时间戳,如果大于等于16777215(0xFFFFFF),该字段仍为16777215,此时Extend Timestamp(扩展时间戳)字段存在,用于对溢出值进行扩展。
    否则,该字段标识整个时间戳,不需要扩展。
    message length(3字节):通常与块载荷的长度不同,块载荷长度通常表示块的最大长度128字节(除了最后一个块)和最后一个块的剩余空间。
    message type id(1字节):消息类型。
    message stream id(4字节):该字段用小端模式保存。

    ☆类型1

    由7个字节组成,不包括message stream ID(消息流ID),此时块与之前的块取相同的消息流ID。可变长度消息的流(例如,一些视频格式)应该在第一块之后使用这一格式表示之后的每个新块。

    timestamp delta(3字节):前一个块时间戳与当前块时间戳的差值,即相对时间戳,如果大于等于16777215(0xFFFFFF),该字段仍为16777215,此时Extend Timestamp(扩展时间戳)字段存在,用于对溢出值进行扩展。否则,该字段标识整个差值,不需要扩展。

    message length(3字节):通常与块载荷的长度不同,块载荷长度通常表示块的最大长度(除了最后一个块)和最后一个块的剩余空间。该长度是指为块载荷AMF编码后的长度。

    message type id(1字节):消息类型。

    ☆类型2

    由3个字节组成,既不包括message stream ID(消息流ID),也不包括message length(消息长度),此时块与之前的块取相同的消息流ID和消息长度。固定长度消息的流(例如,一些音频格式)应该在第一块之后使用这一格式表示之后的每个新块。

    timestamp delta(3字节):前一个块时间戳与当前块时间戳的差值,如果大于等于16777215(0xFFFFFF),该字段仍为16777215,此时Extend Timestamp(扩展时间戳)字段存在,用于对溢出值进行扩展。否则,该字段标识整个差值,不需要扩展。

    ☆类型3

    没有消息头,从之前具有相同块流ID的块中取相应的值。当一条消息被分割成多个块时,所有的块(除了第一个块)应该使用这种类型。

    sps和pps的打包

    sps和pps是需要在其他NALU之前打包推送给服务器。由于RTMP推送的音视频流的封装形式和FLV格式相似,向FMS等流媒体服务器推送H264和AAC直播流时,需要首先发送"AVC sequence header"和"AAC sequence header"(这两项数据包含的是重要的编码信息,没有它们,解码器将无法解码),因此这里的"AVC sequence header"就是用来打包sps和pps的。

    AVC sequence header其实就是AVCDecoderConfigurationRecord结构。

      

    其中,bool CRTMPStream::SendSpsPps(LPSpsPpsData lpSpsPpsData)中发送sps和pps包注意类型为RTMP_PACKET_TYPE_VIDEO
       packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
        packet->m_nBodySize = i;
        packet->m_nChannel = 0x04;
        packet->m_nTimeStamp = 0;
        packet->m_hasAbsTimestamp = 0;
        packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
        packet->m_nInfoField2 = m_pRtmp->m_stream_id;
        /*调用发送接口*/
        int nRet = RTMP_SendPacket(m_pRtmp, packet, 0);
    由于SDP中的SPS和PPS都是BASE64编码形式的(???),不容易理解,附件有一个工具软件可以对SDP中的SPS和PPS进行解析。
    用法是在命令行中输入:
    spsparser sps.txt pps.txt output.txt
  • 相关阅读:
    BETA 版冲刺前准备
    第十一次作业
    Alpha 冲刺 (10/10)
    Alpha 冲刺 (9/10)
    Alpha 冲刺 (8/10)
    Alpha 冲刺 (7/10)
    Alpha 冲刺 (6/10)
    Alpha 冲刺 (5/10)
    Alpha 冲刺 (4/10)
    抽奖系统现场编程
  • 原文地址:https://www.cnblogs.com/ph829/p/6669884.html
Copyright © 2020-2023  润新知