低时延迟流媒体之HTTP-FLV协议
@TOC
</font>
<hr style=” border:solid; width:100px; height:1px;” color=#000000 size=1”>
前言
http-flv 其实网络中发送flv文件, 也是网络格式
一、FLV简介
Flash Video(简称FLV),是一种网络视频格式,也是一种流媒体格式。
FLV文件由 FLV File Header + FLV File Body组成。
FLV File Header固定为9个字节,用来描述版本号,有没有音视频
结构如下:
名称
比特数
描述
Signature
24
签名,固定为“FLV”
Version
8
版本号,固定为0x01,表示FLV Version 1
TypeFlagsReserved
5
全0
TypeFlagsAudio
1
1表示有audio tag,0表示没有audio tag
TypeFlagsReserved
1
全0
TypeFlagsVideo
1
1表示有video tag,0表示没有video tag
DataOffset
32
FLV header的大小,单位是字节
struct FLVHeader {
//FLV
char flv [ 3 ];
//File version (for example, 0x01 for FLV version 1)
uint8_t version ;
// 保留,置0
uint8_t : 5 ;
// 是否有音频
uint8_t have_audio : 1 ;
// 保留,置0
uint8_t : 1 ;
// 是否有视频
uint8_t have_video : 1 ;
固定为9
uint32_t length ;
// 固定为0
uint32_t previous_tag_size0 ;
};
三、FLV File Body
FLV File Body由 PreviousTagSize0+tag1+PreviousTagSize1+…+tagN+PreviousTagSizeN组成。
PreviousTagSize 是一个4字节的整数,表示前一个TAG的大小。
PreviousTagSize0
tag1
PreviousTagSize1
PreviousTagSize1
tag2
PreviousTagSize2
…
tagN
PreviousTagSizeN
//tag header
std :: string tag_header ;
tag_header . append (( char * ) & header , sizeof ( header ));
Writer (( const uint8_t * ) tag_header . c_str (), tag_header . size ()); ///
//tag data
Writer ( data , size );
//PreviousTagSize
uint32_t PreviousTag_Size = htonl (( uint32_t )( size + sizeof ( header )));
std :: string PreviousTagSize ;
PreviousTagSize . append (( char * ) & PreviousTag_Size , 4 );
Writer (( const uint8_t * ) PreviousTagSize . c_str (), PreviousTagSize . size (), true );
四、Flv Tag 存储音频和视频数据的地方
FLV tag由 tag header + tag body组成。
tag header固定为11个字节,结构如下:
名称
比特数
描述
TagType
8
tag类型 8:audio 9:video 18:script data 其他:保留
DataSize
24
message 长度,从StreamID后面到tag结束
Timestamp
24
相对于第一个tag的时间戳(单位是 毫秒),第一个tag的Timestamp总为0
TimestampExtended
8
时间戳的扩展字段,当 Timestamp 3个字节不够时,会启用这个字段,代表高8位
StreamID
24
总是0
struct FlvTagHeader {
uint8_t type = 0 ;
uint8_t data_size [ 3 ] = { 0 };
uint8_t timestamp [ 3 ] = { 0 };
uint8_t timestamp_ex = 0 ;
uint8_t streamid [ 3 ] = { 0 }; /* Always 0. */
};
五、FLV Tag Body
根据 tag type的值,tag body可以分为AUDIODATA(tag type为8),VIDEODATA(tag type为9),SCRIPTDATAOBJECT(tag type为18)
1、AUDIODATA
AUDIODATA 承载音频数据,第一个字节描述音频的信息,第二个字节开始为音频数据。
① AUDIODATA的结构如下:
名称
比特数
描述
SoundFormat
4
音频编码格式
SoundRate
2
采样率
SoundSize
1
采样精度,0表示8-bit,1表示16-bit
SoundType
1
声道类型,0表示单声道,1表示立体声
SoundData
N*8
音频数据
② 音频编码格式:
ID值
音频编码
0
Linear PCM, platform endian
1
ADPCM
2
MP3
3
Linear PCM, little endian
4
Nellymoser 16 kHz mono
5
Nellymoser 8 kHz mono
6
Nellymoser
7
G.711 A-law logarithmic PCM
8
G.711 mu-law logarithmic PCM
9
reserved
10
AAC
11
Speex
13
Opus
14
MP3 8 kHz
15
Device-specific sound
AudioSpecificConfig的结构:
名称
比特数
描述
AudioObjectType
5
音频对象类型
SamplingFrequencyIndex
4
采样率索引值,比如4表示44100
ChannelConfiguration
4
声道配置
AudioObjectType的取值:
值
ObjectType
1
AAC Main
2
AAC LC
3
AAC SSR
5
AAC HE
29
AAC HEV2
SamplingFrequency的取值:
sampling frequency index
frequency
0x0
96000
0x1
88200
0x2
64000
0x3
48000
0x4
44100
0x5
32000
0x6
24000
0x7
22050
0x8
16000
0x9
12000
0xa
11025
0xb
8000
0xc
7350
0xd
reserved
0xe
reserved
0xf
escape value
SamplingFrequencyIndex是SamplingFrequency数组的一个索引。
当SoundFormat=2,即MP3音频时,SoundData就是MP3 RAW数据
具体代码实现
/*
|名称 |比特数| 描述|
|:---:|:---:|:---:|
|SoundFormat |4 |音频编码格式|
|SoundRate |2 |采样率|
|SoundSize |1| 采样精度,0表示8-bit,1表示16-bit|
|SoundType |1 |声道类型,0表示单声道,1表示立体声|
|SoundData| N*8 |音频数据|
*/
{
uint8_t * buffer = out_buffer_ ;
buffer [ 0 ] = ( 10 << 4 /* <<4 */ ) /* SoundFormat */ | ( 3 << 2 ) /* 44k-SoundRate */ | ( 1 << 1 ) /* 16-bit samples */ | 1 /* Stereo sound */ ;
buffer [ 1 ] = 1 ; // AACPacketType
memcpy ( buffer + 2 , frame . data (), frame . size ());
WriteFlvTag ( libflv :: kFlvMsgTypeAudio , out_buffer_ ,
frame . size () + 2 , timestamp - start_timestamp_ );
}
2、VideoData
VIDEODATA Tag第一个字节的高4位描述视频帧的类型,低4位描述视频编码器ID,VIDEODATA Tag的结构如下:
名称
比特数
描述
FrameType
4
帧类型
CodecID
4
视频编码ID
VideoData
N*8
视频数据
uint8_t * ptr = buffer ;
* ptr = FLV_CODECID_H264 ;
* ptr ++ |= FLV_FRAME_KEY ;
FrameType:视频帧的类型。一般keyframe是指IDR帧,而inter frame是指普通I帧。
类型值
视频帧
1
key frame (for AVC, a seekable frame)
2
inter frame (for AVC, a non-seekable frame)
3
disposable inter frame (H.263 only)
4
generated key frame (reserved for server use only)
5
video info/command frame
视频编码ID:
ID值
视频编码
2
Sorenson H.263
3
Screen video
4
On2 VP6
5
On2 VP6 with alpha channel
6
Screen video version 2
7
AVC
当CodecID 为7时,即为AVC视频,第一个字节为AvcPacketType,第二三四个字节为CompositionTime。当AvcPacketType=0,第5个字节开始为AVCDecoderConfigurationRecord;否则VideoData为Avc Raw数据。
AVCDecoderConfigurationRecord的结构:
|名称| 比特数 |描述|
|:---:|:---:|:---:|
|configurationVersion |8 |版本号,总是1|
|AVCProfileIndication| 8 |sps[1]|
|profile_compatibility |8 |sps[2]|
|AVCLevelIndication| 8 |sps[3]|
- configurationVersion,AVCProfileIndication,profile_compatibility,AVCLevelIndication:都是一个字节,具体的内容由解码器去理解。
- lengthSizeMinusOne:unit_length长度所占的字节数减1,也即lengthSizeMinusOne的值+1才是unit_length所占用的字节数。
- numOfSequenceParameterSets:sps的个数
- sequenceParameterSetLength:sps内容的长度
- sequenceParameterSetNALUnit:sps的内容
- numOfPictureParameterSets:pps的个数
- pictureParameterSetLength:pps内容的长度
- pictureParameterSetNALUnit:pps的内容

avcc 格式 |size|nal|
sps
```
01 42 c0 1f ff e0 00 18 67 42 c0 1f 00 00 00 0...
```
01 版本
42 profile
c0
1f level 级别
18就是avcc格式的sps的数据的大小
```javascript
std::string extra_data;
{
/*
configurationVersion 8 版本号,总是1
AVCProfileIndication 8 sps[1]
profile_compatibility 8 sps[2]
AVCLevelIndication 8 sps[3]
configurationVersion,AVCProfileIndication,profile_compatibility,AVCLevelIndication:都是一个字节,具体的内容由解码器去理解。
lengthSizeMinusOne:unit_length长度所占的字节数减1,也即lengthSizeMinusOne的值+1才是unit_length所占用的字节数。
numOfSequenceParameterSets:sps的个数
sequenceParameterSetLength:sps内容的长度
sequenceParameterSetNALUnit:sps的内容
numOfPictureParameterSets:pps的个数
pictureParameterSetLength:pps内容的长度
pictureParameterSetNALUnit:pps的内容
*/
// AVCDecoderConfigurationRecord start
extra_data.push_back(1); // version
// *ptr++ = 1;
extra_data.push_back(sps_[1]); // profile
//*ptr++ = sps_[1];
extra_data.push_back(sps_[2]); // compat
//*ptr++ = sps_[2];
extra_data.push_back(sps_[3]); // level
//*ptr++ = sps_[3];
extra_data.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
//*ptr++ = 0xff;
extra_data.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001)
//*ptr++ = 0xe1;
// sps
uint16_t size = (uint16_t)sps_.size();
size = htons(size);
extra_data.append((char *)&size, 2);
extra_data.append(sps_);
// pps
extra_data.push_back(1); // version
size = (uint16_t)pps_.size();
size = htons(size);
extra_data.append((char *)&size, 2);
extra_data.append(pps_);
}
// memcpy()
//packet.append(extra_data);
memcpy(ptr, extra_data.c_str(), extra_data.size());
ptr += extra_data.size();
WriteFlvTag(libflv::kFlvMsgTypeVideo, buffer, ptr - buffer, 0);
```
当VideoData为AVC RAW时,AVC RAW的结构是avcc的
#### 3、 Script OnMeta (实际解码拿sps和pps中信息解析解码的, 所以该字段没有啥作用)
Script Data Tags通常用来存放跟FLV中音视频相关的元数据信息(onMetaData),比如时长、长度、宽度等。它的定义相对复杂些,采用AMF(Action Message Format)封装了一系列数据类型,比如字符串、数值、数组等。
onMetaData中包含了音视频相关的元数据,封装在Script Data Tag中,它包含了两个AMF。
第一个AMF是固定的值:
```
0x02 0x000A 0x6F 0x6E 0x4D 0x65 0x74 0x61 0x44 0x61 0x74 0x61
```
- 第1个字节:0x02,表示字符串类型
- 第2-3个字节:UI16类型,值为0x000A,表示字符串的长度为10(onMetaData的长度);
- 第4-13个字节:字符串onMetaData对应的16进制数字(0x6F 0x6E 0x4D 0x65 0x74 0x61 0x44 0x61 0x74 0x61);
第二个AMF则是键值对描述的流媒体属性,每个实现这些属性都可能不一样:
|字段| 字段类型 |字段含义|
|:---:|:---|:---:|
|duration |DOUBLE| 文件的时长|
|width| DOUBLE| 视频宽度(px)|
|height |DOUBLE| 视频高度(px)|
|videodatarate| DOUBLE |视频比特率(kb/s)|
|framerate |DOUBLE |视频帧率(帧/s)|
|videocodecid |DOUBLE| 视频编解码器ID(参考Video Tag)|
|audiosamplerate |DOUBLE| 音频采样率|
|audiosamplesize| DOUBLE| 音频采样精度(参考Audio Tag)|
|stereo |BOOL| 是否立体声|
|audiocodecid |DOUBLE |音频编解码器ID(参考Audio Tag)|
|filesize |DOUBLE |文件总得大小(字节)|
```javascript
//prev_packet_size_ = index;
uint8_t * metadata = current_ ;
uint8_t * ptr = metadata;
uint8_t *end = ptr + (1024 * 1023);
uint8_t count = (has_auido ? 5 : 0) + (has_video ? 7 : 0) + 1;
ptr = AMFWriteString(ptr, end, "onMetaData", 10);
// value: SCRIPTDATAECMAARRAY
ptr[0] = AMF_ECMA_ARRAY;
ptr[1] = (uint8_t)((count >> 24) & 0xFF);;
ptr[2] = (uint8_t)((count >> 16) & 0xFF);;
ptr[3] = (uint8_t)((count >> 8) & 0xFF);
ptr[4] = (uint8_t)(count & 0xFF);
ptr += 5;
if (has_auido)
{
ptr = AMFWriteNamedDouble(ptr, end, "audiocodecid", 12, 10);
ptr = AMFWriteNamedDouble(ptr, end, "audiodatarate", 13, 125 /* / 1024.0*/);
ptr = AMFWriteNamedDouble(ptr, end, "audiosamplerate", 15, 44100);
ptr = AMFWriteNamedDouble(ptr, end, "audiosamplesize", 15, 16);
ptr = AMFWriteNamedBoolean(ptr, end, "stereo", 6, (uint8_t)true);
}
if (has_video)
{
ptr = AMFWriteNamedDouble(ptr, end, "duration", 8, 0 );
//ptr = AMFWriteNamedDouble(ptr, end, "interval", 8, metadata->interval);
ptr = AMFWriteNamedDouble(ptr, end, "videocodecid", 12, 7);
ptr = AMFWriteNamedDouble(ptr, end, "videodatarate", 13, 0 /* / 1024.0*/);
ptr = AMFWriteNamedDouble(ptr, end, "framerate", 9, 25);
ptr = AMFWriteNamedDouble(ptr, end, "height", 6, 2560);
ptr = AMFWriteNamedDouble(ptr, end, "width", 5, 1440);
}
ptr = AMFWriteNamedString(ptr, end, "encoder", 7, kflv_muxer, strlen(kflv_muxer));
ptr = AMFWriteObjectEnd(ptr, end);
WriteFlvTag(libflv::kFlvMsgTypeAMFMeta, metadata, ptr - metadata, 0);
```
demo测试效果

http-flv拉流解码效果图

# 总结
[libflv 源码地址:https://github.com/chensongpoixs/libmedia_transfer_protocol/tree/master/libflv](https://github.com/chensongpoixs/libmedia_transfer_protocol/tree/master/libflv)