推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

前言

FLV 是FLASH VIDEO的简称,FLV流媒体格式是随着Flash MX的推出发展而来的视频格式。由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等问题。

rtmp 和 http-flv内部使用 flv协议封装 h264和 AAC音视频包。 FLV协议非常简单,接下来通过几张图快速掌握 flv封装协议。

FLV 格式

FLV 由 FLV header 跟 FLV file body 两部分组成,而 FLV file body 又由多个 FLV tag组成,FLV tag由 tag header + tag body组成。

FLV = FLV header + FLV body
FLV body = PreviousTagSize0 + Tag1 + PreviousTagSize1 + Tag2 + … + PreviousTagSizeN-1 + TagN
Tag1 = Tag1 header + Tag1 body

FLV header

字段字段类型字段含义
SignatureUI8签名,固定为’F’ (0x46)
SignatureUI8签名,固定为’L’ (0x4c)
SignatureUI8签名,固定为’V’ (0x56)
VersionUI80x01 表示 FLV 版本 1
TypeFlagsReservedUB[5]全为0
TypeFlagsAudioUB[1]1表示有audio tag,0表示没有
TypeFlagsReservedUB[1]0
TypeFlagsVideoUB[1]1表示有video tag,0表示没有
DataOffsetUI32FLV header的大小9,单位是字节

在这里插入图片描述

FLV Body

FLV file body很有规律,由一系列的TagSize和Tag组成。

  1. PreviousTagSize0 总是为0;
  2. tag 由tag header、tag body组成;
  3. 对FLV版本1,tag header 固定为11个字节,因此,PreviousTagSize(除第1个)的值为 11 + 前一个tag 的 tag body的大小;
    在这里插入图片描述

FLV tag

FLV tag 分为三类

  • Video Tag 0x08:audio data
  • Audio Tag 0x09:video data
  • Script Tag 0x12:script data

Tag head占11个字节

tag head

Video Tag

Video类型表明Data中存储的是视频数据,由 Video Tag Header(5个字节)和 Video Data组成。视频的编码类型可以是H264、H265等等。
详细定义请参照 flv_v10_1_adobe.pdf文档中的定义,结构如下图
在这里插入图片描述

Audio Tag

Audio类型表明Data中存储的是音频数据,由 Audio Tag Header(2个字节)和 Audio Data组成。音频的编码类型可以是aac、mp3等。
详细定义请参照 flv_v10_1_adobe.pdf文档中的定义,结构如下图:
在这里插入图片描述

Script Tag

Script Data Tags通常用来存放跟FLV中音视频相关的元数据信息(onMetaData),比如时长、长度、宽度等。它的定义相对复杂些,采用AMF(Action Message Format)封装了一系列数据类型,比如字符串、数值、数组等。

FLV格式整体图
在这里插入图片描述

obs 打包 FLV

参考上面的协议介绍,对照 obs打包 FLV tag源码,可以更好的掌握 FLV 文件协议。

源码文件 obs-studio\plugins\obs-outputs\flv-mux.c

打包 FLV header 和 Script Tag

打包 FLV header 和 Script Tag

void flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size,
		   bool write_header)
{
	struct array_output_data data;
	struct serializer s;
	uint8_t *meta_data = NULL;
	size_t meta_data_size;
	uint32_t start_pos;

	array_output_serializer_init(&s, &data);
	build_flv_meta_data(context, &meta_data, &meta_data_size);

	//======= flv header 9 Byte ======
	if (write_header) {
		s_write(&s, "FLV", 3);	// 3字节 FLV
		s_w8(&s, 1);		// 1字节 Version :1
		s_w8(&s, 5);		// 1字节 0000 0101 包含音频和视频
		s_wb32(&s, 9);		// 4字节 DataOffset 文件头部的大小:9
		s_wb32(&s, 0);		// 第一个 previous tag size:0 
	}

	start_pos = serializer_get_pos(&s);
	
	//=== tag header 11 Byte ==================
	s_w8(&s, RTMP_PACKET_TYPE_INFO);  //1字节 tag type 0x12: script tag
	s_wb24(&s, (uint32_t)meta_data_size);// 3字节 数据区大小
	s_wb32(&s, 0);						// 4字节 时间戳
	s_wb24(&s, 0);						//3字节 流ID 总为0
	//=========================================
	
	s_write(&s, meta_data, meta_data_size); // write script tag
	s_wb32(&s, (uint32_t)serializer_get_pos(&s) - start_pos - 1); //write tag size

	*output = data.bytes.array;
	*size = data.bytes.num;

	bfree(meta_data);
}

打包 Video Tag

打包 Video Tag

// 打包视频tag
static void flv_video(struct serializer *s, int32_t dts_offset,
		      struct encoder_packet *packet, bool is_header)
{
	int64_t offset = packet->pts - packet->dts;
	int32_t time_ms = get_ms_time(packet, packet->dts) - dts_offset;

	if (!packet->data || !packet->size)
		return;

	//=== tag header 11 Byte ==================
	s_w8(s, RTMP_PACKET_TYPE_VIDEO);	// 1字节 tag type  0x09:video tag
	s_wb24(s, (uint32_t)packet->size + 5);	// 3字节 数据区大小
	s_wb24(s, time_ms);			// 3字节 时间戳
	s_w8(s, (time_ms >> 24) & 0x7F);	// 1字节 扩展时间戳 
	s_wb24(s, 0);				// 3字节 流ID 总为0
	//=======================================
	
	//========vidoe tag header 5 Byte ==================
	/* these are the 5 extra bytes mentioned above */
	s_w8(s, packet->keyframe ? 0x17 : 0x27);	//1字节 frame type and codec ID  (0x17:关键帧 AVC)
	s_w8(s, is_header ? 0 : 1);			//1字节 packet type 0:AVC sequence header (sps pps) 1:avc nalu
	s_wb24(s, get_ms_time(packet, offset));		//3字节 composition time 
	//===================================================

	//========video data ===========================
	s_write(s, packet->data, packet->size);		// nalu data

	/* write tag size (starting byte doesn't count) */
	s_wb32(s, (uint32_t)serializer_get_pos(s) - 1); // previous tag size
}

打包 Audio Tag

打包 Audio Tag

// 打包音频 tag
static void flv_audio(struct serializer *s, int32_t dts_offset,
		      struct encoder_packet *packet, bool is_header)
{
	int32_t time_ms = get_ms_time(packet, packet->dts) - dts_offset;

	if (!packet->data || !packet->size)
		return;
	//=== tag header 11 Byte ==================
	s_w8(s, RTMP_PACKET_TYPE_AUDIO);       // 1字节 tag type // 0x08:audio tag
	s_wb24(s, (uint32_t)packet->size + 2); // 3字节 数据区大小
	s_wb24(s, time_ms);		// 3字节 时间戳
	s_w8(s, (time_ms >> 24) & 0x7F);	// 1字节 扩展时间戳 
	s_wb24(s, 0);				// 3字节 流ID 总为0
	//=========================================

	/* these are the two extra bytes mentioned above */
	s_w8(s, 0xaf);	//1个字节  1010 (4个bit 10: AAC 2: mp3) 11(2个bit 3: 44 kHz) 1(1个bit 0:8bit 1: 16bit) 1(1个bit 1:stereo  0: mono)  
	s_w8(s, is_header ? 0 : 1);	//1个字节  0:AAC Sequence Header  1:raw AAC raw data 
	s_write(s, packet->data, packet->size); // aac data

	/* write tag size (starting byte doesn't count) */
	s_wb32(s, (uint32_t)serializer_get_pos(s) - 1); // previous tag size
}

总结

FLV协议本身不算复杂,理解上的困难,更多时候来自音视频编解码相关的知识,比如H.264、AAC相关知识,建议不懂的时候自行查下。此外,FLV的字节序为大端序,在做协议解析的时候一定要注意。


  1. 本文部分技术点出处:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
  2. FLV协议5分钟入门浅析
  3. FLV视频文件格式图示
  4. video_file_format_spec_v10.pdf https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐