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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM