AVFrame存储YUV420P对齐分析

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对齐分析
 *
 *缪国凯
 *821486004@qq.com
 *
 *本程序实现了视频解码保存为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、工程下载

工程地址

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dancing_night

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值