前言
1、視頻縮放是指將視頻放大或者縮小,放大或者縮小對應(yīng)著不同的縮放算法芳室,每一種算法性能和效果也不一致。視頻縮小也是很常見的需求刹勃,各個點播平臺基本上都會提供不同分辨率(超清1080P堪侯,高清720P,標(biāo)清360P)的視頻資源以適應(yīng)用戶不同網(wǎng)絡(luò)條件的需求荔仁。
2伍宦、視頻像素格式轉(zhuǎn)換芽死;安卓平臺碎片化的特性,安卓手機錄制的視頻可能有多種不同像素格式雹拄,比如NV12,NV21等等收奔,雖然他們都是YUV顏色空間掌呜,但是轉(zhuǎn)換成RGB的方式和方法卻不一致滓玖。
視頻縮放及像素格式轉(zhuǎn)換流程
視頻縮放及像素格式轉(zhuǎn)換相關(guān)命令行介紹
- 1、MP4文件中提取視頻并轉(zhuǎn)換為YUV
ffmpeg -i test_1280x720.MP4 -ss 00:00:00 -t 00:00:05 -pixel_format yuv420p -vf scale=640:360 -f rawvideo test_640x360_yuv420p.yuv
備注:pixel_format指定輸出的YUV的格式质蕉。vf scale=640:360表示壓縮視頻濾鏡势篡,最終轉(zhuǎn)換后的視頻分辨率為640x360
- 2、播放YUV
ffplay -i test.yuv -f rawvideo -pixel_format nv12 -video_size 320x180 -framerate 50
備注:-pixel_format代表yuv的格式模暗,如果不指定默認yuv420p;framerate代表播放時視頻的幀率禁悠,如果不指定默認25
視頻縮放及像素格式轉(zhuǎn)換流程相關(guān)函數(shù)介紹
1、SwsContext
視頻進行縮放和格式轉(zhuǎn)換的上下文
2兑宇、struct SwsContext *sws_getContext(
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,SwsFilter *dstFilter,
const double *param);
創(chuàng)建SwsContext上下文碍侦,并將函數(shù)中參數(shù)賦值到上下文中
1、參數(shù)1 2 3:轉(zhuǎn)換源數(shù)據(jù)寬隶糕,高瓷产,像素格式
2、參數(shù)4 5 6:轉(zhuǎn)換后數(shù)據(jù)寬枚驻,高濒旦,像素格式
3、參數(shù)7:如果進行縮放再登,所采用的的縮放算法
4尔邓、參數(shù)8:轉(zhuǎn)換源數(shù)據(jù)的 SwsFilter 參數(shù)
5、參數(shù)9:轉(zhuǎn)換后數(shù)據(jù)的 SwsFilter 參數(shù)
6锉矢、參數(shù)10:轉(zhuǎn)換縮放算法的相關(guān)參數(shù) const double* 類型數(shù)組
7梯嗽、8 9 10三個參數(shù)一般傳遞NULL,即采用默認值即可
8沽损、SWS_BICUBIC性能比較好慷荔,SWS_FAST_BILINEAR在性能和速度之間有一個比好好的平衡,SWS_POINT的效果比較差缠俺。
struct SwsContext *sws_alloc_context(void)
創(chuàng)建SwsContext上下文显晶,并賦值默認的參數(shù)
av_opt_set_xxx()給SwsContext上下文設(shè)置對應(yīng)的參數(shù)
int sws_init_context(struct SwsContext *sws_context, SwsFilter *srcFilter, SwsFilter *dstFilter);
初始化SwsContext上下文
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);
進行縮放和格式轉(zhuǎn)換
ffmpeg實現(xiàn)代碼
頭文件
#ifndef videoresample_hpp
#define videoresample_hpp
#include <stdio.h>
#include <string>
#include "cppcommon/CLog.h"
extern "C" {
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
}
using namespace std;
typedef enum
{
FormatTypeYUV420P,
FormattypeRGB24
}FormatType;
class VideoScale {
public:
VideoScale();
~VideoScale();
/** sws_scale()函數(shù)主要用來進行視頻的縮放和視頻數(shù)據(jù)的格式轉(zhuǎn)換。對于縮放壹士,有不同的算法磷雇,每個算法的性能不一樣
*/
void doScale();
};
#endif /* videoresample_hpp */
實現(xiàn)文件
VideoScale::VideoScale(){
}
VideoScale::~VideoScale()
{
}
const char* nameForOptionType(AVOptionType type) {
const char *names[19] = {
"AV_OPT_TYPE_FLAGS",
"AV_OPT_TYPE_INT",
"AV_OPT_TYPE_INT64",
"AV_OPT_TYPE_DOUBLE",
"AV_OPT_TYPE_FLOAT",
"AV_OPT_TYPE_STRING",
"AV_OPT_TYPE_RATIONAL",
"AV_OPT_TYPE_BINARY",
"AV_OPT_TYPE_DICT",
"AV_OPT_TYPE_UINT64",
"AV_OPT_TYPE_CONST",
"AV_OPT_TYPE_IMAGE_SIZE",
"AV_OPT_TYPE_PIXEL_FMT",
"AV_OPT_TYPE_SAMPLE_FMT",
"AV_OPT_TYPE_VIDEO_RATE",
"AV_OPT_TYPE_DURATION",
"AV_OPT_TYPE_COLOR",
"AV_OPT_TYPE_CHANNEL_LAYOUT",
"AV_OPT_TYPE_BOOL"
};
return names[type];
}
/** 視頻像素格式存儲方式
* YUV420P:三個plane,按照 YYYY........U........V..........分別存儲于各個plane通道
* RGB24:一個plane躏救,按照RGBRGB....順序存儲在一個planne中
* NV12:兩個plane唯笙,一個存儲YYYYYY螟蒸,另一個按照UV的順序交叉存儲
* NV21:兩個plane,一個存儲YYYYYY崩掘,另一個按照VU的順序交叉存儲
*/
/** 播放yuv的ffplay 命令
* ffplay -i test_320x180_nv12.yuv -f rawvideo --pixel_format nv12 -video_size 320x180 -framerate 50
*/
/** 遇到問題:源MP4文件視頻的幀率為50fps七嫌,解碼成yuv之后用ffplay播放rawvideo速度變慢
* 原因分析:ffplay播放rawvideo的默認幀率為25fps;如上添加-framerate 50 代表以50的fps播放
*/
void VideoScale::doScale()
{
string curFile(__FILE__);
unsigned long pos = curFile.find("1-video_encode_decode");
if (pos == string::npos) {
LOGD("1-video_encode_decode file fail");
return;
}
string resourceDir = curFile.substr(0,pos)+"filesources/";
string srcyuvPath = resourceDir + "test_640x360_yuv420p.yuv";
string dstyuvPath = "test.yuv";
enum AVPixelFormat src_pix_fmt = AV_PIX_FMT_YUV420P;
enum AVPixelFormat dst_pix_fmt = AV_PIX_FMT_NV12;
int src_w = 640,src_h = 360;
int dst_w = 320,dst_h = 180;
/** 1、因為視頻的planner個數(shù)最多不超過4個苞慢,所以這里的src_data數(shù)組和linesize數(shù)組長度都是4
* 2诵原、linesize數(shù)組存儲的是各個planner的寬,對應(yīng)"視頻的寬"挽放,由于字節(jié)對齊可能會大于等于對應(yīng)"視頻的寬"
*/
uint8_t *src_buffer[4],*dst_buffer[4];
int src_linesize[4],dst_linesize[4];
const AVPixFmtDescriptor *pixfmtDest = av_pix_fmt_desc_get(src_pix_fmt);
// 一绍赛、獲取視頻轉(zhuǎn)換上下文
/**
* 方式一:
* 1、參數(shù)1 2 3:轉(zhuǎn)換源數(shù)據(jù)寬辑畦,高吗蚌,像素格式
* 2、參數(shù)4 5 6:轉(zhuǎn)換后數(shù)據(jù)寬纯出,高蚯妇,像素格式
* 3、參數(shù)7:如果進行縮放暂筝,所采用的的縮放算法
* 4箩言、參數(shù)8:轉(zhuǎn)換源數(shù)據(jù)的 SwsFilter 參數(shù)
* 5、參數(shù)9:轉(zhuǎn)換后數(shù)據(jù)的 SwsFilter 參數(shù)
* 6乖杠、參數(shù)10:轉(zhuǎn)換縮放算法的相關(guān)參數(shù) const double* 類型數(shù)組
* 7分扎、8 9 10三個參數(shù)一般傳遞NULL,即采用默認值即可
* 8胧洒、SWS_BICUBIC性能比較好畏吓,SWS_FAST_BILINEAR在性能和速度之間有一個比好好的平衡,SWS_POINT的效果比較差卫漫。
*/
SwsContext *swsCtx = NULL;
#if 1
swsCtx = sws_getContext(src_w,src_h,src_pix_fmt,
dst_w,dst_h,dst_pix_fmt,
SWS_BICUBIC,
NULL,NULL,
NULL
);
if (swsCtx == NULL) {
LOGD("sws_getContext fail");
return;
}
#else
// 方式二菲饼;此方式可以設(shè)置更多的轉(zhuǎn)換屬性,比如設(shè)置YUV的顏色取值范圍是MPEG還是JPEG
swsCtx = sws_alloc_context();
// const AVOption *opt = NULL;
// LOGD("輸出 AVOption的所有值===>");
/** 基于AVClass的結(jié)構(gòu)體列赎。
* 1宏悦、所有基于AVClass的結(jié)構(gòu)體都可以用av_opt_set_xxx()函數(shù)和av_opt_get_xxx()函數(shù)來設(shè)置和獲取結(jié)構(gòu)體對應(yīng)的屬性,注意:AVClass
* 必須是該結(jié)構(gòu)體的第一個屬性包吝。這個結(jié)構(gòu)體的每一個屬性是AVOption結(jié)構(gòu)體饼煞,包含了屬性名,屬性值等等诗越。存儲于AVClass的
* const struct AVOption *option中
* 2砖瞧、ffmpeg的常用結(jié)構(gòu)體都是基于AVClass的,比如AVCodec嚷狞,SwsContext等等块促。這些結(jié)構(gòu)體對應(yīng)屬性的屬性名在各個目錄的
* options.c中荣堰,比如SwsContext的就在libswscale目錄下的options.c文件中定義,并且有默認值
* 3竭翠、av_opt_next()可以遍歷基于AVClass的結(jié)構(gòu)體的屬性對應(yīng)的AVOption結(jié)構(gòu)體
*/
// while ((opt = av_opt_next(swsCtx, opt)) != NULL) {
// LOGD("name:%s help:%s type %s",opt->name,opt->help,nameForOptionType(opt->type));
// }
/** 遇到問題:視頻進行壓縮后變形
* 原因分析:因為下面dstw的參數(shù)寫成了src_w導(dǎo)致變形振坚,設(shè)置爭取即可
*/
av_opt_set_pixel_fmt(swsCtx,"src_format",src_pix_fmt,AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(swsCtx, "srcw", src_w, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(swsCtx,"srch",src_h,AV_OPT_SEARCH_CHILDREN);
/** 設(shè)置源的AVColorRange
* AVCOL_RANGE_JPEG:以JPEG為代表的標(biāo)準(zhǔn)中,Y斋扰、U渡八、V的取值范圍都是0-255。FFmpeg中稱之為“jpeg” 范圍褥实。
* AVCOL_RANGE_MPEG:一般用于以Rec.601為代表(還包括BT.709 / BT.2020)的廣播電視標(biāo)準(zhǔn)Y的取值范圍是16-235呀狼,
* U裂允、V的取值范圍是16-240损离。FFmpeg中稱之為“mpeg”范圍。
* 默認是MPEG范圍绝编,對于源來說 要與實際情況一致僻澎,這里源的實際范圍是MPEG范圍
*/
av_opt_set_int(swsCtx,"src_range",0,AV_OPT_SEARCH_CHILDREN);
av_opt_set_pixel_fmt(swsCtx,"dst_format",dst_pix_fmt,AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(swsCtx,"dstw",dst_w,AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(swsCtx,"dsth",dst_h,AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(swsCtx,"dst_range",1,AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(swsCtx,"sws_flags",SWS_BICUBIC,AV_OPT_SEARCH_CHILDREN);
if (sws_init_context(swsCtx, NULL, NULL) < 0) {
LOGD("sws_init_context fail");
sws_freeContext(swsCtx);
return;
}
#endif
LOGD("src_fmt %s dst_fmt %s",av_get_pix_fmt_name(src_pix_fmt),av_get_pix_fmt_name(dst_pix_fmt));
// 二、分配內(nèi)存 轉(zhuǎn)換前和轉(zhuǎn)換后的
/** av_image_alloc()返回分配內(nèi)存的大小
* 1十饥、該大小大于等于視頻數(shù)據(jù)實際占用的大小窟勃,當(dāng)最后一個參數(shù)為1時或者視頻長寬剛好滿足字節(jié)對齊時
* 兩者會相等。
* 2逗堵、linesize對應(yīng)視頻的寬秉氧,它的值會大于等于視頻的寬,在將視頻數(shù)據(jù)讀入這個內(nèi)存或者從這個內(nèi)存取出數(shù)據(jù)時都要用實際的寬的值
* 3蜒秤、該函數(shù)分配的內(nèi)存實際上一塊連續(xù)的內(nèi)存汁咏,只不過src_data數(shù)組的指針分別指向了這塊連續(xù)內(nèi)存上對應(yīng)各個planner
* 上的首地址
*/
int src_size = av_image_alloc(src_buffer,src_linesize,src_w,src_h,src_pix_fmt,16);
LOGD("size %d linesize[0] %d linesize[1] %d linesize[2] %d log2_chroma_h %d",src_size,src_linesize[0],src_linesize[1],src_linesize[2],pixfmtDest->log2_chroma_h);
if (src_size < 0) {
LOGD("src image_alloc <0 %d",src_size);
return;
}
int dst_size = av_image_alloc(dst_buffer,dst_linesize,dst_w,dst_h,dst_pix_fmt,16);
if (dst_size < 0) {
LOGD("dst_size < 0 %d",dst_size);
return;
}
// 三、進行轉(zhuǎn)換
FILE *srcFile = fopen(srcyuvPath.c_str(), "rb");
if (srcFile == NULL) {
LOGD("srcFile is NULL");
return;
}
FILE *dstFile = fopen(dstyuvPath.c_str(), "wb+");
if (dstFile == NULL) {
LOGD("dstFile is NULL");
return;
}
while (true) {
// 讀取YUV420P的方式;注意這里用的是src_w而非linesize中的值作媚;yuv中uv的寬高為實際寬高的一半
if (src_pix_fmt == AV_PIX_FMT_YUV420P) {
size_t size_y = fread(src_buffer[0],1,src_w*src_h,srcFile);
size_t size_u = fread(src_buffer[1],1,src_w/2*src_h/2,srcFile);
size_t size_v = fread(src_buffer[2],1,src_w/2*src_h/2,srcFile);
if (size_y <= 0 || size_u <= 0 || size_v <= 0) {
LOGD("size_y %d size_u %d size_v %d",size_y,size_u,size_v);
break;
}
} else if (src_pix_fmt == AV_PIX_FMT_RGB24) {
size_t size = fread(src_buffer[0],1,src_w*src_h*3,srcFile);
if (size <= 0) {
LOGD("size %d",size);
break;
}
} else if (src_pix_fmt == AV_PIX_FMT_NV12) {
size_t size_y = fread(src_buffer[0],1,src_w * src_h,srcFile);
size_t size_uv = fread(src_buffer[1],1,src_w * src_h/2,srcFile);
if (size_y <= 0 || size_uv <= 0) {
LOGD("size_y %d size_uv %d",size_y,size_uv);
break;
}
} else if (src_pix_fmt == AV_PIX_FMT_NV21) {
size_t size_y = fread(src_buffer[0],1,src_w * src_h,srcFile);
size_t size_vu = fread(src_buffer[1],1,src_w * src_h/2,srcFile);
if (size_y <= 0 || size_vu <= 0) {
LOGD("size_y %d size_vu %d",size_y,size_vu);
break;
}
}
/** 參數(shù)1:轉(zhuǎn)換上下文
* 參數(shù)2:轉(zhuǎn)換源數(shù)據(jù)內(nèi)存地址
* 參數(shù)3:轉(zhuǎn)換源的linesize數(shù)組
* 參數(shù)4:從源數(shù)據(jù)的撒位置起開始處理數(shù)據(jù)攘滩,一般是處理整個數(shù)據(jù),傳0
* 參數(shù)5:換換源數(shù)據(jù)的height(視頻的高)
* 參數(shù)6:轉(zhuǎn)換目的數(shù)據(jù)內(nèi)存地址
* 參數(shù)7:轉(zhuǎn)換目的linesize數(shù)組
* 返回:轉(zhuǎn)換后的目的數(shù)據(jù)的height(轉(zhuǎn)換后的視頻的實際高)
*/
int size = sws_scale(swsCtx,src_buffer,src_linesize,0,src_h,
dst_buffer,dst_linesize);
LOGD("size == %d",size);
if (dst_pix_fmt == AV_PIX_FMT_YUV420P) {
fwrite(dst_buffer[0],1,dst_w*dst_h,dstFile);
fwrite(dst_buffer[1],1,dst_w/2*dst_h/2,dstFile);
fwrite(dst_buffer[2],1,dst_w/2*dst_h/2,dstFile);
} else if (dst_pix_fmt == AV_PIX_FMT_RGB24) {
fwrite(dst_buffer[0],1,dst_w*dst_h*3,dstFile);
} else if (dst_pix_fmt == AV_PIX_FMT_NV12) {
fwrite(dst_buffer[0],1,dst_w*dst_h,dstFile);
fwrite(dst_buffer[1],1,dst_w*dst_h/2,dstFile);
} else if (dst_pix_fmt == AV_PIX_FMT_NV21) {
fwrite(dst_buffer[0],1,dst_w*dst_h,dstFile);
fwrite(dst_buffer[1],1,dst_w*dst_h/2,dstFile);
}
}
sws_freeContext(swsCtx);
fclose(srcFile);
fclose(dstFile);
av_freep(&src_buffer[0]);
av_freep(&dst_buffer[0]);
}
項目代碼:
示例地址
示例代碼位于cppsrc目錄下文件
videoresample.hpp
videoresample.cpp
項目下示例可運行于iOS/android/mac平臺纸泡,工程分別位于demo-ios/demo-android/demo-mac三個目錄下漂问,可根據(jù)需要選擇不同平臺