作者: 葉餘 來源: https://www.cnblogs.com/leisure_chn/p/14355017.html
libswscale 源碼分析系列文章:
[1].
FFmpeg libswscale源碼分析1-API介紹 [2]. FFmpeg libswscale源碼分析2-轉碼指令行與濾鏡圖 [3]. FFmpeg libswscale源碼分析3-scale濾鏡源碼分析 [4]. FFmpeg libswscale源碼分析4-libswscale源碼分析 源碼分析基于 FFmpeg 4.1 版本。3. scale 濾鏡源碼分析
scale 濾鏡調用 libswscale 庫來執行像素格式轉換或圖像分辨率縮放工作。閱讀 scale 濾鏡代碼,可以了解 libswscale API 的詳細用法。
3.1 scale 濾鏡對 SwsContext 的初始化
函數調用關系如下:
config_props() -->
sws_init_context() -->
ff_get_unscaled_swscale() -->
config_props() 函數:
static int config_props(AVFilterLink *outlink)
{
AVFilterContext *ctx = outlink->src;
AVFilterLink *inlink0 = outlink->src->inputs[0];
AVFilterLink *inlink = ctx->filter == &ff_vf_scale2ref ?
outlink->src->inputs[1] :
outlink->src->inputs[0];
enum AVPixelFormat outfmt = outlink->format;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
ScaleContext *scale = ctx->priv;
......
if (inlink0->w == outlink->w &&
inlink0->h == outlink->h &&
!scale->out_color_matrix &&
scale->in_range == scale->out_range &&
inlink0->format == outlink->format)
// 如果目前 scale 濾鏡的輸入和輸出一樣,此處不作任何初始化動作
;
else {
// scale->sws 用于一幀圖像,scale->isws[0] 隔行幀頂場,scale->isws[1] 用于隔行幀底場
struct SwsContext **swscs[3] = {&scale->sws, &scale->isws[0], &scale->isws[1]};
int i;
for (i = 0; i < 3; i++) {
int in_v_chr_pos = scale->in_v_chr_pos, out_v_chr_pos = scale->out_v_chr_pos;
struct SwsContext **s = swscs[i];
*s = sws_alloc_context();
if (!*s)
return AVERROR(ENOMEM);
// 将 ffmpeg 指令行中傳入的參數(指令行未給出的參數取預設值)設定到 SwsContext
av_opt_set_int(*s, "srcw", inlink0 ->w, 0);
av_opt_set_int(*s, "srch", inlink0 ->h >> !!i, 0);
av_opt_set_int(*s, "src_format", inlink0->format, 0);
av_opt_set_int(*s, "dstw", outlink->w, 0);
av_opt_set_int(*s, "dsth", outlink->h >> !!i, 0);
av_opt_set_int(*s, "dst_format", outfmt, 0);
av_opt_set_int(*s, "sws_flags", scale->flags, 0);
av_opt_set_int(*s, "param0", scale->param[0], 0);
av_opt_set_int(*s, "param1", scale->param[1], 0);
if (scale->in_range != AVCOL_RANGE_UNSPECIFIED)
av_opt_set_int(*s, "src_range",
scale->in_range == AVCOL_RANGE_JPEG, 0);
if (scale->out_range != AVCOL_RANGE_UNSPECIFIED)
av_opt_set_int(*s, "dst_range",
scale->out_range == AVCOL_RANGE_JPEG, 0);
if (scale->opts) {
AVDictionaryEntry *e = NULL;
while ((e = av_dict_get(scale->opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
if ((ret = av_opt_set(*s, e->key, e->value, 0)) < 0)
return ret;
}
}
/* Override yuv420p default settings to have the correct (MPEG-2) chroma positions
* MPEG-2 chroma positions are used by convention
* XXX: support other 4:2:0 pixel formats */
if (inlink0->format == AV_PIX_FMT_yuv420p && scale->in_v_chr_pos == -513) {
in_v_chr_pos = (i == 0) ? 128 : (i == 1) ? 64 : 192;
}
if (outlink->format == AV_PIX_FMT_yuv420p && scale->out_v_chr_pos == -513) {
out_v_chr_pos = (i == 0) ? 128 : (i == 1) ? 64 : 192;
}
av_opt_set_int(*s, "src_h_chr_pos", scale->in_h_chr_pos, 0);
av_opt_set_int(*s, "src_v_chr_pos", in_v_chr_pos, 0);
av_opt_set_int(*s, "dst_h_chr_pos", scale->out_h_chr_pos, 0);
av_opt_set_int(*s, "dst_v_chr_pos", out_v_chr_pos, 0);
// 調用初始化函數 sws_init_context()
if ((ret = sws_init_context(*s, NULL, NULL)) < 0)
return ret;
if (!scale->interlaced) // 未啟用隔行标志,則不處理 scale->isws[0] 和 scale->isws[1]
break;
}
}
......
return 0;
fail:
return ret;
}
3.2 scale 濾鏡調用 sws_scale 函數
隻看 scale 濾鏡中對視訊幀進行縮放或格式轉換的實作邏輯。
scale 濾鏡的 filter_frame() 函數如下:
static int filter_frame(AVFilterLink *link, AVFrame *in)
{
ScaleContext *scale = link->dst->priv;
AVFilterLink *outlink = link->dst->outputs[0];
AVFrame *out;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(link->format);
......
// 1. 色度子采樣因子
// log2_chroma_w 指由一行亮度樣本數(luma width)右移多少位得到一行色度樣本數(chroma width)
// log2_chroma_h 指由亮度樣本行數(luma height)右移多少位得到色度樣本行數(chroma height)
// 以 YUV410P像素格式為例,
// 水準方向子采樣因子為 1/4,則 scale->hsub = desc->log2_chroma_w = 2
// 垂直方向子采樣因子為 1/2,則 scale->vsub = desc->log2_chroma_h = 1
scale->hsub = desc->log2_chroma_w; // 水準方向
scale->vsub = desc->log2_chroma_h;
......
// 2. 拷貝幀屬性
av_frame_copy_props(out, in);
......
// 3. 調用 scale_slice() 函數執行轉換,分三種情況:
if(scale->interlaced>0 || (scale->interlaced<0 && in->interlaced_frame)){
// 3.1 scale->interlaced 的值由 scale 濾鏡的 interl 參數确定,有三個值:
// 1: 使能隔行縮放方式
// 0:禁用隔行縮放方式
// -1: 根據源幀中的隔行/逐行标志決定是使用隔行縮放還是逐行縮放
// 此處 if 第一個分支,即進行隔行縮放
scale_slice(link, out, in, scale->isws[0], 0, (link->h+1)/2, 2, 0);
scale_slice(link, out, in, scale->isws[1], 0, link->h /2, 2, 1);
}else if (scale->nb_slices) {
// 3.2 此處 if 的第二個分支,是逐行縮放,一個圖像幀有多個 slice 的情況
int i, slice_h, slice_start, slice_end = 0;
const int nb_slices = FFMIN(scale->nb_slices, link->h);
for (i = 0; i < nb_slices; i++) {
slice_start = slice_end;
slice_end = (link->h * (i+1)) / nb_slices;
slice_h = slice_end - slice_start;
scale_slice(link, out, in, scale->sws, slice_start, slice_h, 1, 0);
}
}else{
// 3.3 此處 if 第三個分支,是逐行縮放,一個圖像幀隻有一個 slice 的情況
scale_slice(link, out, in, scale->sws, 0, link->h, 1, 0);
}
return ff_filter_frame(outlink, out);
}
scale_slice() 是對一個 slice 執行縮放操作,最終會調用 sws_scale() 函數。可以在轉碼指令行中,将 scale 濾鏡的 nb_slices 選項參數設定為大于 1,在 scale_slice() 函數中打斷點調試,觀察各參數及變量的值。
static int scale_slice(AVFilterLink *link, AVFrame *out_buf, AVFrame *cur_pic, struct SwsContext *sws, int y, int h, int mul, int field)
{
ScaleContext *scale = link->dst->priv;
const uint8_t *in[4];
uint8_t *out[4];
int in_stride[4],out_stride[4];
int i;
for(i=0; i<4; i++){
int vsub= ((i+1)&2) ? scale->vsub : 0;
in_stride[i] = cur_pic->linesize[i] * mul;
out_stride[i] = out_buf->linesize[i] * mul;
in[i] = cur_pic->data[i] + ((y>>vsub)+field) * cur_pic->linesize[i];
out[i] = out_buf->data[i] + field * out_buf->linesize[i];
}
if(scale->input_is_pal)
in[1] = cur_pic->data[1];
if(scale->output_is_pal)
out[1] = out_buf->data[1];
return sws_scale(sws, in, in_stride, y/mul, h,
out,out_stride);
}
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。