第一个byte包含音频的编码参数:
第一帧共4个byte:
1) 第1个byte : audioCodeId=10,如果是44KHZ、16bit、双声道,第一个byte是0xAF。如果实际采样率不是5.5KHZ、11KHZ、22KHZ、44KHZ,就选一个接近的。
2) 第2个byte : 0x00 表示是sequence header
3) 第3-4个byte : 0x14,0x10
1) 第1个byte : audioCodeId=10,如果是44KHZ、16bit、双声道,第一个byte是0xAF。如果实际采样率不是5.5KHZ、11KHZ、22KHZ、44KHZ,就选一个接近的。
2) 第2个byte : 0x01 表示是raw data
3) 第3byte开始 : 去掉前7个byte的AAC头之后的AAC数据。
1) 第1个byte : audioCodeId=8,如果是11KHZ、16bit、单声道,第一个byte是0x86。如果实际采样率不是5.5KHZ、11KHZ、22KHZ、44KHZ,就选一个接近的。
2) 第2个byte开始是存放G.711U数据。
1) 第1个byte : audioCodeId=7,如果是11KHZ、16bit、单声道,第一个byte是0x76。如果实际采样率不是5.5KHZ、11KHZ、22KHZ、44KHZ,就选一个接近的。
2) 第2个byte开始是存放G.711A数据。
]]>AVCDecoderConfigurationRecord
SPS
PPS
1) frame type
高4位表示是否是关键帧,低4位表示编码类型。SPS和PPS是关键帧用1表示,H.264(AVC)对应的值是7,所以这个byte是0x17。
2) fixed
这4个byte的具体含义我也不清楚,但对于H.264 AVCDecoderConfigurationRecord来说是 0x00 0x00 0x00 0x00
3) configurationVersion
0x01
4) AVCProfileIndication
SPS的第2个byte,也就是去掉第一个byte的NALU type之后的那个byte。
5) profile_compatibility
SPS的第3个byte。
6) AVCLevelIndication
SPS的第4个byte。
7) lengthSizeMinusOne
0xff
8) sps number
SPS的序号,是0xE1
9) sps data length
这2个byte表示SPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:
packet[i++] = (data_size>>8)&0xff;packet[i++] = data_size&0xff;
10) sps data
最后是存放SPS数据,不包含开头的分隔符00000001。
11) pps number
PPS的序号,是0x01
12) pps data length
这2个byte表示PPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。
13) pps data
最后是存放PPS数据,不包含开头的分隔符00000001。
1) frame type
如果关键帧就是0x17,如果非关键帧就是0x27。高4位表示是否是关键帧,低4位表示编码类型,H.264(AVC)对应的值是7。
2) fixed
这4个byte的具体含义我也不清楚,但对于H.264来说都是固定值 0x01 0x00 0x00 0x00
3) NALU size
这4个byte表示NALU数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:
packet[i++] = (data_size>>24)&0xff;packet[i++] = (data_size>>16)&0xff;packet[i++] = (data_size>>8)&0xff;packet[i++] = data_size&0xff;
4) NALU data
最后是存放长度为[NALU size]的NALU数据,不包含开头的分隔符00000001。
rtmp协议中并没有H.265,这里讲的H.265封装是在H.264的基础上改进的。
HEVCDecoderConfigurationRecord
SPS
PPS
VPS
1) frame type
高4位表示是否是关键帧,低4位表示编码类型。SPS、PPS、VPS是关键帧用1表示,H.265(HEVC)对应的值是12,所以这个byte是0x1C。
2) fixed
这4个byte的具体含义我也不清楚,但对于H.265 HEVCDecoderConfigurationRecord来说是 0x00 0x00 0x00 0x00
3) configurationVersion
0x01
4) HEVCProfileIndication
SPS的第2个byte,也就是去掉第一个byte的NALU type之后的那个byte。
5) profile_compatibility
SPS的第3个byte。
6) HEVCLevelIndication
SPS的第4个byte。
7) lengthSizeMinusOne
0x03
8) sps number
SPS的序号,是0xE1
9) sps data length
这2个byte表示SPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:
packet[i++] = (data_size>>8)&0xff;packet[i++] = data_size&0xff;
10) sps data
最后是存放SPS数据,不包含开头的分隔符00000001。
11) pps number
PPS的序号,是0x01
12) pps data length
这2个byte表示PPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。
13) pps data
最后是存放PPS数据,不包含开头的分隔符00000001。
14) vps number
VPS的序号我也不知道,我暂时写0x01
15) vps data length
这2个byte表示VPS数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。
16) vps data
最后是存放VPS数据,不包含开头的分隔符00000001。
1) frame type
如果关键帧就是0x1C,如果非关键帧就是0x2C。高4位表示是否是关键帧,低4位表示编码类型,H.265(HEVC)对应的值是12。
2) fixed
这4个byte的具体含义我也不清楚,我暂时写 0x01 0x00 0x00 0x00
3) NALU size
这4个byte表示NALU数据的长度,长度是去掉分隔符00000001之后的长度。高位存在第一个byte,低位存在最后一个byte。如:
packet[i++] = (data_size>>24)&0xff;packet[i++] = (data_size>>16)&0xff;packet[i++] = (data_size>>8)&0xff;packet[i++] = data_size&0xff;
4) NALU data
最后是存放长度为[NALU size]的NALU数据,不包含开头的分隔符00000001。
]]>在传输完元数据onMetaData之后就开始传输音视频数据了,如视频是H.264编码,第一帧视频帧需要是SPS和PPS,后面才是I帧和P帧。
如果是发布端向服务器推流,方向是C->S,如果是播放器向服务器拉流,方向是S->C。
音视频数据包的封装后面的文章在详谈,这里只是讲信令交互。
Video Data
RTMP Header 00.. .... = Format: 0 ..00 0101 = Chunk Stream ID: 5 Timestamp: 0 Body size: 31 Type ID: Video Data (0x09) Stream ID: 1RTMP Body Control: 0x17 (keyframe H.264) Video data: 00000000014d401effe1000b674d401e95a0280f69b01001...
Video Data
RTMP Header 01.. .... = Format: 1 ..00 0101 = Chunk Stream ID: 5 Timestamp delta: 0 Timestamp: 0 (calculated) Body size: 14488 Type ID: Video Data (0x09)RTMP Body Control: 0x17 (keyframe H.264) Video data: 010000000000388f00000001674d401e95a0280f69b01000...
Audio Data
RTMP Header 00.. .... = Format: 0 ..00 0100 = Chunk Stream ID: 4 Timestamp: 137 Body size: 321 Type ID: Audio Data (0x08) Stream ID: 1RTMP Body Control: 0x86 (G711U 11 kHz 16 bit mono) Audio data: fffefffffffffffefeffffffffffffffffffffffffffffff...
Video Data
RTMP Header 01.. .... = Format: 1 ..00 0101 = Chunk Stream ID: 5 Timestamp delta: 40 Timestamp: 40 (calculated) Body size: 1383 Type ID: Video Data (0x09)RTMP Body Control: 0x27 (inter-frame H.264) Video data: 010000000000055e0000000106f0023410800000000141e0...
Audio Data
RTMP Header 10.. .... = Format: 2 ..00 0100 = Chunk Stream ID: 4 Timestamp delta: 2 Timestamp: 139 (calculated)RTMP Body Control: 0x86 (G711U 11 kHz 16 bit mono) Audio data: 7f7f7fffffffffffffffffffffffffffffffffffffffffff...
Audio Data
RTMP Header 00.. .... = Format: 0 ..00 0110 = Chunk Stream ID: 6 Timestamp: 52601 Body size: 321 Type ID: Audio Data (0x08) Stream ID: 1RTMP Body Control: 0x86 (G711U 11 kHz 16 bit mono) Audio data: ffffffffffffffffffffffffffffffffffffffffffffffff...
Audio Data
RTMP Header 01.. .... = Format: 1 ..00 0110 = Chunk Stream ID: 6 Timestamp delta: 43 Timestamp: 52644 (calculated) Body size: 321 Type ID: Audio Data (0x08)RTMP Body Control: 0x86 (G711U 11 kHz 16 bit mono) Audio data: ffffffffffffffffffffffffffffffff7f7f7fffff7fffff...
Video Data
RTMP Header 00.. .... = Format: 0 ..00 0111 = Chunk Stream ID: 7 Timestamp: 56128 Body size: 31 Type ID: Video Data (0x09) Stream ID: 1RTMP Body Control: 0x17 (keyframe H.264) Video data: 00000000014d401effe1000b674d401e95a0280f69b01001...
Video Data
RTMP Header 01.. .... = Format: 1 ..00 0111 = Chunk Stream ID: 7 Timestamp delta: 40 Timestamp: 56168 (calculated) Body size: 14478 Type ID: Video Data (0x09)RTMP Body Control: 0x17 (keyframe H.264) Video data: 010000000000388500000001674d401e95a0280f69b01000...
Video Data
RTMP Header 01.. .... = Format: 1 ..00 0111 = Chunk Stream ID: 7 Timestamp delta: 40 Timestamp: 56208 (calculated) Body size: 515 Type ID: Video Data (0x09)RTMP Body Control: 0x27 (inter-frame H.264) Video data: 01000000000001fa0000000106f002180e800000000141e0...
]]>在publish或者play之后就是开始传输媒体数据了,媒体数据分为3种,script脚本数据、video视频数据、audio音频数据。首先需要传输的是脚本数据onMetaData,也称为元数据。onMetaData主要描述音视频的编码格式的相关参数。
如果是发布端向服务器推流,则onMetaData的方向是C->S,如果是播放器向服务器拉流,则onMetaData的方向是S->C。
AMF0 Data onMetaData()
RTMP Header 01.. .... = Format: 1 ..00 0011 = Chunk Stream ID: 3 Timestamp delta: 0 Timestamp: 0 (calculated) Body size: 219 Type ID: AMF0 Data (0x12)RTMP Body String 'onMetaData' ECMA array (10 items) AMF0 type: ECMA array (0x08) Array length: 10 Property 'title' String 'ipc' Property 'width' Number 640 Property 'height' Number 480 Property 'framerate' Number 25 Property 'videocodecid' Number 7 Property 'audiocodecid' Number 8 Property 'audiodatarate' Number 64 Property 'audiosamplerate' Number 8000 Property 'audiosamplesize' Number 16 Property 'stereo' Boolean true End Of Object Marker
AMF0 Data onMetaData()
RTMP Header 00.. .... = Format: 0 ..00 0101 = Chunk Stream ID: 5 Timestamp: 0 Body size: 387 Type ID: AMF0 Data (0x12) Stream ID: 1RTMP Body String 'onMetaData' Object (14 items) AMF0 type: Object (0x03) Property 'Server' String 'NGINX RTMP (github.com/arut/nginx-rtmp-module)' Property 'width' Number 640 Property 'height' Number 480 Property 'displayWidth' Number 640 Property 'displayHeight' Number 480 Property 'duration' Number 0 Property 'framerate' Number 25 Property 'fps' Number 25 Property 'videodatarate' Number 0 Property 'videocodecid' Number 7 Property 'audiodatarate' Number 64 Property 'audiocodecid' Number 8 Property 'profile' String '' Property 'level' String '' End Of Object Marker
]]>连接成功之后由客户端选择publish还是play,这里讲的是play。
1、C->S : createStream
2、S->C : _result
服务端对客户端createStream请求的反馈
3、C->S : getStreamLength、play、Set Buffer Length
4、S->C : Stream Begin 1
5、S->C : onStatus
服务端对客户端play请求的反馈
6、S->C : |RtmpSampleAccess
以下为使用wireshark抓包的部分内容:
AMF0 Command createStream()
Response to this call in frame: 940RTMP Header 01.. .... = Format: 1 ..00 0011 = Chunk Stream ID: 3 Timestamp delta: 0 Timestamp: 0 (calculated) Body size: 25 Type ID: AMF0 Command (0x14)RTMP Body String 'createStream' Number 2 AMF0 type: Number (0x00) Number: 2 Null AMF0 type: Null (0x05)
AMF0 Command _result()
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 29 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String '_result' Number 2 Null Number 1
AMF0 Command getStreamLength()
RTMP Header 00.. .... = Format: 0 ..00 1000 = Chunk Stream ID: 8 Timestamp: 0 Body size: 39 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String 'getStreamLength' Number 3 Null String 'stream01'
AMF0 Command play(‘stream01’)
RTMP Header 00.. .... = Format: 0 ..00 1000 = Chunk Stream ID: 8 Timestamp: 0 Body size: 37 Type ID: AMF0 Command (0x14) Stream ID: 1RTMP Body String 'play' Number 4 Null String 'stream01' Number -2000
User Control Message Set Buffer Length 1,3000ms
RTMP Header 01.. .... = Format: 1 ..00 0010 = Chunk Stream ID: 2 Timestamp delta: 1 Timestamp: 1 (calculated) Body size: 10 Type ID: User Control Message (0x04)RTMP Body Event type: Set Buffer Length (3)
User Control Message Stream Begin 1
RTMP Header 00.. .... = Format: 0 ..00 0010 = Chunk Stream ID: 2 Timestamp: 0 Body size: 6 Type ID: User Control Message (0x04) Stream ID: 0RTMP Body Event type: Stream Begin (0)
AMF0 Command onStatus(‘NetStream.Play.Start’)
RTMP Header 00.. .... = Format: 0 ..00 0101 = Chunk Stream ID: 5 Timestamp: 0 Body size: 96 Type ID: AMF0 Command (0x14) Stream ID: 1RTMP Body String 'onStatus' Number 0 Null Object (3 items) AMF0 type: Object (0x03) Property 'level' String 'status' Property 'code' String 'NetStream.Play.Start' Property 'description' String 'Start live' End Of Object Marker
AMF0 Data |RtmpSampleAccess()
RTMP Header 00.. .... = Format: 0 ..00 0101 = Chunk Stream ID: 5 Timestamp: 0 Body size: 24 Type ID: AMF0 Data (0x12) Stream ID: 1RTMP Body String '|RtmpSampleAccess' Boolean true Boolean true
]]>连接成功之后由客户端选择publish还是play,这里讲的是publish。
1、C->S : releaseStream、FCPublish、createStream
2、S->C : _result
服务端对客户端releaseStream、FCPublish、createStream请求的反馈
3、C->S : publish
客户端向服务端请求publish
4、C->S : Set Chunk Size
客户端向服务端指定Chunk的大小
5、S->C : onStatus
服务端对客户端publish请求的反馈
以下为使用wireshark抓包的部分内容:
AMF0 Command releaseStream(‘stream01?user=aj001&token=tk123456’)
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 63 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String 'releaseStream' Number 0 AMF0 type: Number (0x00) Number: 0 Null AMF0 type: Null (0x05) String 'stream01?user=aj001&token=tk123456'
AMF0 Command FCPublish(‘stream01?user=aj001&token=tk123456’)
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 59 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String 'FCPublish' Number 0 Null String 'stream01?user=aj001&token=tk123456'
AMF0 Command createStream()
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 25 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String 'createStream' Number 2 Null
AMF0 Command _result()
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 29 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String '_result' Number 2 Null Number 1
AMF0 Command publish(‘stream01?user=aj001&token=tk123456’)
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 64 Type ID: AMF0 Command (0x14) Stream ID: 1RTMP Body String 'publish' Number 0 Null String 'stream01?user=aj001&token=tk123456' String 'live'
Set Chunk Size 4000
RTMP Header 00.. .... = Format: 0 ..00 0010 = Chunk Stream ID: 2 Timestamp: 0 Body size: 4 Type ID: Set Chunk Size (0x01) Stream ID: 0RTMP Body Chunk size: 4000
AMF0 Command onStatus(‘NetStream.Publish.Start’)
RTMP Header 00.. .... = Format: 0 ..00 0101 = Chunk Stream ID: 5 Timestamp: 0 Body size: 105 Type ID: AMF0 Command (0x14) Stream ID: 1RTMP Body String 'onStatus' Number 0 Null Object (3 items) AMF0 type: Object (0x03) Property 'level' String 'status' Property 'code' String 'NetStream.Publish.Start' Property 'description' String 'Start publishing' End Of Object Marker
]]>握手之后就是连接(connect),由客户端发起,服务端响应,connect指定的app。
1、C->S : connect
2、S->C : Window Acknowledgement Size
指定Window Acknowledgement Size
3、S->C : Set Peer Bandwidth
码流带宽
3、S->C : Set Chunk Size
指定媒体数据拆分成块时的块大小
4、S->C : _result
connect结果是否成功
5、C->S : Window Acknowledgement Size
对服务端的Window Acknowledgement Size的响应
以下为使用wireshark抓包的部分内容:
AMF0 Command connect(‘live’)
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 220 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String 'connect' Number 1 Object (9 items) AMF0 type: Object (0x03) Property 'app' String 'live' Property 'flashVer' String 'LNX 9,0,124,2' Property 'tcUrl' String 'rtmp://192.168.4.158/live' Property 'fpad' Boolean false Property 'capabilities' Number 15 Property 'audioCodecs' Number 3191 Property 'videoCodecs' Number 252 Property 'videoFunction' Number 1 Property 'objectEncoding' Number 0 End Of Object Marker
Window Acknowledgement Size 5000000
RTMP Header 00.. .... = Format: 0 ..00 0010 = Chunk Stream ID: 2 Timestamp: 0 Body size: 4 Type ID: Window Acknowledgement Size (0x05) Stream ID: 0RTMP Body Window acknowledgement size: 5000000
Set Peer Bandwidth 5000000,Dynamic
RTMP Header 00.. .... = Format: 0 ..00 0010 = Chunk Stream ID: 2 Timestamp: 0 Body size: 5 Type ID: Set Peer Bandwidth (0x06) Stream ID: 0RTMP Body Window acknowledgement size: 5000000 Limit type: Dynamic (2)
Set Chunk Size 4000
RTMP Header 00.. .... = Format: 0 ..00 0010 = Chunk Stream ID: 2 Timestamp: 0 Body size: 4 Type ID: Set Chunk Size (0x01) Stream ID: 0RTMP Body Chunk size: 4000
AMF0 Command _result(‘NetConnection.Connect.Success’)
RTMP Header 00.. .... = Format: 0 ..00 0011 = Chunk Stream ID: 3 Timestamp: 0 Body size: 190 Type ID: AMF0 Command (0x14) Stream ID: 0RTMP Body String '_result' Number 1 Object (2 items) AMF0 type: Object (0x03) Property 'fmsVer' String 'FMS/3,0,1,123' Property 'capabilities' Number 31 End Of Object Marker Object (4 items) AMF0 type: Object (0x03) Property 'level' String 'status' Property 'code' String 'NetConnection.Connect.Success' Property 'description' String 'Connection succeeded.' Property 'objectEncoding' Number 0 End Of Object Marker
Window Acknowledgement Size 5000000
RTMP Header 00.. .... = Format: 0 ..00 0010 = Chunk Stream ID: 2 Timestamp: 0 Body size: 4 Type ID: Window Acknowledgement Size (0x05) Stream ID: 0RTMP Body Window acknowledgement size: 5000000
]]>1) C0 1 byte,表示客户端RTMP的版本号。
2) C1 1536 bytes (4-time + 4-zero + 1528-random)
3) C2 1536 bytes (4-time + 4-time2 + 1528-echo),是对S1的回复
4) S0 1 byte 表示服务端RTMP的版本号,格式同C0
5) S1 1536 bytes (4-time + 4-zero + 1528-random),格式同C1
6) S2 1536 bytes (4-time + 4-zero + 1528-echo),是对C1的回复,格式同C2
以下为使用wireshark抓包的部分内容:
Handshake C0+C1
Protocol version: 03Handshake data: 5a49100000000000e16a3d0c750c0f46f8b4b87b61d8084f...
Handshake S0+S1+S2
Protocol version: 03Handshake data: 5a49100000000000e16a3d0c750c0f46f8b4b87b61d8084f...Handshake data: 5a49100000000000e16a3d0c750c0f46f8b4b87b61d8084f...
Handshake C2
Handshake data: 5a4910005a491000e16a3d0c750c0f46f8b4b87b61d8084f...
1) C0 1 byte,表示客户端RTMP的版本号。
2) C1 1536 bytes (4-time + 4-version + 764-key + 764-digest)
764 bytes key:
764 bytes digest:
3) C2 1536 bytes (4-time + 4-version + 764-key + 764-digest),是对S1的回复
4) S0 1 byte 表示服务端RTMP的版本号,格式同C0
5) S1 1536 bytes (4-time + 4-version + 764-key + 764-digest),格式同C1
6) S2 1536 bytes (4-time + 4-version + 764-key + 764-digest),是对C1的回复,格式同C2
Handshake C0+C1
Protocol version: 03Handshake data: 0000000009007c02f778551eceab8e1e362f07c5868a70b2...
Handshake S0+S1+S2
Protocol version: 03Handshake data: 38a29c830d0e0a0db2cf54ce67b9f0d74acb2a8e231ce5f9...Handshake data: 63c00fbe06cb65b72238eafa4a5c4822b34aa75c413fb365...
Handshake C2
Handshake data: e25c15c22caf72d0986fbd3eda0d7151973e19083e0abbef...
]]>箭头>>>表示C->S,箭头<<<表示S->C
rtmp协议在传输script数据onMetaData时使用AMF(Action Message Format)格式封装。
AMF_TYPE对应的编号:
[AMF_TYPE]+[data length]+[data]
1)字符串
AMF_STRING和AMF_LONG_STRING都是存字符串,AMF_STRING的字符串的长度占2个byte,不能超过65536-1,AMF_LONG_STRING的长度占4个byte。以AMF_STRING为例:
第一个byte是2,接着是2个byte的[data length]表示字符串的长度,接着是字符串的内容。
2)数字
AMF_NUMBER,第一个byte是0,接着是4个byte的数字内容。
3)bool值
AMF_BOOLEAN,第一个byte是1,接着是1个byte的bool值。
4)数组
开头用AMF_ECMA_ARRAY表示,结尾用AMF_OBJECT_END表示,中间写入数组的内容。
AMF_ECMA_ARRAY开头:第一个byte是8,接着是4个byte的[array length]表示数组的成员数。
AMF_OBJECT_END结尾:2个byte的数据全是0,接着一个byte是9。
AMF_ECMA_ARRAY的数组内容里是先插入一个字符串数据的name,然后再插入一个具体类似数据的value。
如下面所示的抓包内容就是:
以下是wireshark抓包的结构:
RTMP Header 01.. .... = Format: 1 ..00 0011 = Chunk Stream ID: 3 Timestamp delta: 0 Timestamp: 0 (calculated) Body size: 219 Type ID: AMF0 Data (0x12)RTMP Body String 'onMetaData' ECMA array (10 items) AMF0 type: ECMA array (0x08) Array length: 10 Property 'title' String 'ipc' Property 'width' Number 640 Property 'height' Number 480 Property 'framerate' Number 25 Property 'videocodecid' Number 7 Property 'audiocodecid' Number 8 Property 'audiodatarate' Number 64 Property 'audiosamplerate' Number 8000 Property 'audiosamplesize' Number 16 Property 'stereo' Boolean true End Of Object Marker
]]>由于一帧音视频数据有时候会很大,比如几十M甚至更大。但是为了方便在网络上传输,需要把数据拆分成一个个较小的块,这里称之为消息块(Chunk)。常见的是每块大小为4000 byte左右。
Chunk的结构如下:
[Chunk Basic Header][Chunk Message Header][Extended TimeStamp] 3个合在一起都是 Chunk Header。
struct rtmp_chunk_header_t{ uint8_t fmt; // RTMP_CHUNK_TYPE_XXX uint32_t cid; // chunk stream id(22-bits) uint32_t timestamp; // delta(24-bits) / extended timestamp(32-bits) uint32_t length; // message length (24-bits) uint8_t type; // message type id uint32_t stream_id; // message stream id};
Header Type + Channel ID 组成,长度在1~3个字节之间
FMT决定[Chunk Message Header]的长度,取值有0~3,对应关系是:
通道id号常用的有:
Chunk Basic Header的长度由[Channel ID]决定,对应关系是
if(cid>=(64+255)) size=3;else if(cid>=64) size=2;else size=1;
Chunk Basic Header
/* 0 1 2 3 4 5 6 7+-+-+-+-+-+-+-+-+|fmt| cs id |+-+-+-+-+-+-+-+-+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|fmt| 0 | cs id - 64 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|fmt| 1 | cs id - 64 |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+*/
计算公式如下:
/*** out : Basic Header Data* fmt : Header Type* id : Channel ID* return : Basic Header Data Length*/int rtmp_chunk_basic_header_write(uint8_t* out, uint8_t fmt, uint32_t id){ if (id >= 64 + 255) { *out++ = (fmt << 6) | 1; *out++ = (uint8_t)((id - 64) & 0xFF); *out++ = (uint8_t)(((id - 64) >> 8) & 0xFF); return 3; } else if (id >= 64) { *out++ = (fmt << 6) | 0; *out++ = (uint8_t)(id - 64); return 2; } else { *out++ = (fmt << 6) | (uint8_t)id; return 1; }}
前面有提到,FMT决定[Chunk Message Header]的长度,取值有0~3,对应关系是:
3 : 0 byte
timestamp是时间戳,3个byte,超过0xFFFFFF时填0xFFFFFF,然后使用Extended TimeStamp
补充的时间戳,在Chunk Message Header的时间戳3个byte不够时才使用,4byte
块数据,比如一帧数据长度为8080byte,以4000 byte拆分,第一个Chunk的Chunk Data就是前4000byte,第二个Chunk的Chunk Data就是第4001byte~8000byte,第三个Chunk的Chunk Data就是剩下的80byte。
]]>打开如下网址:
https://dev.mysql.com/downloads/mysql/5.5.html#downloads
根据需要筛选版本。这里版本选5.5.60,操作系统选Linux-Generic,系统版本选32-bit。然后点击下载。或者可以直接使用下面的链接下载:
https://cdn.mysql.com//Downloads/MySQL-5.5/mysql-5.5.60-linux-glibc2.12-i686.tar.gz
使用whereis mysql查找是否已安装了mysql,如果有且希望重新安装,先卸载或删除原有的mysql
安装教程可以参考mysq官网教程:
https://dev.mysql.com/doc/refman/5.5/en/binary-installation.html
1、把下载的tar.gz压缩包放在/usr/local/目录中:
//下载cd /usr/local/wget https://cdn.mysql.com//Downloads/MySQL-5.5/mysql-5.5.60-linux-glibc2.12-i686.tar.gz//解压tar zxvf /usr/local/mysql-5.5.60-linux-glibc2.12-i686.tar.gz//创建软链接ln -s /usr/local/mysql-5.5.60-linux-glibc2.12-i686 mysql
2、检查是否有mysql用户组和mysql用户
groups mysql
如果如下提示,则是已有。
mysql : mysql
如果如下提示,则是还没有,需要添加。
groups: mysql: no such user
执行如下添加mysql用户组和mysql用户
groupadd mysqluseradd -r -g mysql -s /bin/false mysql
3、更改mysql目录权限,执行安装脚本
cd mysql/chown -R mysql .chgrp -R mysql ../scripts/mysql_install_db --user=mysqlchown -R root .chown -R mysql data
4、启动mysql后台服务
./support-files/mysql.server start
1、登陆mysql
-u后面是用户名,-p后面是密码
./bin/mysql -h 127.0.0.1 -uroot -p123456
2、更改密码
-u后面是用户名,-p后面是原密码,password后面是新密码
./bin/mysqladmin -u root -p123456 password 888888
3、如果是部署在云服务器上(如阿里云),还需要允许远程登陆
1)在mysql命令行中输入如下,root是用户名,123456是密码
grant all privileges on *.* to root@'%' identified by '123456';flush privileges;
2)在阿里云控制台设置防火墙端口,允许3306的TCP
可以把/usr/local/mysql/bin目录添加到环境变量/etc/profile中
可以把mysql启动脚本添加到系统启动脚本中,这样就可以开机自启动了
]]>apt-get install nodejsapt-get install npmnpm install -g hexo-cli
1、安装git
apt-get install git
2、注册github和coding账户,并新建项目仓库
//这里linux登录的账号是root用于cd /root/.ssh/ssh-keygen -t rsa -C "your e-mail"
然后在如下提示的地方输入密码,这里的密码是以后部署项目时需要输入的密码。
Enter passphraseEnter same passphrase
cat如下文件并复制文件内容
cat /root/.ssh/id_rsa.pub
把复制的内容粘贴到github新建的ssh keys中
把复制的内容粘贴到coding新建的ssh公钥中
测试github,输入如下命令
ssh -T git@github.com
测试coding,输入如下命令
ssh -T git@git.coding.net
如果成功会收到成功的响应数据。
]]>apt-get install subversion
//创建工程目录mkdir /home/svn_projectcd /home/svn_project//创建版本库目录mkdir code1//创建版本库svnadmin create ./code1//更改目录权限chmod 777 -R /home/svn_project/
配置文件在conf目录中,需要修改的文件有:
1)authz
#定义不同用户组的用户[groups]admin = rootuser = hello#用户组的权限[/]@admin = rw@user = r
2)passwd
#用户密码[users]root = 123456hello = 123456
3)svnserve.conf
[general]anon-access = noneauth-access = writepassword-db = passwdauthz-db = authz
svnserve -d -r /home/svn_project/
svn的默认端口是3690,如果是部署在云服务器上,还需要登陆云服务账号在控制台修改端口防火墙,允许3690端口才行。
svnadmin dump svn1 > back_path/svn1
svnadmin load new_svn1 < back_path/svn1
]]>openssl genrsa -des3 -out https.key 1024
会提示输入密码,根据提示设置密码即可
Enter pass phrase for https.key:
Verifying - Enter pass phrase for https.key:
openssl req -new -key https.key -out https.csr
如果提示输入密码,就输入生成key时输入的密码。
//国家Country Name (2 letter code) [AU]:CN//省State or Province Name (full name) [Some-State]:GD//市Locality Name (eg, city) []:SZ//组织名称Organization Name (eg, company) [Internet Widgits Pty Ltd]:IPC//可不写Organizational Unit Name (eg, section) []:IPC//一般写网站域名Common Name (e.g. server FQDN or YOUR name) []:IPC//邮箱Email Address []:IPCPlease enter the following 'extra' attributesto be sent with your certificate request//可不写A challenge password []://可不写An optional company name []:
openssl x509 -req -days 3650 -in https.csr -signkey https.key -out https.crt
]]>curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
nvm ls-remote
nvm install v8.11.3
node -vnpm -v
nvm use v8.11.3
nvm alias default v8.11.3
]]>虽然在C程序中可以直接创建子进程调用shell命令的ping并等待分析返回的打印信息,但是创建进程开销比较大,不适合频繁调用。于是决定使用socket实现ping的功能。
ping是发送ICMP数据包也接收响应数据,可以参考busybox中的ping.c的源码。
int sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);if(sockfd!=0){ perror("RAW socket created error"); return -1;}//不阻塞int flags = fcntl(sockfd, F_GETFL, 0);fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
//sendbuf malloc合适的大小//datalen=56;//pid_t pid = getpid();/*发送ping消息*/ int send_ping(char *sendbuf,int datalen,int sockfd,struct sockaddr_in *dest,pid_t pid) { int len=0; struct icmp *icmp; icmp=(struct icmp*)sendbuf; icmp->icmp_type=ICMP_ECHO; //设置类型为ICMP请求报文 icmp->icmp_code=0; icmp->icmp_cksum=0; icmp->icmp_seq=0; //序号 icmp->icmp_id=pid; //设置当前进程ID为ICMP标示符 len=ICMP_HEADSIZE+datalen; memset(icmp->icmp_data,0xff,datalen); gettimeofday((struct timeval *)icmp->icmp_data,NULL); /* 获取当前时间*/ icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,len); if(sendto(sockfd,sendbuf,len,0,(struct sockaddr *)dest,sizeof (struct sockaddr))<0) /*经socket传送数据*/ { printf("error : ping sendto error\n"); return -1; } return 0;}
//recvbuf malloc合适的大小//timeout_ms 接收超时时间,单位毫米//GetCurrentTimeStamp 是封装好的一个获取当前时间毫秒的函数,可以使用gettimeofday()函数实现/*接收程序发出的ping命令的应答*/ //return 0则表示收到响应数据,ping通了 int recv_reply(char *recvbuf,int sockfd,struct sockaddr_in *from,struct timeval *recvtime,pid_t pid, int timeout_ms) { int n=0; int len=0; //当前时间, 单位ms unsigned int tNowTime = GetCurrentTimeStamp(); unsigned int tEndTime = tNowTime + timeout_ms; int nrecv = 1; // len = sizeof(struct sockaddr_in); /*发送ping应答消息的主机IP*/ while(nrecv>0) { tNowTime = GetCurrentTimeStamp(); if(tNowTime>tEndTime){ __ERR("ping time out\n"); return -1; } /*经socket接收数据,如果正确接收返回接收到的字节数,失败返回0.*/ if((n=recvfrom(sockfd, recvbuf, PING_BUF_SIZE, 0, (struct sockaddr *)from, &len))<=0) { continue; } gettimeofday(recvtime, NULL); /*记录收到应答的时间*/ if(handle_pkt(recvbuf,n,from,recvtime,pid)<0) /*接收到错误的ICMP应答信息*/ { continue; } nrecv--; break; } return 0;} /*ICMP应答消息处理*/ int handle_pkt(char *recvbuf,int datalen,struct sockaddr_in *from,struct timeval *recvtime,pid_t pid) { struct ip *ip=(struct ip *)recvbuf; int iphdrlen=ip->ip_hl<<2; //求ip报头长度,即ip报头的长度标志乘4 struct icmp *icmp=(struct icmp *)(recvbuf+iphdrlen); //越过ip报头,指向ICMP报头 datalen-=iphdrlen; //ICMP报头及ICMP数据报的总长度 if( datalen<8) return -1; if((icmp->icmp_type!=ICMP_ECHOREPLY) || (icmp->icmp_id!=pid)) return -1; printf("recv icmp reply from %s,icmp_id=%d,icmp_seq=%d\n",inet_ntoa(from->sin_addr),icmp->icmp_id,icmp->icmp_seq); return 0;}
]]>ip字符串转struct sockaddr_in
struct sockaddr_in dest_addr;dest_addr.sin_addr.s_addr = inet_addr("114.114.114.114");
struct sockaddr_in转ip字符串
printf("ip = %s\n",inet_ntoa(sock.sin_addr));
struct sockaddr { sa_family_t sin_family; //地址族 char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息 };
struct sockaddr_in { sa_family_t sin_family;//地址族 uint16_t sin_port;//16位端口 struct in_addr sin_addr;//32位IP地址 char sin_zero[8];//不使用 };struct in_addr { In_addr_t s_addr; //32位IPv4地址};
struct sockaddr s_sockaddr;struct sockaddr_in s_sockaddr_in;struct sockaddr_in d_sockaddr_in = *(struct sockaddr_in *)s_sockaddr;struct sockaddr d_sockaddr = *(struct sockaddr *)s_sockaddr_in;
uint32_t htonl(uint32_t hostlong);//32位uint16_t htons(uint16_t hostshort);//16位
uint32_t ntohl(uint32_t netlong);//32位uint16_t ntohs(uint16_t netshort);//16位
]]>//设置阻塞int flags = fcntl(sock, F_GETFL, 0);fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);//设置接收超时时间int timeout_ms=500;//毫米struct timeval timeout; timeout.tv_sec = timeout_ms/1000;//秒 timeout.tv_usec = (timeout_ms%1000)*1000;//微秒 if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) { perror("setsockopt SO_RCVTIMEO failed:"); printf("setsockopt SO_RCVTIMEO failed,timeout_ms=%d\n",timeout_ms); return -1;}//把SO_RCVTIMEO换成SO_SNDTIMEO就是发送超时
2、设置不阻塞接收
int flags = fcntl(sockfd, F_GETFL, 0);fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
]]>struct hostent *host;if((host = gethostbyname(addr_url)) == NULL) { printf("invalid addr_url(%s)\n", addr_url); return -1; }
struct sockaddr_in dest;dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];
printf("ip = %s\n",inet_ntoa(dest.sin_addr));
]]>