音频特效滤镜 via FFmpeg Filter

音频特效定义

音效或声效(Sound effects 或 Audio effects)是人工制造或加强的声音,用来增强对电影、电子游戏、音乐或其他媒体的艺术或其他内容的声音处理。

常见的音效技术有:回声、合唱、均衡(EQ)、过滤、变调、移相、压缩 / 拉伸、3D、调制和共鸣等等。

FFmpeg filter

FFmpeg 中的 libavfilter 提供了一整套的基于 filter 的机制。Filter 本身是一个插件的形式,可以快速的组装出需要的效果。
比如下面的 filter,可以实现音频的回声效果。

ffplay.exe sample.mp3 -af aecho=0.8:0.9:1000:0.3

FFmpeg 中 filter 分为:

  • source filter (只有输出)
  • audio filter
  • video filter
  • multimedia filter
  • sink filter (只有输入)

这里 演示了一些非常精美的 filter。

FFmpeg filter graph

FFmpeg 将 filtergraph 分为 simple filtergraph 和 complex filtergraph。

  • Simple filtergraph 通常只有一个输入和输出,FFmpeg 命令行中使用 -vf、-af 识别,基本原理图如下:
    simple filter graph
  • Complex filtergraph 通常是具有多个输入输出文件,并有多条执行路径,FFmpeg 命令行中使用 -lavfi、-filter_complex 识别,基本原理图如下:
    complex filter graph

Filter graph 的语法

FFmpeg 中 filter 包含三个层次,filter → filterchain → filtergraph。

Filter 是 libavfilter 提供的基础单元。在同一个 filterchain 中的 filter 使用逗号分隔,在不同 filterchain 中的 filter 使用分号隔开,比如下面的例子:

ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT

Filtergraph 将输入流分成两个流,其中一个通过 crop filter 和 vflip filter,然后通过 overlay filter 将这个流和原始输入流合成一个流输出。如果 filter 需要输入参数,多个参数使用冒号分割。
demo filter graph
这里 crop、vflip 处于同一个 filterchain,split、overlay 位于另一个 filterchain。二者通过命名的 label 实现连接(中括号中的即是 label 的名字)。在上例中 split filter 有两个输出,依次命名为 [main] 和 [tmp];[tmp] 作为 crop filter 的输入,之后通过 vflip filter 输出 [flip];overlay 的输入是 [main] 和 [flilp]。

av_filter_base 类

用 FFmpeg 的 filter 开发时主要区别就是把不同的 filter description string 传到 filtergraph 中,然后设定 sink 端 prefer 的参数,其他部分几乎是一样的,所以我把 filter 的使用封装成了一个 av_filter_base 类:

typedef int (*pf_filter_callback)(AVFrame *frame);

class av_filter_base
{
public:
    av_filter_base();
    virtual ~av_filter_base();
    int init(AVCodecContext *dec_ctx, AVRational stream_time_base, const char *filters_descr);
    int do_filter(AVFrame* in_frame, pf_filter_callback callback_proc);
    int do_filter(AVFrame* in_frame, std::queue<AVFrame*>& out_frames);
    void finalize();
    
protected:
    virtual int init_opts(); // to override this function in sub class
    
private:
    int audio_filter_init_begin(AVCodecContext *dec_ctx, AVRational stream_time_base);
    int video_filter_init_begin(AVCodecContext *dec_ctx, AVRational stream_time_base);
    int filter_init_end(const char *filters_descr);
    
protected:
    AVFilterContext *m_buffersink_ctx;
    AVFilterContext *m_buffersrc_ctx;
    AVFilterGraph *m_filter_graph;
    AVFrame *m_filt_frame;
};

av_filter_base::init 函数

基类的 init 函数使用了设计模式中的模板方法,子类需要实现 init_opts() 虚函数对 sink 端设置 prefer 参数。

// template method design pattern  
int init(AVCodecContext *dec_ctx, AVRational stream_time_base, const char *filters_descr)
{
   int hr = -1;
   
   if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
       hr = audio_filter_init_begin(dec_ctx, stream_time_base);
   else
       hr = video_filter_init_begin(dec_ctx, stream_time_base);
   RETURN_IF_FAILED(hr);

   hr = init_opts();
   RETURN_IF_FAILED(hr);

   hr = filter_init_end(filters_descr);
   RETURN_IF_FAILED(hr);

   return 0;
}

av_filter_base::audio_filter_init_begin 函数

创建 audio source 和 sink filter。

int audio_filter_init_begin(AVCodecContext *dec_ctx, AVRational stream_time_base)
{
    int hr = -1;
    m_filter_graph = avfilter_graph_alloc();
    RETURN_IF_NULL(m_filter_graph);
    
    if (!dec_ctx->channel_layout)
        dec_ctx->channel_layout = av_get_default_channel_layout(dec_ctx->channels);
        
    char args[512] = {0};
    _snprintf_s(args, sizeof(args),
        "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64,
        stream_time_base.num, stream_time_base.den, dec_ctx->sample_rate,
        av_get_sample_fmt_name(dec_ctx->sample_fmt), dec_ctx->channel_layout);
        
    /* buffer audio source: the decoded frames from the decoder will be inserted here. */
    const AVFilter *abuffersrc  = avfilter_get_by_name("abuffer");
    hr = avfilter_graph_create_filter( &m_buffersrc_ctx, abuffersrc, "in", args, NULL, m_filter_graph );
    RETURN_IF_FAILED(hr);
    
    /* buffer audio sink: to terminate the filter chain. */
    const AVFilter *abuffersink = avfilter_get_by_name("abuffersink");
    hr = avfilter_graph_create_filter( &m_buffersink_ctx, abuffersink, "out", NULL, NULL, m_filter_graph );
    RETURN_IF_FAILED(hr);
    
    return hr;
}

Audio_filter 子类

init_opts 函数需要子类去重写,下面的例子设定了 sink filter 支持的参数(格式、声道和采样率)范围。

class Audio_filter : public av_filter_base
{
protected:
    virtual int init_opts()
    {
        int hr = -1;
        const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE };
        const int64_t out_channel_layouts[] = { AV_CH_LAYOUT_MONO, AV_CH_LAYOUT_STEREO, -1 };
        const int out_sample_rates[] = { 16000, 22050, 44100, 48000, -1 };
      
        hr = av_opt_set_int_list(m_buffersink_ctx, "sample_fmts", out_sample_fmts, -1, AV_OPT_SEARCH_CHILDREN);
        RETURN_IF_FAILED(hr);
      
        hr = av_opt_set_int_list(m_buffersink_ctx, "channel_layouts", out_channel_layouts, -1, AV_OPT_SEARCH_CHILDREN);
        RETURN_IF_FAILED(hr);
      
        hr = av_opt_set_int_list(m_buffersink_ctx, "sample_rates", out_sample_rates, -1, AV_OPT_SEARCH_CHILDREN);
        RETURN_IF_FAILED(hr);
      
        return 0;
    }
};

av_filter_base::filter_init_end 函数

这里完成了 filter graph 的构建(src → filter(s) → sink)。没看懂?拿去用就行了~

int filter_init_end(const char *filters_descr)
{
    int hr = -1;
    
    /* The buffer source output must be connected to the input pad of the first filter described by 
     * filters_descr; since the first filter input label is not specified, it is set to "in" by default. */
    AVFilterInOut *outputs = avfilter_inout_alloc();
    outputs->name       = av_strdup("in");
    outputs->filter_ctx = m_buffersrc_ctx;
    outputs->pad_idx    = 0;
    outputs->next       = NULL;
    
    /* The buffer sink input must be connected to the output pad of the last filter described by 
     * filters_descr; since the last filter output label is not specified, it is set to "out" by default. */
    AVFilterInOut *inputs  = avfilter_inout_alloc();
    inputs->name        = av_strdup("out");
    inputs->filter_ctx  = m_buffersink_ctx;
    inputs->pad_idx     = 0;
    inputs->next        = NULL;
    
    hr = avfilter_graph_parse_ptr(m_filter_graph, filters_descr, &inputs, &outputs, NULL);
    hr = avfilter_graph_config(m_filter_graph, NULL);
    
    m_filt_frame = av_frame_alloc();
    
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);
    return hr;
}

av_filter_base::do_filter 函数

一进 N 出(N >= 0),新的 FFmpeg API 似乎都这样,注意判断 API 的返回值(AVERROR(EAGAIN) ,AVERROR_EOF)即可。剩下的都丢给回调函数。

typedef int (*pf_filter_callback)(AVFrame *frame);

int do_filter(AVFrame* in_frame, pf_filter_callback callback_proc)
{
    RETURN_IF_NULL(in_frame);
    int hr = -1;

    /* push the decoded frame into the filtergraph */
    hr = av_buffersrc_add_frame_flags( m_buffersrc_ctx, in_frame, AV_BUFFERSRC_FLAG_KEEP_REF );
    RETURN_IF_FAILED(hr);

    /* pull filtered frame from the filtergraph */
    while (true) {
        hr = av_buffersink_get_frame( m_buffersink_ctx, m_filt_frame );
        if (hr == AVERROR(EAGAIN) || hr == AVERROR_EOF)
            break;
        RETURN_IF_FAILED(hr);

        if (NULL != callback_proc)
            callback_proc(m_filt_frame);
        av_frame_unref(m_filt_frame);
    }

    return hr;
}

结语

本文讲解的是如何使用 FFmpeg 的 filter,而不是如何写一个 filter,那么可以自己写一个标准的 filter 吗?答案是肯定的,BUT,要自己把 filter 集成到 libavfilter 中去,或者写信给 FFmpeg 组织请求他们把你的 filter 集成到官方的 libavfilter 中去。So,还是踏实点写个算法函数吧 (╯-╰)。

其他框架的滤镜

  • 关于 DirectShow 的音频滤镜请参考 这里
  • 关于 Media Foundation 的音频滤镜请参考 这里

Blueware
EOF

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OBS(Open Broadcaster Software)是一款功能强大的开源直播和录制软件,可以帮助用户实现多种实时流媒体应用。而FFmpeg则是一个跨平台的开源多媒体框架,可以处理音频和视频的编解码、转码等操作。 通过使用FFmpeg,我们可以将OBS的直播或录制的内容进行进一步的处理和编辑。比如,可以使用FFmpeg对OBS输出的视频和音频进行格式转换、剪辑、压缩等操作。对于需要直播的内容,可以使用FFmpeg进行实时流媒体的编码和推流,以便在各个平台上进行直播。 在OBS中,可以通过添加一个自定义输出,选择使用FFmpeg,然后配置好相关的参数,就可以在直播结束后,将OBS的直播内容保存为本地视频文件。这样,我们就可以使用FFmpeg对这些视频文件进行进一步的处理,比如提取音频、添加水印、调整分辨率等。 另外,OBS也支持使用插件来扩展其功能,其中就包括一些与FFmpeg相关的插件。通过安装和配置这些插件,我们可以在OBS中使用FFmpeg的更多功能,比如使用FFmpeg进行硬件加速的视频编码、使用FFmpeg进行画面抓取等。 总的来说,通过OBS和FFmpeg的结合,可以实现更多复杂的直播和录制需求,并且可以对直播和录制的内容进行更加灵活和精确的处理。无论是对于个人还是企业用户来说,这种结合都可以帮助他们更好地创建、编辑和分享他们的多媒体内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值