一: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;
}