rtmp協議移植注意

一:RTMP簡介

https://www.jianshu.com/p/a77156c60868

RTMP是Real Time Messaging Protocol(實時消息傳輸協議)的首字母縮寫,該協議基於TCP,是一個協議簇包括RTMP基本協議及RTMPT/RTMPS/RTMPE等多種變種,RTMP是一種設計用來進行實時數據通信的網絡協議,主要用來在Flash/AIR平臺和支持RTMP協議的流媒體/交互服務器之間進行音視頻和數據通信。

基本的數據單元:消息(message);

 在傳輸過程中 ,會分爲更小的單元塊(chunk)。,

默認的rtmp的port端口爲1935.

我這裏使用的rtmp流rtmp://192.168.3.84:1935/appdemo

2:基本定義

Payload(載荷),包含於一個數據包中的數據,音頻採樣,視頻壓縮數據。

Port(端口):TCP/IP使用小的正整數對端口進行標識

Packet(數據包)一個數據包由固定頭和載荷構成。

Transport address(傳輸地址):用以識別傳輸層端點的網絡地址和端口的組合

Message stream(消息流):通信中消息流通的一個邏輯通道。

Message stream ID(消息流ID):每個消息有一個關聯的ID,使用ID可以識別出該消息屬於哪個消息流

Chunk stream(塊流):通信中允許塊流向一個特定方向的邏輯通道。塊流可以從客戶端流向服務器,也可以從服務器流向客戶端。
Chunk stream ID(塊流 ID):每個塊有一個關聯的ID,使用ID可以識別出該塊屬於哪個塊流

Multiplexing(合成):將獨立的音頻/視頻數據合成爲一個連續的音頻/視頻流,這樣就可以同時發送視頻和音頻了。

DeMultiplexing(分解):Multiplexing 的逆向處理,將交叉的音頻和視頻數據還原成原始音頻和視頻數據的格式。

*****消息傳輸格式

Action Message Format (AMF,操作消息格式):AMF是Adobe獨家開發出來的通信協議,它採用二進制壓縮,序列化、反序列化、傳輸數據,從而爲Flash 播放器與Flash Remoting網關通信提供了一種輕量級的、高效能的通信方式。

握手之後,連接開始對一個或多個塊流進行合併。每個塊都有一個唯一ID對其進行關聯,這個ID叫做chunk stream ID(塊流ID)。這些塊通過網絡進行傳輸,在發送端,每個塊必須被完全發送纔可以發送下一塊。在接收端,這些塊根據塊流ID被組裝成消息。

  二:協議流程

1:RTMP握手過程(handshake):  RTMP_Serve(&rtmp)->SHandShake(RTMP *r)

客戶端發送c0,c1,服務器接收到發送s0,s1,客戶端收到s0s1,發送c2,服務器收齊c0,c1,發送s2,,當客戶端和服務器接收到s2,c2,握手完成。

2:建立網絡連接:客戶端發送命令消息,請求與一個服務應用實例建立連接,服務器接收到連接命令消息後,發送確認窗口的大小的協議消息到客戶端,同時連接到連接酶精靈中提到的應用程序,服務器發送設置帶寬消息到客戶端,客戶端處理設置帶寬協議後,發送確認窗口大小,服務器向客戶端發送流開始命令(stream begin)服務器發送結果(_result,通知客戶端的鏈接狀態),建立網絡流。,客戶端發送命令消息中的創建流(creatstream)命令到服務器端,服務器接收到創建流命令後,發送命令消息中的結果(_result)通知客戶端的流的狀態。

三:簡單實例程序:(通過分析rtmpsrv.c默認的rtmp例程)

RTMP_Init(&rtmp);初始化一個rtmp協議,包括輸入輸出的塊大小,音視頻解碼頻率。

RTMP_Serve(&rtmp)創建鏈接,rtmp握手程序。

RTMP_IsConnected(RTMP *r)判斷網路是否鏈接,RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)讀取和解析一個數據包(從客戶端讀取和解析的網絡數據包)。

其中:ReadN讀取第一個字節,開始對paket流進行解析。

https://blog.csdn.net/lucyTheSlayer/article/details/79788561(RTMP_ReadPacket:讀取一個數據包

 AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method);獲取具體命令的字符串

spawn_dumper(int argc, AVal *av, char *cmd)通過解析參數找到文件目錄

int ServePacket(STREAMING_SERVER *server, RTMP *r, RTMPPacket *packet)根據接收到的數據包類型解析數據包,客戶端傳來的參數不同格式,例如控制信息,此時傳過來的命令是創建connect,而Connect命令的m_packetType爲0x14 ,主要爲14類型,該包類型用來作爲流的實際控制操作,RTMP_PACKET_TYPE_INVOKE,消息Message ID爲20,此消息是經過了AMF0編碼的消息。ID爲0x04也比較重要,用來作爲流的控制類型接口。(這是比較重要的兩個編碼類型)

0×01

Chunk Size

changes the chunk size for packets

0×02

Unknown

 

0×03

Bytes Read

send every x bytes read by both sides

0×04

Ping

ping is a stream control message, has subtypes

0×05

Server BW

the servers downstream bw

0×06

Client BW

the clients upstream bw

0×07

Unknown

 

0×08

Audio Data

packet containing audio

0×09

Video Data

packet containing video data

0x0A-0x0E

Unknown

 

0x0F

FLEX_STREAM_SEND

TYPE_FLEX_STREAM_SEND

0x10

FLEX_SHARED_OBJECT

TYPE_FLEX_SHARED_OBJECT

0x11

FLEX_MESSAGE 

TYPE_FLEX_MESSAGE 

0×12

Notify

an invoke which does not expect a reply

0×13

Shared Object

has subtypes

0×14

Invoke

like remoting call, used for stream actions too.

0×16

StreamData

這是FMS3出來後新增的數據類型,這種類型數據中包含AudioData和VideoData

注意rtmp傳輸的message消息是經過AMF編碼的,首先調用AMF_Decode解碼,解析數據包的命令數據,AMFProp_GetString獲取具體命令的字符串,AVMATCH對比收到的命令與協議的規定的命令,解析不同的命令。

服務器解析客戶端發送的paket包含的鏈接參數的內容後,按照客戶端的要求,設置好網絡connection參數,向客戶端發送數據

ServeInvoke:解析客戶端傳來的命令,根據不同命令做不同的操作,解析控制流的參數:eg :AVMATCH(&method, &av_play)爲播放命令。

解析客戶端傳來的命令,這裏狙擊幾個比較重要的值的描述:(控制狀態參數)

AVMATCH(&method, &av_connect) 鏈接

連接命令的命令對象使用的值的描述:App;客戶端鏈接到服務器的名稱。Flashver:flash版本號,TcUrl服務url,audioCodecs客戶端所支持的音頻解碼器,videoCodecs支持的視頻解碼器,objectEncoding AMF的編碼方法,接收到的數據用來填充(struct rtmp)r->Link結構體.

屬性

類型

描述

示例值

App

字符串

客戶端要連接到的服務應用名

Testapp

Flashver

字符串

Flash播放器版本。和應用文檔中getversion()函數返回的字符串相同。

FMSc/1.0

SwfUrl

字符串

發起連接的swf文件的url

file://C:/ FlvPlayer.swf

TcUrl

字符串

服務url。有下列的格式。protocol://servername:port/appName/appInstance

rtmp://localhost::1935/testapp/instance1

fpad

布爾值

是否使用代理

true or false

audioCodecs

數字

指示客戶端支持的音頻編解碼器

SUPPORT_SND_MP3

videoCodecs

數字

指示支持的視頻編解碼器

SUPPORT_VID_SORENSON

pageUrl

字符串

SWF文件被加載的頁面的Url

http:// somehost/sample.html

objectEncoding

數字

AMF編碼方法

kAMF3

        在連接過程添加下列命令接口(通過對比標準流,填充連接和發送的交互接口): Sendonbdown(r);//服務器收到客戶端的創建流命令後,請求窗口大小
       Sendonbdown_2(r);//設置對端帶寬
        Sendonbdown_3(r); //設置流ID等參數
      SendConnectResult(r, txn); //鏈接返回參數

這裏之貼出部分修改程序:(用於控制鏈接部分程序)

static int
SendConnectResult(RTMP *r, double txn)
{
  RTMPPacket packet;
  char pbuf[384], *pend = pbuf+sizeof(pbuf);
  AMFObject obj;
  AMFObjectProperty p, op;
  AVal av;

  packet.m_nChannel = 0x03;     // control channel (invoke)
  packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */
  packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;
  packet.m_nTimeStamp = 0;
  packet.m_nInfoField2 = 0;
  packet.m_hasAbsTimestamp = 0;
  packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;

  char *enc = packet.m_body;
  enc = AMF_EncodeString(enc, pend, &av__result);
  enc = AMF_EncodeNumber(enc, pend, txn);
  *enc++ = AMF_OBJECT;  //object��Ŀ
  STR2AVAL(av, "FMS/3,5,1,525");
  enc = AMF_EncodeNamedString(enc, pend, &av_fmsVer, &av);
  enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 31.0);
  enc = AMF_EncodeNamedNumber(enc, pend, &av_mode, 1.0);
  *enc++ = 0;
  *enc++ = 0;
  *enc++ = AMF_OBJECT_END; //object     ����

  *enc++ = AMF_OBJECT;

  STR2AVAL(av, "status");
  enc = AMF_EncodeNamedString(enc, pend, &av_level, &av);
  STR2AVAL(av, "NetConnection.Connect.Success");
  enc = AMF_EncodeNamedString(enc, pend, &av_code, &av);
  STR2AVAL(av, "Connection succeeded.");
  enc = AMF_EncodeNamedString(enc, pend, &av_description, &av);
  enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding);
#if 0
  STR2AVAL(av, "58656322c972d6cdf2d776167575045f8484ea888e31c086f7b5ffbd0baec55ce442c2fb");
  enc = AMF_EncodeNamedString(enc, pend, &av_secureToken, &av);
#endif
 enc = AMF_EncodeNamedNumber(enc, pend, &av_data, 0);
  STR2AVAL(av,"3,5,1,525");
  enc = AMF_EncodeNamedString(enc, pend, &av_version, &av);
   *enc++ = 0;
  *enc++ = 0;
  *enc++ = AMF_OBJECT_END;

  packet.m_nBodySize = enc - packet.m_body;

  return RTMP_SendPacket(r, &packet, FALSE);
}

AVMATCH(&method, &av_createStream)創建流

AVMATCH(&method, &av_play)播放流。

通過wiresharp對比正常的rtmp流接收,我們需要添加控制接口,修改接收的控制命令接口,

      RTMP_SendCtrl(r, 0, 1, 0); //發送控制消息命令
//      SendPlayDataStart_1(r);
     SendPlayResert(r);  //發送復位命令
     SendPlayStart(r);   //流開始命令()
     SendSampleAcce(r);//發送一些源數據
     SendPlayDataStart(r);  //開始播放命令
     SendPlaypublish(r);  //開始推流

    發送完推流命令,就可以發送流。

RTMP_SendPacket(r, &packet, TRUE);向客戶端發送一個數據包命令。

這裏做一個簡單的裝包例程函數:(用來發送一個數據包:填充數據包頭,AMF數據包的格式)主要爲AMF數據組包

static int SendPlaypublish(RTMP *r)
{
  RTMPPacket packet;
  char pbuf[512], *pend = pbuf+sizeof(pbuf);

  packet.m_nChannel = 0x04;     //控制通道命令
  packet.m_headerType = 1; /* 數據包頭類型 */
  packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; //數據包類型
  packet.m_nTimeStamp = 0;
  packet.m_nInfoField2 = 0;
  packet.m_hasAbsTimestamp = 0;
  packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; //數據包

  char *enc = packet.m_body; //用於添加AMF數據包
  enc = AMF_EncodeString(enc, pend, &av_onStatus); //用於添加一個字符串
 enc = AMF_EncodeNumber(enc, pend, 0x00); //用於添加一個數據
   *enc++ = AMF_NULL;
  *enc++ = AMF_OBJECT; //AMF數據包類型是一個項目

  enc = AMF_EncodeNamedString(enc, pend, &av_level, &av_status);
  enc = AMF_EncodeNamedString(enc, pend, &av_code, &av_NetStream_Play_publish);("NetStream.Play.PublishNotify")
  enc = AMF_EncodeNamedString(enc, pend, &av_description, &av_Started_publish);("hks is now published")
  enc = AMF_EncodeNamedString(enc, pend, &av_details, &r->Link.playpath); //播放鏈接狀態
  enc = AMF_EncodeNamedString(enc, pend, &av_clientid, &av_clientid);
  *enc++ = 0;
  *enc++ = 0;
  *enc++ = AMF_OBJECT_END; //AMF結束

  packet.m_nBodySize = enc - packet.m_body;
  return RTMP_SendPacket(r, &packet, TRUE);
}

四:av_play播放命令,接收到此命令後,函數完成實時播放功能,爲了保證實時傳輸,我們開闢新的線程來處理實時封裝和實時傳輸的任務,首先發送塊大小的消息,接下來判斷是否爲第一個接入的客戶

SendH264Packet,對採集編碼獲得的H264數據進行封裝,RTMP發送的數據流格式weiflv,我們需要對包含h264的數據的nalu結構進行flv封裝,

H264格式的分析:https://mp.csdn.net/postedit/79454636

int SendH264Packet(unsigned char *data,unsigned int size,int bIsKeyFrame,unsigned int nTimeStamp ,RTMP *rtmp)  
{   //H264的裝包函數
    if(data == NULL && size<11){  
        return false;  
    }  
    unsigned char *body = (unsigned char*)malloc(size+9);  
    memset(body,0,size+9); //添加數據頭

   int i = 0;
    int j=0;
   if(bIsKeyFrame){    //是否爲關鍵幀
        body[i++] = 0x17;// 1:Iframe  7:AVC    
        body[i++] = 0x01;// AVC NALU    
        body[i++] = 0x00;  
        body[i++] = 0x00;  
        body[i++] = 0x00; 
        // 幀的大小
        body[i++] = size>>24 &0xff;  
        body[i++] = size>>16 &0xff;  
        body[i++] = size>>8 &0xff;  
        body[i++] = size&0xff;
        // NALU data   
        memcpy(&body[i],data,size);
        SendVideoSpsPps(metaData.Pps,metaData.nPpsLen,metaData.Sps,metaData.nSpsLen,rtmp, nTimeStamp);
//        SendPlayDataStart_2(rtmp);

    }else{  
        body[i++] = 0x27;// 2:Pframe  7:AVC   
        body[i++] = 0x01;// AVC NALU   
        body[i++] = 0x00;  
        body[i++] = 0x00;  
        body[i++] = 0x00;  
/*        
        body[i++] = 0x00;// AVC NALU    
        body[i++] = 0x00;  
        body[i++] = 0x00;  
        body[i++] = 0x01;

        body[i++] = 0x09;  
        body[i++] = 0x30;


        body[i++] = 0x00;// AVC NALU    
        body[i++] = 0x00;  
        body[i++] = 0x00;  
        body[i++] = 0x00;
*/        // NALU size   
        body[i++] = size>>24 &0xff;  
        body[i++] = size>>16 &0xff;  
        body[i++] = size>>8 &0xff;  
        body[i++] = size&0xff;
        // NALU data   
        memcpy(&body[i],data,size);

    }   
    int bRet = SendPacket(RTMP_PACKET_TYPE_VIDEO,body,i+size,nTimeStamp,rtmp);  
    free(body);  

    return bRet;  
}

int SendPacket(unsigned int nPacketType,unsigned char *data,unsigned int size,unsigned int nTimestamp ,RTMP *rtmp)  
{  
int i;
    RTMPPacket* packet;
    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+size);
    memset(packet,0,RTMP_HEAD_SIZE);
    /**/
    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    //
    packet->m_nBodySize = size;  //包大小
    memcpy(packet->m_body,data,size);
    packet->m_hasAbsTimestamp = 0;
    packet->m_packetType = nPacketType; /*包類型*/
    packet->m_nInfoField2 = 0x01;//rtmp->m_stream_id;rtmp的劉大小
    RTMP_LogPrintf(" !!!RTMPH264_send packet->m_nInfoField2=%d\n",packet->m_nInfoField2);
    packet->m_nChannel = 0x04;
    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;//頭大小類型
    if (RTMP_PACKET_TYPE_AUDIO ==nPacketType && size !=4)
    {
        packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
    }
    packet->m_nTimeStamp = nTimestamp;
    int nRet =0;

    if (RTMP_IsConnected(rtmp))
    {
        nRet = RTMP_SendPacket(rtmp,packet,TRUE);    }
    free(packet);
    return nRet;  
}

int SendVideoSpsPps(unsigned char *pps,int pps_len,unsigned char * sps,int sps_len ,RTMP *rtmp,unsigned int timestamp)
{ //解析sps信息,如果是sps或者pps幀,則
    RTMPPacket * packet=NULL;//rtmp的數據包
    unsigned char * body=NULL;
    int i;
    packet = (RTMPPacket *)malloc(RTMP_HEAD_SIZE+1024); //分配一個數據包״̬
    memset(packet,0,RTMP_HEAD_SIZE+1024); //數據包加頭大小
    packet->m_body = (char *)packet + RTMP_HEAD_SIZE;
    body = (unsigned char *)packet->m_body;
                
    i = 0;
    body[i++] = 0x17; //sps頭
    body[i++] = 0x00;

   body[i++] = 0x00;
    body[i++] = 0x00;
    body[i++] = 0x00;

    /*avc的解析配置記錄*/
    body[i++] = 0x01;
    body[i++] = sps[1]; //42
    body[i++] = sps[2]; //00
    body[i++] = sps[3]; //1f
    body[i++] = 0xff;
        
    /*sps長度以及參數*/
    body[i++]   = 0xe1;
    body[i++] = (sps_len >> 8) & 0xff;
    body[i++] = sps_len & 0xff;
    memcpy(&body[i],sps,sps_len);
    i +=  sps_len;

    /*pps長度以及參數*/

    body[i++]   = 0x01;
    body[i++] = (pps_len >> 8) & 0xff;
    body[i++] = (pps_len) & 0xff;
    memcpy(&body[i],pps,pps_len);
    i +=  pps_len;

//填充數據包參數(調試的時候就因爲配置的問題,導致接收不到數據,所以需要注意)
    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; //數據包類型
    packet->m_nBodySize = i; //數據包大小+頭
    packet->m_nChannel = 0x04; //數據包通道
    packet->m_nTimeStamp = timestamp;
    packet->m_hasAbsTimestamp = 0;
    packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
    packet->m_nInfoField2 = 0x01;//rtmp->m_stream_id;是流id,在配置過程中參數都要匹配
    int nRet = RTMP_SendPacket(rtmp,packet,TRUE); //發送數據包
    free(packet); 
    return nRet;
}

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章