/**
* 最简单的 FLV 封装格式解析程序
* Simplest FLV Parser
*
* 原程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本项目是一个 FLV 封装格式解析程序,可以将 FLV 中的 MP3 音频码流分离出来。
*
* This project is a FLV format analysis program.
* It can analysis FLV file and extract MP3 audio stream.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Important
#pragma pack(1)
// 解决报错:fopen() 函数不安全
#pragma warning(disable:4996)
#define PREVIOUS_TAG_SIZE_LENGTH 4
// Tag Type
#define TAG_TYPE_AUDIO 8
#define TAG_TYPE_VIDEO 9
#define TAG_TYPE_SCRIPT 18
// 1 字节,8 bit
typedef unsigned char UI8;
// 2 字节,16 bit
typedef unsigned short UI16;
// 4 字节,32 bit
typedef unsigned int UI32;
// FLV header,9 字节
typedef struct {
UI8 Signature[3]; // 'F' (0x46),'L' (0x4C),'V' (0x56)
UI8 Version; // FLV 版本,一般为 0x01,表示 FLV 版本 1
UI8 TypeFlags; // b0 是否存在视频流,b2 是否存在音频流,其他字段保留,值为 0
UI32 DataOffset; // FLV Header 长度,单位:字节,一般为 9
} FLVHEADER;
// FLV tag header,11 字节
typedef struct {
UI8 TagType; // tag 类型,8 = 音频,9 = 视频,18 = 脚本数据
UI8 DataSize[3]; // tag data 部分的大小,不包含 tag header
UI8 Timestamp[3]; // 当前 tag 的解码时间戳 (DTS),相对值,单位:ms
UI8 TimestampExtended; // 和 Timestamp 字段一起构成一个 32 位值, 此字段为高 8 位,单位:ms
UI8 StreamID[3]; // 总是 0
} TAGHEADER;
// turn a BigEndian byte array into a LittleEndian integer
unsigned int reverse_bytes(UI8 *p, char c)
{
int r = 0;
int i;
for (i = 0; i < c; i++)
r |= (*(p + i) << (((c - 1) * 8) - 8 * i));
return r;
}
/**
* Analysis FLV file
* @param url Location of input FLV file
*/
int simplest_flv_parser(char *url)
{
// whether output audio / video stream
int output_a = 1;
int output_v = 1;
FILE *ifh = NULL, *vfh = NULL, *afh = NULL;
// FILE *myout = fopen("output_log.txt", "wb+");
FILE *myout = stdout;
FLVHEADER flv_header;
TAGHEADER tag_header;
UI32 PreviousTagSize, PreviousTagSize_z = 0;
UI32 ts = 0, ts_new = 0;
ifh = fopen(url, "rb+");
if (ifh == NULL)
{
printf("Failed to open files.\n");
return -1;
}
// read FLV header
fread((char *)&flv_header, sizeof(FLVHEADER), 1, ifh);
// print FLV header information
fprintf(myout, "================== FLV Header ==================\n");
fprintf(myout, "Signature: %c %c %c\n", flv_header.Signature[0], flv_header.Signature[1], flv_header.Signature[2]);
fprintf(myout, "Version: 0x%X\n", flv_header.Version);
fprintf(myout, "TypeFlags: 0x%X", flv_header.TypeFlags);
// b2 是否存在音频流,1 = 存在,0 = 不存在
fprintf(myout, ",Audio identification: %d,", (flv_header.TypeFlags & 0x04) >> 2);
// b0 是否存在视频流,1 = 存在,0 = 不存在
fprintf(myout, "Video identification: %d\n", flv_header.TypeFlags & 0x01);
UI32 header_size = reverse_bytes((UI8 *)&flv_header.DataOffset, sizeof(flv_header.DataOffset));
fprintf(myout, "HeaderSize: 0x%X\n", header_size);
fprintf(myout, "================================================\n");
// move the file pointer to the end of the header
fseek(ifh, header_size, SEEK_SET); // SEEK_SET 表示文件开头
// process each FLV tag
do
{
/**
* int _getw(FILE *stream):以二进制形式读取一个整数
* @param stream 指向 FILE 结构的指针
* @return 返回流中下一个整数值
*/
PreviousTagSize = _getw(ifh);
// read FLV tag header
fread((char *)&tag_header, sizeof(TAGHEADER), 1, ifh);
char tagtype_str[10];
switch (tag_header.TagType)
{
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;
}
// 当前 tag data 部分的大小,不包含 tag header
int tagheader_datasize = tag_header.DataSize[0] * 65536 + tag_header.DataSize[1] * 256 + tag_header.DataSize[2];
// 当前 tag 的解码时间戳 (DTS),相对值,单位:ms
int tagheader_timestamp = tag_header.Timestamp[0] * 65536 + tag_header.Timestamp[1] * 256 + tag_header.Timestamp[2];
fprintf(myout, "[%6s] %6d %6d |", tagtype_str, tagheader_datasize, tagheader_timestamp);
// if we are not past the end of file, process the tag
if (feof(ifh))
break;
// process tag by type
switch (tag_header.TagType)
{
case TAG_TYPE_AUDIO:
{
char audio_tag_str[100] = { 0 };
strcat(audio_tag_str, "| ");
/**
* int fgetc(FILE *stream):从文件指针 stream 指向的文件中读取一个字符,读取一个字节后,光标位置后移一个字节
* @param stream 指向 FILE 结构的指针
* @return 返回流中读取的一个整数值
*/
UI8 audio_tag_header = fgetc(ifh);
// audio tag header 占 1 字节
// 前 4 bit 表示音频格式
int SoundFormat = (audio_tag_header & 0xF0) >> 4;
switch (SoundFormat)
{
case 0: strcat(audio_tag_str, "Linear PCM, platform endian"); break;
case 1: strcat(audio_tag_str, "ADPCM"); break;
case 2: strcat(audio_tag_str, "MP3"); break;
case 3: strcat(audio_tag_str, "Linear PCM, little endian"); break;
case 4: strcat(audio_tag_str, "Nellymoser 16-kHz mono"); break;
case 5: strcat(audio_tag_str, "Nellymoser 8-kHz mono"); break;
case 6: strcat(audio_tag_str, "Nellymoser"); break;
case 7: strcat(audio_tag_str, "G.711 A-law logarithmic PCM"); break;
case 8: strcat(audio_tag_str, "G.711 mu-law logarithmic PCM"); break;
case 9: strcat(audio_tag_str, "reserved"); break;
case 10: strcat(audio_tag_str, "AAC"); break;
case 11: strcat(audio_tag_str, "Speex"); break;
case 14: strcat(audio_tag_str, "MP3 8-Khz"); break;
case 15: strcat(audio_tag_str, "Device-specific sound"); break;
default: strcat(audio_tag_str, "unknown"); break;
}
strcat(audio_tag_str, "| ");
// 第 5、6 bit 表示采样率
int SoundRate = (audio_tag_header & 0x0C) >> 2;
switch (SoundRate)
{
case 0: strcat(audio_tag_str, "5.5-kHz"); break;
case 1: strcat(audio_tag_str, "11-kHz"); break;
case 2: strcat(audio_tag_str, "22-kHz"); break;
case 3: strcat(audio_tag_str, "44-kHz"); break;
default: strcat(audio_tag_str, "unknown"); break;
}
strcat(audio_tag_str, "| ");
// 第 7 bit 表示采样精度
int SoundSize = (audio_tag_header & 0x02) >> 1;
switch (SoundSize)
{
case 0: strcat(audio_tag_str, "8Bit"); break;
case 1: strcat(audio_tag_str, "16Bit"); break;
default: strcat(audio_tag_str, "unknown"); break;
}
strcat(audio_tag_str, "| ");
// 第 8 bit 表示音频声道
int SoundType = audio_tag_header & 0x01;
switch (SoundType)
{
case 0: strcat(audio_tag_str, "Mono"); break;
case 1: strcat(audio_tag_str, "Stereo"); break;
default: strcat(audio_tag_str, "unknown"); break;
}
fprintf(myout, "%s", audio_tag_str);
if (SoundFormat == 10)
{
// 如果音频格式为 10,即是 AAC 格式。
// AudioTagHeader 中会多出一个字节 AACPacketType,这个字段来表示 AACAUDIODATA 的类型,
// 0 = AAC sequence header,1 = AAC raw。
char aac_pakect_type[100] = { 0 };
UI8 AACPacketType = fgetc(ifh);
switch (AACPacketType)
{
case 0: strcat(aac_pakect_type, "AAC sequence header"); break;
case 1: strcat(aac_pakect_type, "AAC raw"); break;
default: strcat(aac_pakect_type, "unknown"); break;
}
fprintf(myout, "| %s", aac_pakect_type);
// 文件指针回退 1 字节
fseek(ifh, -1, SEEK_CUR); // SEEK_CUR 表示文件当前位置
}
// if the output file hasn't been opened, open it.
if (output_a != 0