C++实现flv封装格式解析(音视频学习笔记三)
程序员文章站
2022-07-13 12:53:08
...
这篇博文使用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
*aaa@qq.com
*/
#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);
}