RTMP服务器(一)
一:chunk_header/rtmp_header
块由头和数据组成。块头由三部分组成:
块基本头:1到3 字节
本字段包含块流ID和块类型。块类型决定编码的消息头的格式。长度取决于块流ID。块流ID是可变长字段。
块消息头:0,3,7或11字节。本字段编码要发送的消息的信息。本字段的长度,取决于块头中指定的块类型。
扩展时间戳:0个或4字节
本字段必须在发送普通时间戳(普通时间戳是指块消息头中的时间戳)设置为0xffffff时发送,正常时间戳为其他值时都不应发送本值。当普通时间戳的值小于0xffffff时,本字段不用出现,而应当使用正常时间戳字段。
//rtmp packet
typedef struct RtmpPacket
{
ChunkHeader m_ChunkHeader; //chunkheader 1-3字节
//chunk msg header
unsigned int m_TimeStamp; //时间戳3字节 大端模式
unsigned int m_MessageLenth; //数据大小3个字节//amfsize 大端模式
unsigned char m_MessageTypeID; //数据类型1个字节 //MessageTypeID
unsigned int m_MessgageStreamID; //流ID 4字节 “小”端模式
unsigned int m_ExtendTimeStamp; //扩展时间戳4字节
//则m_MessageLenth== 100,m_BytesRead == 50;
unsigned int m_hasAbsTimestamp; //11字节的完整ChunkMsgHeader的TimeStamp是绝对值
char * m_PacketData; //包数据内容
}RtmpPacket;
//chunk 头
typedef struct ChunkHeader
{
unsigned char m_F0mt; //块类型2位
unsigned int m_CsID/m_ChunkStreamId; //编码块流ID 6位或字节位或字节位 大端模式
}ChunkHeader;
Rtmp可以衍生出21种packetheader,即ChunkHeader一个字节,没有扩展时间戳,常用的4种分别为:
类型0
0类型的块长度为11字节。在一个块流的开始和时间戳返回的时候必须有这种块。
时间戳:3字节
对于0类型的块。消息的绝对时间戳在这里发送。如果时间戳大于或等于16777215(16进制0x00ffffff),该值必须为16777215,并且扩展时间戳必须出现。否则该值就是整个的时间戳。
类型1
类型1的块占7个字节长。消息流 ID不包含在本块中。块的消息流ID与先前的块相同。具有可变大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
类型2
类型2的块占3个字节。既不包含流ID也不包含消息长度。本块使用的流ID和消息长度与先前的块相同。具有固定大小消息的流,在第一个消息之后的每个消息的第一个块应该使用这个格式。
类型3
类型3的块没有头。流ID,消息长度,时间戳都不出现。这种类型的块使用与先前块相同的数据。当一个消息被分成多个块,除了第一块以外,所有的块都应使用这种类型。由相同大小,流ID,和时间间隔的流在类型2的块之后应使用这种块。
如果第一个消息和第二个消息的时间增量与第一个消息的时间戳相同,那么0类型的块之后必须是3类型的块而,不需要类型2的块来注册时间增量。如果类型3的块在类型0的块之后,那么类型3的时间戳增量与0类型的块的时间戳相同。
时间戳增量:3字节
对于类型1的块和类型2的块,本字段表示先前块的时间戳与当前块的时间戳的差值。如果增量大于等于1677215(16进制0x00ffffff),这个值必须是16777215 ,并且扩展时间戳必须出现。否则这个值就是整个的增量。
消息长度:3字节
对于类型0或类型1的块本字段表示消息的长度。
注意,这个值通常与负载长度是不相同的。The chunk payload length is themaximum chunk size for all but the last chunk, and the remainder (which maybe the entire length, for small messages) for the last chunk.
消息类型ID:1字节
对于0类型和1类型的块,本字段发送消息类型。
消息流ID:4 字节
对于0类型的块,本字段存储消息流ID。通常,在一个块流中的消息来自于同一个消息流。虽然,由于不同的消息可能复用到一个块流中而使头压缩无法有效实施。但是,如果一个消息流关闭而另一个消息流才打开,那么通过发送一个新的0类型的块重复使用一个存在的块流也不是不可以。
/* MessageTypeID -数据类型
0× Chunk Size changes the chunk size for packets
0× Unknown
0× Bytes Read send every x bytes read by both sides
0× Ping ping is a stream control message, hassubtypes
0× Server BW the servers downstream bw
0× Client BW the clients upstream bw
0× Unknown
0× Audio Data packet containing audio
0× Video Data packet containing video data
0x0A-0x0E Unknown
0x0F FLEX_STREAM_SENDTYPE_FLEX_STREAM_SEND
0x10 FLEX_SHARED_OBJECT TYPE_FLEX_SHARED_OBJECT
0x11 FLEX_MESSAGE TYPE_FLEX_MESSAGE
0× Notify an invoke which does not expect a reply
0× Shared Object has subtypes
0× Invoke like remoting call, used for stream actionstoo.
0× StreamData 这是FMS3出来后新增的数据类型,这种类型数据中包含AudioData和VideoData
*/
/* RTMP_PACKET_TYPE_... 0x00 */
#define RTMP_PACKET_TYPE_CHUNK_SIZE 0x01
/* RTMP_PACKET_TYPE_... 0x02 */
#define RTMP_PACKET_TYPE_BYTES_READ_REPORT 0x03
#define RTMP_PACKET_TYPE_CONTROL 0x04
#define RTMP_PACKET_TYPE_SERVER_BW 0x05
#define RTMP_PACKET_TYPE_CLIENT_BW 0x06
/* RTMP_PACKET_TYPE_... 0x07 */
#define RTMP_PACKET_TYPE_AUDIO 0x08
#define RTMP_PACKET_TYPE_VIDEO 0x09
/* RTMP_PACKET_TYPE_... 0x0A */
/* RTMP_PACKET_TYPE_... 0x0B */
/* RTMP_PACKET_TYPE_... 0x0C */
/* RTMP_PACKET_TYPE_... 0x0D */
/* RTMP_PACKET_TYPE_... 0x0E */
#define RTMP_PACKET_TYPE_FLEX_STREAM_SEND 0x0F
#define RTMP_PACKET_TYPE_FLEX_SHARED_OBJECT 0x10
#define RTMP_PACKET_TYPE_FLEX_MESSAGE 0x11
#define RTMP_PACKET_TYPE_INFO 0x12
#define RTMP_PACKET_TYPE_SHARED_OBJECT 0x13
#define RTMP_PACKET_TYPE_INVOKE 0x14
/* RTMP_PACKET_TYPE_... 0x15 */
#define RTMP_PACKET_TYPE_FLASH_VIDEO 0x16
二:amf结构
/*
AMF数据由部分组成:ObjType加上ObjValue。ObjType的大小为一个字节。ObjValue的大小不固定,和ObjType相关。常用的ObjType类型和对应的ObjValue大小整理如下,详细的ObjType的数据在本文的最下面列出:
类型说明(ObjType) 数据 dataSize
CORE_String 0x02 2字节(2字节的数据纪录了String的实际长度)
CORE_Object 0x03 0字节(开始嵌套x00000009表示嵌套结束)
NULL 0x05 0字节空字节无意义
CORE_NUMBER 0x00 8字节
CORE_Map 0x08 4字节(开始嵌套)
CORE_BOOLEAN 0x01 1字节
ObjValue不一定是一个固定的大小,他可以包含另外一个AMF数据,这另外一个AMF数据里面又有ObjType 加上ObjValue,也就是AMF数据的嵌套关系
AMF0数据的嵌套关系如下:
Object={ObjType + ObjValue}
CORE_BOOLEAN={Value(1 Byte)}
CORE_NUMBER={Value(8 Byte)}
CORE_String={StringLen(2Byte) + StringValue(StringLen Byte)}
CORE_DATE={value(10 Byte)}
CORE_Array={ArrayLen(4 Byte)+ Object}
CORE_Map={MapNum(4 Byte) +CORE_Object}
CORE_Object={CORE_String +Object}
*/
typedef enum
{
AMF_NUMBER = 0, AMF_BOOLEAN,AMF_STRING, AMF_OBJECT,
AMF_MOVIECLIP, /* reserved, not used */
AMF_NULL, AMF_UNDEFINED,AMF_REFERENCE, AMF_ECMA_ARRAY,AMF_OBJECT_END,
AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING,AMF_UNSUPPORTED,
AMF_RECORDSET, /* reserved, not used */
AMF_XML_DOC, AMF_TYPED_OBJECT,
AMF_AVMPLUS, /* switch to AMF3 */
AMF_INVALID = 0xff
} AMFDataType;
三:rtmp拆包组包
分块使高层协议的大消息分割成小的消息,保证大的低优先级消息不阻塞小的高优先级消息。分块把原本应该消息中包含的信息压缩在块头中减少了小块消息发送的开销。
块大小是可配置的。最大块是65535字节,最小块是128字节。块越大CPU使用率越低,但是也导致大的写入,在低带宽下产生其他内容的延迟。块大小对每个方向都保持独立。
1:拆包:即当组成一个完整的rtmppacket有header和body,将此包分成小块,一般发送第一个包时候需要完整的12字节头或更多,剩下发送的包根据ChunkStreamId:编码块流ID匹配。
2:组包:即当接收到rtmp分片包,根据ChunkStreamId:编码块流ID匹配组成一个完整的rtmp包,无论是命令或数据。
四:flv发包处理
这里需要解析flv流。
1:发包:首先:去掉9个字节的flv文件头(如果是流不用去掉),
然后分析各个tag类型,flv有三种tag分别是Script,Video,Audio.
Script:里面存放的是flv流或文件的信息,宽高,文件长度等等。
Video:存放的视频数据(其中包含sps,pps ,用AvcC结构表示),结构如下
typedef struct Tag_Video_AvcC
{
unsigned char configurationVersion;
unsigned char AVCProfileIndication;
unsigned char profile_compatibility;
unsigned char AVCLevelIndication;
unsigned char reserved_1;
unsigned char lengthSizeMinusOne;
unsigned char reserved_2;
unsigned char numOfSequenceParameterSets; //一般都是一个
unsigned int sequenceParameterSetLength; //sps长度
unsigned char * sequenceParameterSetNALUnit; //sps
unsigned char numOfPictureParameterSets; //一般都是一个
unsigned int pictureParameterSetLength; //pps长度
unsigned char * pictureParameterSetNALUnit; //pps
unsigned char reserved_3;
unsigned char chroma_format;
unsigned char reserved_4;
unsigned char bit_depth_luma_minus8;
unsigned char reserved_5;
unsigned char bit_depth_chroma_minus8;
unsigned char numOfSequenceParameterSetExt;
unsigned int sequenceParameterSetExtLength;
unsigned char * sequenceParameterSetExtNALUnit;
}Video_AvcC;
Audio: 存放的视频数据(其中包含声道,采样率样本等信息 ,用ASC结构表示):结构如下:
typedef struct Tag_Audio_ASC
{
unsigned char audioObjectType; //编解码类型:AAC-LC = 0x02
unsigned char samplingFrequencyIndex; //采样率44100 = 0x04
unsigned char channelConfiguration; //声道= 2
unsigned char framelengthFlag; //标志位,位于表明IMDCT窗口长度= 0
unsigned char dependsOnCoreCoder; //标志位,表明是否依赖于corecoder = 0
unsigned char extensionFlag; //选择了AAC-LC = 0
}Audio_ASC;
发包的时候,根据获取到的流信息://音频(x08)、视频(x09)和script data(x12),其它保留
填写rtmppacket头然后拆包发出去。
2:收包:
收包的时候,首先将分片的包组成完整的包,然后根据上面结构解析出视频:sps,pps。音频采样率,样本,声道等,如果是aac填写7字节adts头,然后一帧一帧的接收数据存入内存或者渲染。
五:命令流程
以s代表服务器,c代表客户端(publish或play)。
1:publish
Accept之后
c->s:c0c1
s->c:s0s1s2
c->s:c2
c->s:connect命令
s->c:windowacknowledgement size set peer bandwidth __result
c->s:windowacknowledgement size
s->c:onbwdone
c->s:checkbw
c->s:releasestream fcpublish createstream
s->c:__result
c->s:publish
s->c:onstatus;
c->s:开始向服务器发送数据
2:player
Accept之后
c->s:c0c1
s->c:s0s1s2
c->s:c2
c->s:connect命令
s->c:windowacknowledgement size set peerbandwidth __result
c->s:windowacknowledgement size
s->c:onbwdone
c->s:checkbw
c->s:createstream
s->c:__result
c->s:play
s->c:onstatus;
s->c:服务器开始向player发送数据
当c向服务器发送命令,服务器给个响应,回复的命令对应是用rtmp body中的mumber做对应的。再有当MessageTypeID /type id == 0x14或0x11时候说明服务器给客户端发送的命令完成。
即MessageTypeID /type id == #define RTMP_PACKET_TYPE_INVOKE 0x14或
MessageTypeID/typeid == #define RTMP_PACKET_TYPE_FLEX_MESSAGE 0x11。
解释下三个ID;
1: MessageTypeID:MessageTypeID中的不同类型,如:
#define RTMP_PACKET_TYPE_AUDIO 0x08
#define RTMP_PACKET_TYPE_VIDEO 0x09
2:chunkstream id :分片区分,如有个包大于128分包,当传输第二个分片的时候要和第一个分片的chunk stream id相同才能判断是一个包。
3:streamid :connectstream之前填写0,之后为了区分flv/f4v流中多路流由server传给client。
//注: AMF3 比AMF0 的 数据body前面多一个字节的“00”;
例如:
amf0: (HandleInvoke(r,packet->m_body,packet->m_nBodySize)
Amf3: (HandleInvoke(r,packet->m_body + 1,packet->m_nBodySize - 1)
上面用以区分 amf0 和amf3。