FFmpeg libswscale源码分析3-scale滤镜源码分析

简介: 源码分析基于 FFmpeg 4.1 版本。

作者:叶余

来源: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);
}


「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

阿里云视频云@凡科快图.png

相关文章
|
编解码 API 数据安全/隐私保护
FFmpeg中overlay滤镜用法-水印及画中画
overlay 技术又称视频叠加技术。overlay 视频技术使用非常广泛,常见的例子有,电视屏幕右上角显示的电视台台标,以及画中画功能。画中画是指在一个大的视频播放窗口中还存在一个小播放窗口,两个窗口不同的视频内容同时播放。
2023 0
FFmpeg中overlay滤镜用法-水印及画中画
|
编解码 并行计算 计算机视觉
FFmpeg libswscale源码分析2-转码命令行与滤镜图
libswscale 是 FFmpeg 中完成图像尺寸缩放和像素格式转换的库。
190 0
FFmpeg libswscale源码分析2-转码命令行与滤镜图
|
存储 编解码 算法
FFmpeg libswscale源码分析1-API介绍
libswscale 是 FFmpeg 中完成图像尺寸缩放和像素格式转换的库。用户可以编写程序,调用 libswscale 提供的 API 来进行图像尺寸缩放和像素格式转换。也可以使用 scale 滤镜完成这些功能,scale 滤镜实现中调用了 libswscale 的 API。libswscale 的 API 非常简单,就一个 sws_scale() 接口,但内部的实现却非常复杂。
373 0
FFmpeg libswscale源码分析1-API介绍
|
编解码
LiveVideoStackCon讲师热身分享 ( 八 ) —— FFmpeg的滤镜在视频编辑场景中的应用
LiveVideoStackCon 2018音视频技术大会是每年的多媒体技术人的盛宴,为了让参会者与大会讲师更多互动交流,我们推出了LiveVideoStackCon讲师热身分享第一季,在每周四晚19:30,邀请1名大会讲师进行线上分享技术干货,解答热点问题。
1430 0
|
数据安全/隐私保护 编解码
FFMPEG 最简滤镜filter使用实例(实现视频缩放,裁剪,水印等)
FFMPEG官网给出了FFMPEG 滤镜使用的实例,它是将视频中的像素点替换成字符,然后从终端输出。我在该实例的基础上稍微的做了修改,使它能够保存滤镜处理过后的文件。在上代码之前先明白几个概念:     Filter:代表单个filter     FilterPad:代表一个filter的输入或...
2735 0
|
Linux C语言
FFmpeg &#39;scale&#39; filter not present, cannot convert pixel formats.
/*************************************************************************** * FFmpeg 'scale' filter not present, cannot convert pixel formats. * 说明: * 使用FFmpeg的过程中遇到这个问题,记录一下解决办法。
1914 0
|
18天前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
80 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
25天前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
45 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
1月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
117 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
1月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
60 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势

热门文章

最新文章