您的当前位置:首页正文

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

来源:要发发知识网

单输入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");
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波形对比


修改音量前 修改音量后

可以明显看出音量已经发生变化。
在播放音频时,可以听见有一些噪声,需要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");
                }