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