zoukankan      html  css  js  c++  java
  • FFmpeg libswscale源码分析3-scale滤镜源码分析

    本文为作者原创,转载请注明出处: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);
    }
    
  • 相关阅读:
    mysql数据库管理工具(navicat for mysql)
    一次测试岗位针对Java和接口的面试题
    接口测试 rest-assured 使用指南
    简单实现接口自动化测试(基于python+unittest)
    负载测试、压力测试和性能测试的异同
    【Excle数据透视表】如何新建数据透视表样式
    【Excle数据透视表】如何为数据透视表应用样式
    【Linux】Linux删除指定文件夹下面 名称不包含指定字符的文件
    【Excle数据透视表】如何水平并排显示报表筛选区域的字段
    【Excle数据透视表】如何在数据透视表中使用合并单元格标志
  • 原文地址:https://www.cnblogs.com/leisure_chn/p/14355017.html
Copyright © 2011-2022 走看看