SRS 代码分析【FLV文件解析】
FLV文件的结构
SRS对FLV文件的解析实现
1.FLV头部解析
header部分记录了flv的类型、版本等信息,是flv的开头,一般都差不多,占9bytes。具体格式如下:
文件类型 | 3 bytes | “FLV” |
版本 | 1 byte | 一般为0x01 |
流信息 | 1 byte | 倒数第一位是1表示有视频,倒数第三位是1表示有音频,倒数第二、四位必须为0 |
header长度 | 4 bytes | 整个header的长度,一般为9;大于9表示下面还有扩展信息 |
int SrsFlvDecoder::read_header(char header[9])
{
int ret = ERROR_SUCCESS;
srs_assert(header);
// TODO: FIXME: Should use readfully.
if ((ret = reader->read(header, 9, NULL)) != ERROR_SUCCESS) {
return ret;
}
char* h = header;
if (h[0] != 'F' || h[1] != 'L' || h[2] != 'V') {
ret = ERROR_KERNEL_FLV_HEADER;
srs_warn("flv header must start with FLV. ret=%d", ret);
return ret;
}
return ret;
}
2.FLV tag_header解析
Tag类型 | 1 bytes | 8:音频 9:视频 18:脚本 其他:保留 |
数据区长度 | 3 bytes | 在数据区的长度 |
时间戳 | 3 bytes | 整数,单位是毫秒。对于脚本型的tag总是0 |
时间戳扩展 | 1 bytes | 将时间戳扩展为4bytes,代表高8位。很少用到 |
StreamsID | 3 bytes | 总是0 |
int SrsFlvDecoder::read_tag_header(char* ptype, int32_t* pdata_size, uint32_t* ptime)
{
int ret = ERROR_SUCCESS;
srs_assert(ptype);
srs_assert(pdata_size);
srs_assert(ptime);
char th[11]; // tag header
// read tag header
// TODO: FIXME: Should use readfully.
if ((ret = reader->read(th, 11, NULL)) != ERROR_SUCCESS) {
if (ret != ERROR_SYSTEM_FILE_EOF) {
srs_error("read flv tag header failed. ret=%d", ret);
}
return ret;
}
// Reserved UB [2]
// Filter UB [1]
// TagType UB [5]
*ptype = (th[0] & 0x1F);
// DataSize UI24
char* pp = (char*)pdata_size;
pp[3] = 0;
pp[2] = th[1];
pp[1] = th[2];
pp[0] = th[3];
// Timestamp UI24
pp = (char*)ptime;
pp[2] = th[4];
pp[1] = th[5];
pp[0] = th[6];
// TimestampExtended UI8
pp[3] = th[7];
return ret;
}
3.解析Tag data
Tag data的大小在read_tag_header中已经读取出来
int SrsFlvDecoder::read_tag_data(char* data, int32_t size)
{
int ret = ERROR_SUCCESS;
srs_assert(data);
// TODO: FIXME: Should use readfully.
if ((ret = reader->read(data, size, NULL)) != ERROR_SUCCESS) {
if (ret != ERROR_SYSTEM_FILE_EOF) {
srs_error("read flv tag header failed. ret=%d", ret);
}
return ret;
}
return ret;
}
4.读取Previous tag size
在Tag与Tag之间有4个字节的Previous tag size 记录前一个tag的大小
int SrsFlvDecoder::read_previous_tag_size(char previous_tag_size[4])
{
int ret = ERROR_SUCCESS;
srs_assert(previous_tag_size);
// ignore 4bytes tag size.
// TODO: FIXME: Should use readfully.
if ((ret = reader->read(previous_tag_size, 4, NULL)) != ERROR_SUCCESS) {
if (ret != ERROR_SYSTEM_FILE_EOF) {
srs_error("read flv previous tag size failed. ret=%d", ret);
}
return ret;
}
return ret;
}
SRS写入数据到FLV文件的实现
1.写入FLV header,写入头部后会接着写入4个字节的Previous tag size 值为0
int SrsFlvTransmuxer::write_header()
{
int ret = ERROR_SUCCESS;
// 9bytes header and 4bytes first previous-tag-size
char flv_header[] = {
'F', 'L', 'V', // Signatures "FLV"
(char)0x01, // File version (for example, 0x01 for FLV version 1)
(char)0x05, // 4, audio; 1, video; 5 audio+video.
(char)0x00, (char)0x00, (char)0x00, (char)0x09 // DataOffset UI32 The length of this header in bytes
};
// flv specification should set the audio and video flag,
// actually in practise, application generally ignore this flag,
// so we generally set the audio/video to 0.
// write 9bytes header.
if ((ret = write_header(flv_header)) != ERROR_SUCCESS) {
return ret;
}
return ret;
}
int SrsFlvTransmuxer::write_header(char flv_header[9])
{
int ret = ERROR_SUCCESS;
// write data.
if ((ret = writer->write(flv_header, 9, NULL)) != ERROR_SUCCESS) {
srs_error("write flv header failed. ret=%d", ret);
return ret;
}
// previous tag size.
char pts[] = { (char)0x00, (char)0x00, (char)0x00, (char)0x00 };
if ((ret = writer->write(pts, 4, NULL)) != ERROR_SUCCESS) {
return ret;
}
return ret;
}
2.写入MetaData tag到FLV中
脚本Tag一般只有一个,是flv的第一个Tag,用于存放flv的信息,比如duration、audiodatarate、creator、width等。
首先介绍下脚本的数据类型。所有数据都是以数据类型+(数据长度)+数据的格式出现的,数据类型占1byte,数据长度看数据类型是否存在,后面才是数据。
其中数据类型的种类有:
- 0 = Number type
- 1 = Boolean type
- 2 = String type
- 3 = Object type
- 4 = MovieClip type
- 5 = Null type
- 6 = Undefined type
- 7 = Reference type
- 8 = ECMA array type
- 10 = Strict array type
- 11 = Date type
- 12 = Long string type
如果类型为String,后面的2bytes为字符串的长度(Long String是4bytes),再后面才是字符串数据;如果是Number类型,后面的8bytes为Double类型的数据;Boolean类型,后面1byte为Bool类型。
知道了这些后再来看看flv中的脚本,一般开头是0x02,表示String类型,后面的2bytes为字符串长度,一般是0x000a(“onMetaData”的长度),再后面就是字符串“onMetaData”。
首先调用write_metadata_to_cache将MetaData的Tag Header写入cache中,然后调用write_tag将cache中tag header 与MetaData一同写入
int SrsFlvTransmuxer::write_metadata(char type, char* data, int size)
{
int ret = ERROR_SUCCESS;
srs_assert(data);
if ((ret = write_metadata_to_cache(type, data, size, tag_header)) != ERROR_SUCCESS) {
return ret;
}
if ((ret = write_tag(tag_header, sizeof(tag_header), data, size)) != ERROR_SUCCESS) {
if (!srs_is_client_gracefully_close(ret)) {
srs_error("write flv data tag failed. ret=%d", ret);
}
return ret;
}
return ret;
}
metadata tag header写入cache
int SrsFlvTransmuxer::write_metadata_to_cache(char type, char* data, int size, char* cache)
{
int ret = ERROR_SUCCESS;
srs_assert(data);
// 11 bytes tag header
/*char tag_header[] = {
(char)type, // TagType UB [5], 18 = script data
(char)0x00, (char)0x00, (char)0x00, // DataSize UI24 Length of the message.
(char)0x00, (char)0x00, (char)0x00, // Timestamp UI24 Time in milliseconds at which the data in this tag applies.
(char)0x00, // TimestampExtended UI8
(char)0x00, (char)0x00, (char)0x00, // StreamID UI24 Always 0.
};*/
// write data size.
if ((ret = tag_stream->initialize(cache, 11)) != ERROR_SUCCESS) {
return ret;
}
tag_stream->write_1bytes(type);
tag_stream->write_3bytes(size);
tag_stream->write_3bytes(0x00);
tag_stream->write_1bytes(0x00);
tag_stream->write_3bytes(0x00);
return ret;
}
3. 写入Audio Tag数据
音频信息第一个byte格式如下。
音频格式 | 4 bits | 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 14 = MP3 8-Khz 15 = Device-specific sound |
采样率 | 2 bits | 0 = 5.5-kHz 1 = 11-kHz 2 = 22-kHz 3 = 44-kHz 对于AAC总是3 |
采样的长度 | 1 bit | 0 = snd8Bit 1 = snd16Bit 压缩过的音频都是16bit |
音频类型 | 1 bit | 0 = sndMono 1 = sndStereo 对于AAC总是1 |
首先调用write_audio_to_cache将Audio的Tag Header写入到cache中,然后调用write_tag将写入cache的header与AudioData一同写入
int SrsFlvTransmuxer::write_audio(int64_t timestamp, char* data, int size)
{
int ret = ERROR_SUCCESS;
srs_assert(data);
if ((ret = write_audio_to_cache(timestamp, data, size, tag_header)) != ERROR_SUCCESS) {
return ret;
}
if ((ret = write_tag(tag_header, sizeof(tag_header), data, size)) != ERROR_SUCCESS) {
if (!srs_is_client_gracefully_close(ret)) {
srs_error("write flv audio tag failed. ret=%d", ret);
}
return ret;
}
return ret;
}
Audio tag header 写入cache
int SrsFlvTransmuxer::write_audio_to_cache(int64_t timestamp, char* data, int size, char* cache)
{
int ret = ERROR_SUCCESS;
srs_assert(data);
timestamp &= 0x7fffffff;
// 11bytes tag header
/*char tag_header[] = {
(char)SrsFrameTypeAudio, // TagType UB [5], 8 = audio
(char)0x00, (char)0x00, (char)0x00, // DataSize UI24 Length of the message.
(char)0x00, (char)0x00, (char)0x00, // Timestamp UI24 Time in milliseconds at which the data in this tag applies.
(char)0x00, // TimestampExtended UI8
(char)0x00, (char)0x00, (char)0x00, // StreamID UI24 Always 0.
};*/
// write data size.
if ((ret = tag_stream->initialize(cache, 11)) != ERROR_SUCCESS) {
return ret;
}
tag_stream->write_1bytes(SrsFrameTypeAudio);
tag_stream->write_3bytes(size);
tag_stream->write_3bytes((int32_t)timestamp);
// default to little-endian
tag_stream->write_1bytes((timestamp >> 24) & 0xFF);
tag_stream->write_3bytes(0x00);
return ret;
}
视频信息第一个byte,格式说明如下:
名称 | 长度 | 介绍 |
---|---|---|
帧类型 | 4 bits | 1: keyframe (for AVC, a seekable frame) 2: inter frame (for AVC, a non-seekable frame) 3: disposable inter frame (H.263 only) 4: generated keyframe (reserved for server use only) 5: video info/command frame |
编码ID | 4 bits | 1: JPEG (currently unused) 2: Sorenson H.263 3: Screen video 4: On2 VP6 5: On2 VP6 with alpha channel 6: Screen video version 2 |
首先调用write_video_to_cache将header写入到cache,然后调用write_tag将tag Header与VideoData的数据写入
int SrsFlvTransmuxer::write_video(int64_t timestamp, char* data, int size)
{
int ret = ERROR_SUCCESS;
srs_assert(data);
if ((ret = write_video_to_cache(timestamp, data, size, tag_header)) != ERROR_SUCCESS) {
return ret;
}
if ((ret = write_tag(tag_header, sizeof(tag_header), data, size)) != ERROR_SUCCESS) {
srs_error("write flv video tag failed. ret=%d", ret);
return ret;
}
return ret;
}
将Video tag header 写入cache中
int SrsFlvTransmuxer::write_video_to_cache(int64_t timestamp, char* data, int size, char* cache)
{
int ret = ERROR_SUCCESS;
srs_assert(data);
timestamp &= 0x7fffffff;
// 11bytes tag header
/*char tag_header[] = {
(char)SrsFrameTypeVideo, // TagType UB [5], 9 = video
(char)0x00, (char)0x00, (char)0x00, // DataSize UI24 Length of the message.
(char)0x00, (char)0x00, (char)0x00, // Timestamp UI24 Time in milliseconds at which the data in this tag applies.
(char)0x00, // TimestampExtended UI8
(char)0x00, (char)0x00, (char)0x00, // StreamID UI24 Always 0.
};*/
// write data size.
if ((ret = tag_stream->initialize(cache, 11)) != ERROR_SUCCESS) {
return ret;
}
tag_stream->write_1bytes(SrsFrameTypeVideo);
tag_stream->write_3bytes(size);
tag_stream->write_3bytes((int32_t)timestamp);
// default to little-endian
tag_stream->write_1bytes((timestamp >> 24) & 0xFF);
tag_stream->write_3bytes(0x00);
return ret;
}
5. 将tag header,tag data以及Previous tag size 一同写入 flv文件中
int SrsFlvTransmuxer::write_tag(char* header, int header_size, char* tag, int tag_size)
{
int ret = ERROR_SUCCESS;
// PreviousTagSizeN UI32 Size of last tag, including its header, in bytes.
char pre_size[SRS_FLV_PREVIOUS_TAG_SIZE];
if ((ret = write_pts_to_cache(tag_size + header_size, pre_size)) != ERROR_SUCCESS) {
return ret;
}
iovec iovs[3];
iovs[0].iov_base = header;
iovs[0].iov_len = header_size;
iovs[1].iov_base = tag;
iovs[1].iov_len = tag_size;
iovs[2].iov_base = pre_size;
iovs[2].iov_len = SRS_FLV_PREVIOUS_TAG_SIZE;
if ((ret = writer->writev(iovs, 3, NULL)) != ERROR_SUCCESS) {
if (!srs_is_client_gracefully_close(ret)) {
srs_error("write flv tag failed. ret=%d", ret);
}
return ret;
}
return ret;
}
上一篇: 深入浅出ES6之let和const命令
推荐阅读
-
Spring用代码来读取properties文件实例解析
-
Android使用Pull解析器解析xml文件的实现代码
-
基于android中读取assets目录下a.txt文件并进行解析的深入分析
-
Python解析m3u8拼接下载mp4视频文件的示例代码
-
Android使用Pull解析器解析xml文件的实现代码
-
python实现的解析crontab配置文件代码
-
Python 文件操作技巧(File operation) 实例代码分析
-
JavaScript Base64 作为文件上传的实例代码解析
-
基于android中读取assets目录下a.txt文件并进行解析的深入分析
-
编写JavaScript函数parseQueryString,把URL参数解析为一个对象(代码分析)