本次文章主要涉及到对视频的缩放操作,操作本身很简单,涉及到的核心函数有三个:
1、对SwsContext上下文初始化
2、转换操作
3、资源释放
经过上面三步的操作就可以实现一个视频的缩放,如果涉及到图像的滤波,也是上面三个步骤。针对函数的详细解释,
在接下来的代码展示中会写出来。
另外一个不得不说的是函数:
int av_image_alloc(uint8_t *pointers[4], int linesizes[4],int w, int h, enum AVPixelFormat pix_fmt, int align)
其最后一个参数align,表示分配的数据是按多少字节对齐。只所以对齐是为了提高数据的读取效率。FFMPEG源码中
一般都是将这个参数设置为32,即32字节对齐。所以我在此次代码练习中,也将最后转换的YUV数据保存到按32字节对齐
的空间中,而没有考虑到为什么要写为32。最后播放生存的YUV数据是出现的画面如下:
此时YUV数据已经乱了,无法播放了。
最后查资料发现,数据的对齐要根据保存视频的尺寸来设置的。比方说最后缩放的视频尺寸是480X480。
我们知道针对YUV420P的数据Y分量为480,U,V分量为Y分量width的一半即240。如果我们把align设置为32,那么240/32是不能整除的,最后导致数据错位,播放的时候就会不能正常显示画面,而是出现类似上图所示画面。另外该对齐基数align必须是2的n次方。如果我们想32自己对齐,可以设置生存的视频尺寸是32的倍数,比如576x400。我们可以发现576的一半是288,288是可以整除32的。如果我们将align设置为1,那基本是通用的了,但是这样会严重的影响读取效率。最后设置缩放的分辨率为576x400的效果如下:
代码展示如下:
#ifndef _IO_FILE_H_
#define _IO_FILE_H_
typedef struct _IOFile
{
char *inputName; /*输入文件名*/
char *outputName; /*输出文件名*/
char *inputFrameSize; /*输入图像尺寸*/
char *outputFrameSize; /*输出图像尺寸*/
FILE *iFile; /*输入文件指针*/
FILE *oFile; /*输出文件指针*/
}IOFile;
#endif
#ifndef _FFMPEG_AV_SCALING_H_
#define _FFMPEG_AV_SCALING_H_
#include
#include
#include
#endif
#ifndef _COMMON_H_
#define _COMMON_H_
#include
#include "ffmpeg_av_scaling.h"
#include "IOFile.h"
typedef int bool;
#define false 0
#define true 1
#endif
#include "common.h"
#define MAX_FRAME_NUM (100)
/*解析输入的参数*/
static bool hello(int argc, char **argv, IOFile *files)
{
printf("FFMpeg Scaling Demo.\nCommand format: %s input_file input_frame_size output_file output_frame_size\n", argv[0]);
if (argc != 5)
{
printf("Error: command line error, please re-check.\n");
return false;
}
files->inputName = argv[1];
files->inputFrameSize = argv[2];
files->outputName = argv[3];
files->outputFrameSize = argv[4];
files->iFile=fopen(files->inputName, "rb+");
if (!files->iFile)
{
printf("Error: cannot open input file.\n");
return false;
}
files->oFile=fopen(files->outputName, "wb+");
if (!files->oFile)
{
printf("Error: cannot open output file.\n");
return false;
}
return true;
}
/*************************************************
Function: read_yuv_from_ifile
Description: 从输入文件中读取像素数据
Calls: 无
Called By: main
Input: (in)srcWidth : 输入图像的宽度
(in)srcHeight : 输入图像的的高度
(in)color_plane :颜色分量:0——Y;1——U;2——V
(in)files : 包含输入文件的结构
Output: (out)src_data : 保存输入数据的缓存
(out)src_linesize :
Return: true : 命令行解析正确
false : 命令行解析错误
*************************************************/
static int read_yuv_from_ifile(unsigned char *src_data[4], int src_linesize[4],int srcWidth,int srcHeight,int color_plane,IOFile *files)
{
int frame_height = color_plane == 0?srcHeight : srcHeight/2;
int frame_width = color_plane == 0?srcWidth : srcWidth/2;
int frame_size = frame_width*frame_height;
int frame_stride = src_linesize[color_plane];
int row_idx;
/*
linesize = width + padding size(16+16) for YUV
*/
if (frame_width == frame_stride)
{
/*宽度和跨度相等,像素信息是连续存放*/
fread(src_data[color_plane],frame_size, 1, files->iFile);
}
else
{
/*宽度小于跨度,像素信息保存空间之间存在间隔*/
for ( row_idx = 0 ; row_idx < frame_height ; row_idx++ )
{
fread(src_data[color_plane]+row_idx*frame_stride, frame_width, 1, files->iFile);
}
}
//printf("frame_size:%d\n",frame_size);
return frame_size;
}
int main(int argc, char **argv)
{
int ret = 0;
int srcWidth,srcHeight;
int dstWidth,dstHeight;
/*解析命令行输入参数*/
IOFile files = {NULL};
if ( !hello(argc,argv,&files) )
{
goto FF_END;
}
/*
根据framesize解析出width和height,比如320x240,解析出
宽为320,高为240
*/
if (av_parse_video_size(&srcWidth,&srcHeight,files.inputFrameSize))
{
printf("Error:parsing input size failed.\n");
goto FF_END;
}
if (av_parse_video_size(&dstWidth,&dstHeight,files.outputFrameSize))
{
printf("Error:parsing output size failed.\n");
goto FF_END;
}
/*创建SwsContext结构*/
enum AVPixelFormat src_pix_fmt = AV_PIX_FMT_YUV420P;
enum AVPixelFormat dst_pix_fmt = AV_PIX_FMT_YUV420P;
/*设置图像缩放上下文信息,函数参数信息如下:
定义输入图像信息,输出图像信息,SWS_BILINEAR选择的缩放图像算法。(当输入输出图像大小不一样时才有效)
最后三个NULL的三个参数表示,前两个分别定义输入输出图像滤波器信息,如果不做前后图像滤波,默认为NULL,
最后一个参数定义了特定缩放算法所需要的参数,默认为NULL
*/
printf("srcw:%d,h:%d,dstw:%d,h:%d\n",srcWidth,srcHeight,dstWidth,dstHeight);
struct SwsContext *sws_ctx = sws_getContext(srcWidth,srcHeight,src_pix_fmt,dstWidth,dstHeight,dst_pix_fmt,SWS_BILINEAR,NULL,NULL,NULL);
if (!sws_ctx)
{
printf("Error: allocating SwsContext struct failed.\n");
goto FF_END;
}
/*分配input和output*/
unsigned char *src_data[4],*dst_data[4];
int src_linesize[4],dst_linesize[4];
/*
根据w,h,pixfmt,分配图像空间,填充srcdata和linesize,最后返回的大小
对应linesize空间大小
*/
if ((ret = av_image_alloc(src_data,src_linesize,srcWidth,srcHeight,src_pix_fmt,32)) < 0)
{
printf("Error:allocating src image failed.\n");
goto FF_END;
}
if ((ret = av_image_alloc(dst_data,dst_linesize,dstWidth,dstHeight,dst_pix_fmt,1)) < 0)
{
printf("Error:allocating dst image failed.\n");
goto FF_END;
}
/*从输出frame中写出到输出文件*/
int dst_bufsize = ret;
int idx;
for ( idx = 0 ; idx < MAX_FRAME_NUM ; idx++ )
{
read_yuv_from_ifile(src_data, src_linesize, srcWidth, srcHeight, 0, &files);
read_yuv_from_ifile(src_data, src_linesize, srcWidth, srcHeight, 1, &files);
read_yuv_from_ifile(src_data, src_linesize, srcWidth, srcHeight, 2, &files);
/*转换处理*/
sws_scale(sws_ctx, (const unsigned char * const *)src_data,src_linesize, 0, srcHeight, dst_data, dst_linesize);
fwrite(dst_data[0], 1, dst_bufsize, files.oFile);
}
printf("Video scaling succeeded.\n");
FF_END:
fclose(files.iFile);
fclose(files.oFile);
av_freep(&src_data[0]);
av_freep(&dst_data[0]);
sws_freeContext(sws_ctx);
return 0;
}