本文记录了FLV封装格式解析,封装格式数据在视频播放器中的位置如下所示
原理
FLV封装格式是由一个FLV Header文件头和一个一个的Tag组成。Tag中包含了音频数据以及视频数据。其结构如下图所示。
有关FLV的格式本文的说明见雷神《视音频编解码学习工程:FLV封装格式分析器》。本程序对FLV中的FLV Header和Tag的解析,并可以分离出其中的音视频流。
代码
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <math.h>
#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "xohnffmpeg", format, ##__VA_ARGS__)
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, "xohnffmpeg", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...) printf("xohnffmpeg" format "\n", ##__VA_ARGS__)
#define LOGI(format, ...) printf("xohnffmpeg " format "\n", ##__VA_ARGS__)
#endif
//Important!
#pragma pack(1)
#define TAG_TYPE_SCRIPT 18
#define TAG_TYPE_AUDIO 8
#define TAG_TYPE_VIDEO 9
typedef unsigned char byte;
typedef unsigned int uint;
typedef struct {
byte Signature[3];
byte Version;
byte Flags;
uint DataOffset;
} FLV_HEADER;
typedef struct {
byte TagType;
byte DataSize[3];
byte Timestamp[3];
uint Reserved;
} TAG_HEADER;
//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;
}
unsigned int getw(FILE *fp){
char *s;
unsigned int i;
s = (char*)&i;
s[0] = getc(fp);
s[1] = getc(fp);
s[2] = getc(fp);
s[3] = getc(fp);
return(i);
}
/**
* Analysis FLV file
* @param jstr_url Location of input FLV file.
*/
extern "C" JNIEXPORT jint JNICALL
Java_com_xohn_ffmpeg_AVUtils_flvParser(
JNIEnv *env,jobject /* this */,
jstring jstr_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;
FLV_HEADER flv;
TAG_HEADER tagheader;
uint previoustagsize, previoustagsize_z=0;
uint ts = 0, ts_new = 0;
char input_url[100] = {0};
const char *str = env->GetStringUTFChars(jstr_url, NULL);
sprintf(input_url,"%s",str);
env->ReleaseStringUTFChars(jstr_url,str);
ifh = fopen(input_url, "rb+");
if (ifh == NULL) {
LOGE("Failed to open files!");
return -1;
}
//FLV file header
fread((char *)&flv,1,sizeof(FLV_HEADER),ifh);
LOGI("============== FLV Header ==============\n");
LOGI("Signature: %c %c %c\n",flv.Signature[0],flv.Signature[1],flv.Signature[2]);
LOGI("Version: 0x%02X\n",flv.Version);
LOGI("Flags : 0x%02X\n",flv.Flags);
LOGI("HeaderSize: 0x%02X\n",reverse_bytes((byte *)&flv.DataOffset, sizeof(flv.DataOffset)));
LOGI("========================================\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 {
char print_str[150] = {0};
//previoustagsize = _getw(ifh);
previoustagsize = getw(ifh);
fread((void *)&tagheader,sizeof(TAG_HEADER),1,ifh);
//int temp_datasize1=reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize));
int tagheader_datasize = tagheader.DataSize[0]*65536+tagheader.DataSize[1]*256+tagheader.DataSize[2];
int tagheader_timestamp = tagheader.Timestamp[0]*65536+tagheader.Timestamp[1]*256+tagheader.Timestamp[2];
char tagtype_str[10];
switch(tagheader.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;
}
//LOGI("[%6s] %6d %6d |",tagtype_str,tagheader_datasize,tagheader_timestamp);
sprintf(print_str,"[%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 (tagheader.TagType) {
case TAG_TYPE_AUDIO:{
char audiotag_str[100] = {0};
strcat(audiotag_str,"| ");
char tagdata_first_byte;
tagdata_first_byte = fgetc(ifh);
int x = tagdata_first_byte&0xF0;
x = x>>4;
switch (x){
case 0:strcat(audiotag_str,"Linear PCM, platform endian");break;
case 1:strcat(audiotag_str,"ADPCM");break;
case 2:strcat(audiotag_str,"MP3");break;
case 3:strcat(audiotag_str,"Linear PCM, little endian");break;
case 4:strcat(audiotag_str,"Nellymoser 16-kHz mono");break;
case 5:strcat(audiotag_str,"Nellymoser 8-kHz mono");break;
case 6:strcat(audiotag_str,"Nellymoser");break;
case 7:strcat(audiotag_str,"G.711 A-law logarithmic PCM");break;
case 8:strcat(audiotag_str,"G.711 mu-law logarithmic PCM");break;
case 9:strcat(audiotag_str,"reserved");break;
case 10:strcat(audiotag_str,"AAC");break;
case 11:strcat(audiotag_str,"Speex");break;
case 14:strcat(audiotag_str,"MP3 8-Khz");break;
case 15:strcat(audiotag_str,"Device-specific sound");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
strcat(audiotag_str,"| ");
x = tagdata_first_byte&0x0C;
x = x>>2;
switch (x){
case 0:strcat(audiotag_str,"5.5-kHz");break;
case 1:strcat(audiotag_str,"1-kHz");break;
case 2:strcat(audiotag_str,"22-kHz");break;
case 3:strcat(audiotag_str,"44-kHz");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
strcat(audiotag_str,"| ");
x = tagdata_first_byte&0x02;
x = x>>1;
switch (x){
case 0:strcat(audiotag_str,"8Bit");break;
case 1:strcat(audiotag_str,"16Bit");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
strcat(audiotag_str,"| ");
x = tagdata_first_byte&0x01;
switch (x){
case 0:strcat(audiotag_str,"Mono");break;
case 1:strcat(audiotag_str,"Stereo");break;
default:strcat(audiotag_str,"UNKNOWN");break;
}
//LOGI("%s",audiotag_str);
strcat(print_str,audiotag_str);
//if the output file hasn't been opened, open it.
if(output_a!=0 && afh == NULL){
strcpy(input_url+strlen(input_url)-4,".mp3");
afh = fopen(input_url, "wb");
}
//TagData - First Byte Data
int data_size = reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize))-1;
if(output_a != 0){
//TagData+1
for (int i=0; i<data_size; i++)
fputc(fgetc(ifh),afh);
}else{
for (int i=0; i<data_size; i++)
fgetc(ifh);
}
break;
}
case TAG_TYPE_VIDEO:{
char videotag_str[100] = {0};
strcat(videotag_str,"| ");
char tagdata_first_byte;
tagdata_first_byte = fgetc(ifh);
int x = tagdata_first_byte&0xF0;
x = x>>4;
switch (x){
case 1:strcat(videotag_str,"key frame ");break;
case 2:strcat(videotag_str,"inter frame");break;
case 3:strcat(videotag_str,"disposable inter frame");break;
case 4:strcat(videotag_str,"generated keyframe");break;
case 5:strcat(videotag_str,"video info/command frame");break;
default:strcat(videotag_str,"UNKNOWN");break;
}
strcat(videotag_str,"| ");
x = tagdata_first_byte&0x0F;
switch (x){
case 1:strcat(videotag_str,"JPEG (currently unused)");break;
case 2:strcat(videotag_str,"Sorenson H.263");break;
case 3:strcat(videotag_str,"Screen video");break;
case 4:strcat(videotag_str,"On2 VP6");break;
case 5:strcat(videotag_str,"On2 VP6 with alpha channel");break;
case 6:strcat(videotag_str,"Screen video version 2");break;
case 7:strcat(videotag_str,"AVC");break;
default:strcat(videotag_str,"UNKNOWN");break;
}
//LOGI("%s",videotag_str);
strcat(print_str,videotag_str);
fseek(ifh, -1, SEEK_CUR);
//if the output file hasn't been opened, open it.
if (output_v!=0 && vfh == NULL) {
//write the flv header (reuse the original file's hdr) and first previoustagsize
strcpy(input_url+strlen(input_url)-4,"_extract.flv");
vfh = fopen(input_url, "wb");
fwrite((char *)&flv,1, sizeof(flv),vfh);
fwrite((char *)&previoustagsize_z,1,sizeof(previoustagsize_z),vfh);
}
#if 0
//Change Timestamp
//Get Timestamp
ts = reverse_bytes((byte *)&tagheader.Timestamp, sizeof(tagheader.Timestamp));
ts = ts*2;
//Writeback Timestamp
ts_new = reverse_bytes((byte *)&ts, sizeof(ts));
memcpy(&tagheader.Timestamp, ((char *)&ts_new) + 1, sizeof(tagheader.Timestamp));
#endif
//TagData + Previous Tag Size
int data_size = reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize))+4;
if(output_v != 0){
//TagHeader
fwrite((char *)&tagheader,1, sizeof(tagheader),vfh);
//TagData
for (int i=0; i<data_size; i++)
fputc(fgetc(ifh),vfh);
}else{
for (int i=0; i<data_size; i++)
fgetc(ifh);
}
//rewind 4 bytes, because we need to read the previoustagsize again for the loop's sake
fseek(ifh, -4, SEEK_CUR);
break;
}
default:
//skip the data of this tag
fseek(ifh, reverse_bytes((byte *)&tagheader.DataSize, sizeof(tagheader.DataSize)), SEEK_CUR);
}
//LOGI("\n");
strcat(print_str,"\n");
LOGI("%s",print_str);
} while (!feof(ifh));
//_fcloseall();
fclose(ifh);
if(output_a!=0 && afh != NULL){
fclose(afh);
}
if (output_v!=0 && vfh != NULL) {
fclose(vfh);
}
return 0;
}
函数调用
AVUtils.getInstance().flvParser(ROOT+"cuc_ieschool.flv");
输出为FLV的统计数据,如下图
并且会在同目录下提取出 cuc_ieschool_extract.mp3 & cuc_ieschool_extract.flv,单纯的音视频文件
参考文章:
雷神文章 https://blog.csdn.net/leixiaohua1020/article/details/50535082
FLV封装格式分析器 https://blog.csdn.net/leixiaohua1020/article/details/17934487
项目git地址:
https://gitee.com/xohn/FFmpeg.git 中的FFMpeg1 - xohn_avutils_flv.cpp