版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dancing_night/article/details/80830920
1、概述
被这个问题困扰很久,由于懒癌晚期,一直都是云里雾里,最近终于把微信聊天记录里的视频分析了一下,记录于此已做备忘。
2、分析过程
用ffmpeg写了个解码程序,遍历微信聊天视频目录找出所有视频文件,一个600多个,挨个解码,并把其长宽,AVFrame.linesize[0],AVFrame.linesize[1],AVFrame.linesize[2]打印出来,做对比,终于发现一些规律。下面是打印log(宽相同并且linesize[0], linesize[1], linesize[2]相同的只打印第一个):
可以发现其中linesize[0]是64位对齐,linesize[1]、linesize[2]为linesize[0]的一半。
然后又打印宽度和linesize[0]不一致的视频中对齐的部分(只打印第一帧的第一行):
填充大部分是0x00,也有一些其他值,先简单的总结填充字节没规律吧。
然后把每个视频的第一帧生成yuv,生成代码如下(也就是把填充字节丢掉):
if (pYUVFile != NULL)
{
for(int j=0; j<height; j++)
fwrite(frame->data[0] + j * frame->linesize[0], 1, width, pYUVFile);
for(int j=0; j<height/2; j++)
fwrite(frame->data[1] + j * frame->linesize[1], 1, width/2, pYUVFile);
for(int j=0; j<height/2; j++)
fwrite(frame->data[2] + j * frame->linesize[2], 1, width/2, pYUVFile);
fclose(pYUVFile);
}
并用雷神改写的yuv播放器播放,完全清晰,也不错位,如果把填充字节也保存,会出现花,或者错位。
3、代码
/*
*基于最简单的FFmpeg的解码器做的YUV420对齐分析
*
*缪国凯
*[email protected]
*
*本程序实现了视频解码保存为yuv,写yuv是用文件方式写的,没用到muxer,用作AVFrame里YUV420对齐分析
*/
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
//#include "libavfilter/avfiltergraph.h"
//#include "libavfilter/avcodec.h"
//#include "libavfilter/buffersink.h"
//#include "libavfilter/buffersrc.h"
//#include "libavutil/avutil.h"
//#include "libavutil/opt.h"
//#include "libavutil/pixdesc.h"
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
//#pragma comment(lib, "avdevice.lib")
//#pragma comment(lib, "avfilter.lib")
//#pragma comment(lib, "postproc.lib")
//#pragma comment(lib, "swresample.lib")
//#pragma comment(lib, "swscale.lib")
#ifdef __cplusplus
};
#endif
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include <iostream>
#include <io.h>
AVFormatContext *ifmt_ctx = NULL;
int openinputfile(const char* filename)
{
int ret = 0;
//open the input
if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0)
{
printf("can not open input\n");
return ret;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)))
{
printf("can not find input stream info\n");
return ret;
}
//open the decoder
for (int i = 0; i < ifmt_ctx->nb_streams; i++)
{
if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
ret = avcodec_open2(ifmt_ctx->streams[i]->codec,
avcodec_find_decoder(ifmt_ctx->streams[i]->codec->codec_id), NULL);
if (ret < 0)
{
printf("can not open decoder\n");
return ret;
}
}
}
return 0;
}
typedef struct _videoInfo
{
int _H;
int _L0;
int _L1;
int _L2;
bool operator==(const _videoInfo &_Right)
{
if ((_L0 == _Right._L0) && (_L1 == _Right._L1) && (_L2 == _Right._L2))
{
return true;
}
return false;
}
_videoInfo(const _videoInfo &_Right)
{
_H = _Right._H;
_L0 = _Right._L0;
_L1 = _Right._L1;
_L2 = _Right._L2;
}
_videoInfo(int _h, int _l0, int _l1, int _l2)
{
_H = _h;
_L0 = _l0;
_L1 = _l1;
_L2 = _l2;
}
_videoInfo()
{
_H = 0;
_L0 = 0;
_L1 = 0;
_L2 = 0;
}
}VideoInfo;
int main()
{
AVPacket pkt_in, pkt_out;
AVFrame *frame = NULL;
unsigned int stream_index;
av_register_all();
std::vector<std::string> tmpVec;
std::string path = "C:\\Users\\Administrator\\Documents\\WeChat Files\\evil-sight\\Video";
FILE *pLogFile = NULL;
pLogFile = fopen("testYUVAlign", "w");
FILE *pYUVLogFile = NULL;
pYUVLogFile = fopen("testYUVAlign_YUVData", "w");
std::map<int, VideoInfo> videoInfoMap;
int VideoIndex = 0;
long hFile = 0;
struct _finddata_t fileInfo;
std::string pathName, exdName;
// \\* 代表要遍历所有的类型
if ((hFile = _findfirst(pathName.assign(path).append("\\*").c_str(), &fileInfo)) == -1)
{
return 0;
}
do
{
if (!(fileInfo.attrib&_A_SUBDIR))
{
std::string tmpStr = fileInfo.name;
tmpStr = tmpStr.substr(tmpStr.find('.') + 1);
//if (tmpStr == "mp4")
{
tmpStr = fileInfo.name;
std::string tmpPath = path + "/" + tmpStr;
if (openinputfile(tmpPath.c_str()) < 0)
{
printf("failed to open input file\n");
goto end;
}
while(1)
{
if (av_read_frame(ifmt_ctx, &pkt_in) < 0)
{
break;
}
pkt_out.data = NULL;
pkt_out.size = 0;
av_init_packet(&pkt_out);
stream_index = pkt_in.stream_index;
frame = av_frame_alloc();
int got_frame = -1;
int ret = -1;
if (ifmt_ctx->streams[stream_index]->codec->codec_type == AVMEDIA_TYPE_VIDEO && ifmt_ctx->streams[stream_index]->codec->pix_fmt == AV_PIX_FMT_YUV420P)
{
ret = avcodec_decode_video2(ifmt_ctx->streams[stream_index]->codec, frame, &got_frame, &pkt_in);
if (ret < 0)
{
av_frame_free(&frame);
printf("decoding video stream failed\n");
break;
}
if (got_frame)
{
VideoIndex++;
int height = ifmt_ctx->streams[stream_index]->codec->height;
int width = ifmt_ctx->streams[stream_index]->codec->width;
{
//if (width != frame->linesize[0])
{
float tmpf = width / 16.0;
float tmpf1 = frame->linesize[0] / 16.0;
float tmpf2 = tmpf1 / 4.0;
float tmpf3 = ((float)frame->linesize[0]) / frame->linesize[1];
float tmpf4 = ((float)frame->linesize[0]) / frame->linesize[2];
float tmpf5 = ((float)frame->linesize[0]) / 64.0;
bool bPrint = false;
if (videoInfoMap.find(width) == videoInfoMap.end())
{
bPrint = true;
videoInfoMap[width]._H = height;
videoInfoMap[width]._L0 = frame->linesize[0];
videoInfoMap[width]._L1 = frame->linesize[1];
videoInfoMap[width]._L2 = frame->linesize[2];
}
else
{
VideoInfo tmpVI(height, frame->linesize[0], frame->linesize[1], frame->linesize[2]);
if (!(tmpVI == videoInfoMap[width]))
{
bPrint = true;
}
}
if(bPrint)
{
fprintf(pLogFile, "name = %s, VS:% 4d x% 4d, L0 = % 4d, L1 = % 4d, L2 = % 4d, L0/L1 = %.02f, L0/L2 = %.02f, W/16=%.02f, L0/16=%.02f, (L0/16)/4=%.02f, (L0/64)=%.02f\n",
tmpStr.c_str(), width, height, frame->linesize[0], frame->linesize[1], frame->linesize[2], tmpf3, tmpf4, tmpf, tmpf1, tmpf2, tmpf5);
if ((frame->linesize[0] - width) != 0)
{
fprintf(pYUVLogFile, "name = %s, VS:% 4d x% 4d, L0 = % 4d, L1 = % 4d, L2 = % 4d, L0/L1 = %.02f, L0/L2 = %.02f, W/16=%.02f, L0/16=%.02f, (L0/16)/4=%.02f, (L0/64)=%.02f\n",
tmpStr.c_str(), width, height, frame->linesize[0], frame->linesize[1], frame->linesize[2], tmpf3, tmpf4, tmpf, tmpf1, tmpf2, tmpf5);
fprintf(pYUVLogFile, "(frame->linesize[0] - width = %02d): ", frame->linesize[0] - width);
for (int i = width; i < frame->linesize[0]; i++)
{
fprintf(pYUVLogFile, "%02X ", frame->data[0][i]);
}
fprintf(pYUVLogFile, "\n\n");
}
//write a yuv file
{
std::string strYUVFileName = tmpStr.substr(0, tmpStr.find('.'));
strYUVFileName = "../TestVideo/" + strYUVFileName;
char chYUVFileName[256];
sprintf(chYUVFileName, "%s_%dx%d.yuv", strYUVFileName.c_str(), width, height);
FILE *pYUVFile = fopen(chYUVFileName, "w+b");
if (pYUVFile != NULL)
{
for(int j=0; j<height; j++)
fwrite(frame->data[0] + j * frame->linesize[0], 1, width, pYUVFile);
for(int j=0; j<height/2; j++)
fwrite(frame->data[1] + j * frame->linesize[1], 1, width/2, pYUVFile);
for(int j=0; j<height/2; j++)
fwrite(frame->data[2] + j * frame->linesize[2], 1, width/2, pYUVFile);
fclose(pYUVFile);
}
}
}
printf("name = %s, VS:% 4d x% 4d, L0 = % 4d, L1 = % 4d, L2 = % 4d, L0/L1 = %.02f, L0/L2 = %.02f, W/16=%.02f, L0/16=%.02f, (L0/16)/4=%.02f, (L0/64)=%.02f\n",
tmpStr.c_str(), width, height, frame->linesize[0], frame->linesize[1], frame->linesize[2], tmpf3, tmpf4, tmpf, tmpf1, tmpf2, tmpf5);
}
break;
}
}
}
}
end:
avformat_close_input(&ifmt_ctx);
}
}
} while (_findnext(hFile, &fileInfo) == 0);
_findclose(hFile);
fprintf(pLogFile, "analyze file num %d\n", VideoIndex);
fclose(pLogFile);
fclose(pYUVLogFile);
printf("press any key continue\n");
getchar();
return 0;
}
4、结论
AVFrame中保存YUV420P,的Y按64位对齐,UV为Y的一半。
5、工程下载