FFmpeg音频播放器(5)-单输入filter(volume,atempo)

FFmpeg音频播放器(1)-简介
FFmpeg音频播放器(2)-编译动态库
FFmpeg音频播放器(3)-将FFmpeg加入到Android中
FFmpeg音频播放器(4)-将mp3解码成pcm
FFmpeg音频播放器(5)-单输入filter(volume,atempo)
FFmpeg音频播放器(6)-多输入filter(amix)
FFmpeg音频播放器(7)-使用OpenSLES播放音频
FFmpeg音频播放器(8)-创建FFmpeg播放器
FFmpeg音频播放器(9)-播放控制
FFmpeg另一个强大之处在于它实现了各式各样的filter,可以将音视频出来成不同的效果,视频可以裁剪、缩放、旋转、合并、添加水印等效果,音频可以去噪、回声、延迟、混音、变速等效果。一个filter的输出可以作为另一个filter的输入。通过filter组合使用,我们可以定制自己想要的音视频特效。此次分两节讲两种音频filter的api用法,一种是单个输入volume(音量调节),atempo(变速),另外是多输入filter(在下一节讲到)。

单输入filter流程

解码出AVFrame -> abuffer-> 其他过滤器(volume)...->aformat->abuffersink->过滤后的AVFrame

这里看到有三个通用的过滤器,abuffer,aformat,abuffersink。
abuffer用于接收输入frame,形成待处理的数据缓存,abuffersink用于传出输出Frame,aformat过滤器约束最终的输出格式(采样率,声道数,存储位数等),这三个不可缺少。
而中间的其他过滤器可以串联多个filter,如volume,atempo

初始化过滤器

这里我们先要知道三个重要的结构体
AVFilterGraph (管理所有的filter)
AVFilterContext (filter上下文)
AVFilter(具体过滤器)
具体步骤
1.注册所有filter

avfilter_register_all();

2.创建AVFilterGraph,分配内存

AVFilterGraph *graph = avfilter_graph_alloc();

3.获取过滤器,初始化上下文

AVFilter *abuffer = avfilter_get_by_name("abuffer");
AVFilterContext *abuffer_ctx = avfilter_graph_alloc_filter(graph, abuffer, "src");

4.设置参数

avfilter_init_str(abuffer_ctx,"sample_rate=48000:sample_fmt=s16p:channel_layout=stereo");

这里参数设置还可以通过av_dict_set设置具体某个变量,参数的说明可以看官网文档#abuffer
5.根据3.4步骤依次初始化volume,aformat,abuffersink过滤器以及上下文
6.按照处理顺序依次链接过滤器上下文

avfilter_link(abuffer_ctx, 0, volume_ctx, 0);
avfilter_link(volume_ctx, 0, aformat_ctx, 0);
avfilter_link(aformat_ctx, 0, sink_ctx, 0);

7.配置AVFilterGraph

avfilter_graph_config(graph, NULL);

具体代码如下

int init_volume_filter(AVFilterGraph **pGraph, AVFilterContext **src, AVFilterContext **out,
                       char *value) {

    //初始化AVFilterGraph
    AVFilterGraph *graph = avfilter_graph_alloc();
    //获取abuffer用于接收输入端
    AVFilter *abuffer = avfilter_get_by_name("abuffer");
    AVFilterContext *abuffer_ctx = avfilter_graph_alloc_filter(graph, abuffer, "src");
    //设置参数,这里需要匹配原始音频采样率、数据格式(位数)
    if (avfilter_init_str(abuffer_ctx, "sample_rate=48000:sample_fmt=s16p:channel_layout=stereo") <
        0) {
        LOGE("error init abuffer filter");
        return -1;
    }
    //初始化volume filter
    AVFilter *volume = avfilter_get_by_name("volume");
    AVFilterContext *volume_ctx = avfilter_graph_alloc_filter(graph, volume, "volume");
    //这里采用av_dict_set设置参数
    AVDictionary *args = NULL;
    av_dict_set(&args, "volume", value, 0);//这里传入外部参数,可以动态修改
    if (avfilter_init_dict(volume_ctx, &args) < 0) {
        LOGE("error init volume filter");
        return -1;
    }

    AVFilter *aformat = avfilter_get_by_name("aformat");
    AVFilterContext *aformat_ctx = avfilter_graph_alloc_filter(graph, aformat, "aformat");
    if (avfilter_init_str(aformat_ctx,
                          "sample_rates=48000:sample_fmts=s16p:channel_layouts=stereo") < 0) {
        LOGE("error init aformat filter");
        return -1;
    }
    //初始化sink用于输出
    AVFilter *sink = avfilter_get_by_name("abuffersink");
    AVFilterContext *sink_ctx = avfilter_graph_alloc_filter(graph, sink, "sink");
    if (avfilter_init_str(sink_ctx, NULL) < 0) {//无需参数
        LOGE("error init sink filter");
        return -1;
    }
    //链接各个filter上下文
    if (avfilter_link(abuffer_ctx, 0, volume_ctx, 0) != 0) {
        LOGE("error link to volume filter");
        return -1;
    }
    if (avfilter_link(volume_ctx, 0, aformat_ctx, 0) != 0) {
        LOGE("error link to aformat filter");
        return -1;
    }
    if (avfilter_link(aformat_ctx, 0, sink_ctx, 0) != 0) {
        LOGE("error link to sink filter");
        return -1;
    }
    if (avfilter_graph_config(graph, NULL) < 0) {
        LOGI("error config filter graph");
        return -1;
    }
    *pGraph = graph;
    *src = abuffer_ctx;
    *out = sink_ctx;
    LOGI("init filter success...");
    return 0;
}

这里通过传参数value,来动态修改音量

使用过滤器

使用方法很简单,将解码后的AVFrame通过av_buffersrc_add_frame(abuffer_ctx,frame)加入到输入过滤器上下文abuffer_ctx中,通过av_buffersink_get_frame(sink_ctx,frame)获取处理完成的frame。
代码如下

    AVFilterGraph *graph;
    AVFilterContext *in_ctx;
    AVFilterContext *out_ctx;
    //注册所有过滤器
    avfilter_register_all();
    init_volume_filter(&graph, &in_ctx, &out_ctx, "0.5");
    //初始化
    while (av_read_frame(fmt_ctx, packet) == 0) {//将音频数据读入packet
        if (packet->stream_index == audio_stream_index) {//取音频索引packet
           ... 解码音频
            if (got_frame > 0) {
                LOGI("decode frame:%d", index++);
               if (index == 1000) {//模拟动态修改音量
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "0.01");
                }
                if (index == 2000) {
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "1.0");
                }
                if (index == 3000) {
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "0.01");
                }
                if (index == 4000) {
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "1.0");
                }
                if (av_buffersrc_add_frame(in_ctx, frame) < 0) {//将frame放入输入filter上下文
                    LOGE("error add frame");
                    break;
                }
                while (av_buffersink_get_frame(out_ctx, frame) >= 0) {//从输出filter上下文中获取frame
                    fwrite(frame->data[0], 1, static_cast<size_t>(frame->linesize[0]),
                           out_file); //想将单个声道pcm数据写入文件
                }
            }
        }
    }

最终解码出来pcm和原始mp3波形对比


3005998-3cc3609ca7e3ffb9.png
修改音量前
3005998-cac10381c2f3c2a5.png
修改音量后

可以明显看出音量已经发生变化。
在播放音频时,可以听见有一些噪声,需要swr_convert来重新采样,取出完整的pcm数据。

    //初始化SwrContext
    SwrContext *swr_ctx = swr_alloc();
    enum AVSampleFormat in_sample = codec_ctx->sample_fmt;
    enum AVSampleFormat out_sample = AV_SAMPLE_FMT_S16;
    int inSampleRate = codec_ctx->sample_rate;
    int outSampleRate = inSampleRate;
    uint64_t in_ch_layout = codec_ctx->channel_layout;
    uint64_t outChannelLayout = AV_CH_LAYOUT_STEREO;
    swr_alloc_set_opts(swr_ctx, outChannelLayout, out_sample, outSampleRate, in_ch_layout, in_sample,
                       inSampleRate, 0, NULL);
    swr_init(swr_ctx);
    int out_ch_layout_nb = av_get_channel_layout_nb_channels(out_ch_layout);//声道个数
    uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_SIZE);//重采样数据

写入pcm数据之前,用swr_convert重新采样一下

 while (av_buffersink_get_frame(out_ctx, frame) >= 0) {//从输出filter上下文中获取frame
//                    fwrite(frame->data[0], 1, static_cast<size_t>(frame->linesize[0]),
//                           out_file); //想将单个声道pcm数据写入文件
  swr_convert(swr_ctx, &out_buffer, MAX_AUDIO_SIZE,
                                (const uint8_t **) frame->data, frame->nb_samples);
  int out_size = av_samples_get_buffer_size(NULL,out_ch_layout_nb,frame->nb_samples,out_sample_fmt,0);
                    fwrite(out_buffer,1,out_size,out_file);
}

这次我们写入的是完整2个声道的数据,而且也没有噪声了。

使用变速过滤器atempo给音频变速

将volumefilter改成atempo,注意参数设置名为tempo(没有a,不知道为什么)

//初始化volume filter
    AVFilter *volume = avfilter_get_by_name("atempo");
    AVFilterContext *volume_ctx = avfilter_graph_alloc_filter(graph, volume, "atempo");
    //这里采用av_dict_set设置参数
    AVDictionary *args = NULL;
    av_dict_set(&args, "tempo", value, 0);//调节音量为原先的一半
    if (avfilter_init_dict(volume_ctx, &args) < 0) {
        LOGE("error init volume filter");
        return -1;
    }

解码时模拟动态修改速度改成如下

               if (index == 1000) {//模拟动态修改音量
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "1.0");
                }
                if (index == 2000) {
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "0.8");
                }
                if (index == 3000) {
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "1.5");
                }
                if (index == 4000) {
                    init_volume_filter(&graph, &in_ctx, &out_ctx, "2.0");
                }

成功后,就可以一个不同速度的音频,使用Audition打开,选择48000,2通道播放,可以听出它先是按照0.5,1.0,0.8,1.5,2.0的播放的,而且音调保持不变,没有因为速度的改变而变高或者变低。
项目地址

猜你喜欢

转载自blog.csdn.net/weixin_34019144/article/details/87161265