C++实现flv封装格式解析(音视频学习笔记三)

这篇博文使用C++解析一个flv文件信息,对其中一些重要的信息进行log输出,对flv的数据封装格式信息不清楚的可以去看这篇博文-FLV 封装格式解析,里面详细说明了flv文件的结构信息。这篇博文参考了雷霄骅博士的视音频数据处理入门:FLV封装格式解析的部分代码。

  • flv封装格式简要概述
    flv文件主要有FLV Header和FLV BODY两部份组成,借用FLV 封装格式解析中的一张图进行说明:
    在这里插入图片描述
  • flv封装主要由两部分构成,FLV HEADER和FLV BODY,FLV HEADER中存储着一些版本号,数据类型,与Header的size等信息。入下图所示:
    在这里插入图片描述
    其数据结构可表示为:
typedef struct {
    UI8 Signature;
    UI8 Signature;
    UI8 Signature;
    UI8 Version;
    UI8 TypeFlags;
    UI32 DataOffset;
}   FLVHEADER;
  • FLV BODY中back-point与TAG结构交织存储,其中back-point也即图片中的Prev Tag size表示的是上一个TAG的大小。TAG中存储Video TAG、Audio TAG、Script TAG其中之一,在具体的媒体流 TAG中以Video TAG为例,其中包含了11个字节的GeneraTagHeader头,紧接着是一个VideoTagHeader头部,然后是一个VideoTagBody,具体每个模块都对应着什么意思可以查阅FLV 封装格式解析

  • 下面先看代码的输出结果:
    在这里插入图片描述

  • 第一列是媒体类型,第二列是数据块的size,第三列是timestamp,然后是对照着结构信息输出的一些具体信息,包括SoundFormat、SoundRate、FrameType等信息。

- 全部代码:

/*
*WangYU
*2020-08-07
*[email protected]
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#pragma pack(1)

typedef unsigned char byte;
typedef unsigned int uint; //uint与int一样同是32位数4个字节。
#define TAG_TYPE_AUDIO 8
#define TAG_TYPE_VIDEO 9
#define TAG_TYPE_SCRIPT 18
typedef struct
{
	byte Signature[3];
	byte Version;
	byte TypeFlags; //b[0]表示是否存在视频流,b[2]表示是否存在音频流。
	uint DataOffset;
}FLVHEADER; //9字节。
typedef struct
{
	byte TagType;
	byte DataSize[3];
	byte Timestamp[3];
	uint Reserved;
}TAGHEADER;
//reverse_bytes - turn a BigEndian byte array into a LittleEndian integer
uint reverse_bytes(byte* p, char c) {
	int r = 0;
	int i;
	for (i = 0; i < c; i++)
		r |= (*(p + i) << (((c - 1) * 8) - 8 * i));
	return r;
}

int simple_fly_analyise(char* url)
{
	//FLYBODY中与tag交织在一起,表示前一个TAG的大小。
	uint PreviousTagS = 0;
	FILE* ifh = NULL;
	FLVHEADER flv;
	TAGHEADER tagheader;
	ifh = fopen(url, "rb+");
	if (ifh == NULL)
	{
		printf("Failed to open files!");
		return -1;
	}
	FILE* myout = stdout;
	fread((char*)&flv, 1, sizeof(FLVHEADER), ifh);
	fprintf(myout, "============FLV HEADER=============\n");
	fprintf(myout, "Signature:  0x %c %c %c\n", flv.Signature[0], flv.Signature[1], flv.Signature[2]);
	fprintf(myout, "Version: 0x %X\n", flv.Version);
	//通过判断Flag来判断音视频是否存在。
	byte type = flv.TypeFlags & 0x05;
	switch (type & 0x05)
	{
	case 0x01:
		fprintf(myout, "Flags  :    0x %X\n", flv.TypeFlags);
		fprintf(myout, "Type   :    %s\n", "仅仅存在视频流,音频流不存在!");
		break;
	case 0x04:
		fprintf(myout, "Flags  :    0x %X\n", flv.TypeFlags);
		fprintf(myout, "Type   :    %s\n", "仅仅存在音频流,视频流不存在!");
		break;
	case 0x05:
		fprintf(myout, "Flags  :    0x %X\n", flv.TypeFlags);
		fprintf(myout, "Type   :    %s\n", "音视频都存在");
		break;
	default:
		fprintf(myout, "Flags  :    0x %X\n", flv.TypeFlags);
		fprintf(myout, "Type   :    %s\n", "音视频都不存在");
		break;
	}
	fprintf(myout, "HeaderSize: 0x %X\n", reverse_bytes((byte*)&flv.DataOffset, sizeof(flv.DataOffset)));
	fprintf(myout, "=================================\n");
	//move the file pointer to the end of the header
	fseek(ifh, reverse_bytes((byte*)&flv.DataOffset, sizeof(flv.DataOffset)), SEEK_SET);
	//process each tag
	do {
		PreviousTagS = _getw(ifh);//以二进制形式读取一个整数,与TAG交织存储,所以搜先应先将他读出来。
		fread((void*)&tagheader, sizeof(TAGHEADER), 1, ifh);
		int datasize = tagheader.DataSize[0] * 65536 + tagheader.DataSize[1] * 256 + tagheader.DataSize[2];
		int timestamp = tagheader.Timestamp[0] * 65536 + tagheader.Timestamp[1] * 256 + tagheader.Timestamp[2];
		char tagtype_str[10];
		switch (tagheader.TagType&0x1f) //过滤掉前三位。
		{
		case TAG_TYPE_AUDIO:sprintf(tagtype_str, "AUDIO"); break;
		case TAG_TYPE_VIDEO:sprintf(tagtype_str, "VIDEO"); break;
		case TAG_TYPE_SCRIPT:sprintf(tagtype_str, "SCRIPT"); break;
		default:sprintf(tagtype_str, "UNKNOWN"); break;
		}
		fprintf(myout, "[%6s] %6d,%6d |", tagtype_str, datasize, timestamp);
		/*
		*一个 FLVTAG 中,前 11 个字节是通用 TagHeader,
		*后面紧跟跟着音频 Tag、视频 Tag 或脚本 Tag,
		*其中音频 Tag 和视频 Tag 都包含 TagHeader 和 TagBody 两部分,
		*脚本 Tag 只有 TagBody 部分。
		*/
		int x;
		switch (tagheader.TagType &0x1f)
		{
		case TAG_TYPE_AUDIO:
			char tag_first_byte;
			tag_first_byte = fgetc(ifh);//获取一个字符。SoundFormat[4],SoundRate[2],SoundSize[1],SoundType[1]
			x = tag_first_byte & 0xF0; //取前四位。
			//			x = x >> 4;
			switch (x >> 4) //SoundFormat
			{
			case 0: fprintf(myout, " Linear PCM, platform endian"); break;
			case 1: fprintf(myout, " ADPCM"); break;
			case 2: fprintf(myout, " MP3"); break;
			case 3: fprintf(myout, " Linear PCM, little endian"); break;
			case 4: fprintf(myout, " Nellymoser 16-kHz mono"); break;
			case 5: fprintf(myout, " Nellymoser 8-kHz mono"); break;
			case 6: fprintf(myout, " Nellymoser"); break;
			case 7: fprintf(myout, " G.711 A-law logarithmic PCM"); break;
			case 8: fprintf(myout, "G.711 mu-law logarithmic PCM 9 = reserved"); break;
			case 10: fprintf(myout, "AAC"); break;
			case 11: fprintf(myout, "Speex"); break;
			case 14: fprintf(myout, " MP3 8-Khz"); break;
			case 15: fprintf(myout, " Device-specific sound"); break;
			default:
				break;
			}
			x = tag_first_byte & 0xC0;//取5,6位
			switch (x >> 2)  //SoundRate
			{
			case 0:fprintf(myout, "|采样率:5.5KHZ"); break;
			case 1:fprintf(myout, "|采样率:11KHZ"); break;
			case 2:fprintf(myout, "|采样率:22KHZ"); break;
			case 3:fprintf(myout, "|采样率:44KHZ"); break;
			default:
				break;
			}
			x = tag_first_byte & 0x02; //取7位
			switch (x >> 1)  //SoundSize
			{
			case 0:fprintf(myout, "|采样位深:8位"); break;
			case 1:fprintf(myout, "|采样位深:16位"); break;
			default:
				break;
			}
			x = tag_first_byte & 0x01; //取7位
			switch (x )  //SoundType
			{
			case 0:fprintf(myout, "|单声道\n"); break;
			case 1:fprintf(myout, "|立体声道\n"); break;
			default:
				fprintf(myout, "\n"); break;
				break;
			}
			for (int i = 0; i < datasize-1; i++)
				fgetc(ifh);
			break;
		case TAG_TYPE_VIDEO:
			char videotag_first_byte;
			videotag_first_byte = fgetc(ifh);//获取一个字符。SoundFormat[4],SoundRate[2],SoundSize[1],SoundType[1]
			x = videotag_first_byte & 0xF0; //取前四位。
			switch (x>>4)
			{
			case 1:fprintf(myout, "keyframe (for AVC, a seekable frame)"); break;
			case 2:fprintf(myout, "inter frame (for AVC, a non-seekable frame)"); break;
			case 3:fprintf(myout, "disposable inter frame (H.263 only)"); break;
			case 4:fprintf(myout, "generated keyframe (reserved for server use only)"); break;
			case 5:fprintf(myout, "video info/command frame"); break;
			default:
				break;
			}
			x = videotag_first_byte & 0x0F; //取前四位。
			switch (x)
			{
			case 1:fprintf(myout, "JPEG (currently unused)\n"); break;
			case 2:fprintf(myout, "Sorenson H.263\n"); break;
			case 3:fprintf(myout, "Screen video\n"); break;
			case 4:fprintf(myout, "On2 VP6)\n"); break;
			case 5:fprintf(myout, "On2 VP6 with alpha channel\n"); break;
			case 6:fprintf(myout, "Screen video version 2\n"); break;
			case 7:fprintf(myout, "AVC\n"); break;
			default:
				break;
			}
			for (int i = 0; i < datasize-1; i++)
				fgetc(ifh);
			break;
		case TAG_TYPE_SCRIPT:
			printf("\n");
			for (int i = 0; i < datasize; i++)
				fgetc(ifh);
			break;
		default:
			break;
		}
		
	} while (!feof(ifh));
}
int main()
{
	char url[] = "test4.flv";
	simple_fly_analyise(url);
}

猜你喜欢

转载自blog.csdn.net/weixin_40840000/article/details/107859776