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

 

 

 

 

 

 

 

 

你可能感兴趣的:(个人随笔)