• 如何使用libavfilter库给pcm音频采样数据添加音频滤镜?


    一.初始化音频滤镜

      初始化音频滤镜的方法基本上和初始化视频滤镜的方法相同,不懂的可以看上篇博客,这里直接给出代码:

    复制代码
    //audio_filter_core.cpp
    #define INPUT_SAMPLERATE 44100
    #define INPUT_FORMAT AV_SAMPLE_FMT_FLTP
    #define INPUT_CHANNEL_LAYOUT AV_CH_LAYOUT_STEREO
    static AVFilterGraph *filter_graph;
    static AVFilterContext *abuffersrc_ctx;
    static AVFilterContext *volume_ctx;
    static AVFilterContext *aformat_ctx;
    static AVFilterContext *abuffersink_ctx;
    static AVFrame *input_frame= nullptr,*output_frame= nullptr;
    int32_t init_audio_filter(const char *volume_factor){
        int32_t result=0;
        char ch_layout[64];
        char options_str[1024];
        AVDictionary *options_dict= nullptr;
        //创建滤镜图
        filter_graph=avfilter_graph_alloc();
        if(!filter_graph){
            cerr<<"Error:Unable to create filter graph."<<endl;
            return -1;
        }
        //创建abuffer滤镜
        const AVFilter *abuffer= avfilter_get_by_name("abuffer");
        if(!abuffer){
            cerr<<"Error:Could not find abuffer filter."<<endl;
            return -1;
        }
        abuffersrc_ctx= avfilter_graph_alloc_filter(filter_graph,abuffer,"src");
        if(!abuffersrc_ctx){
            cerr<<"Error:could not allocate the abuffer instance."<<endl;
            return -1;
        }
        av_get_channel_layout_string(ch_layout,sizeof(ch_layout),0,INPUT_CHANNEL_LAYOUT);
        av_opt_set(abuffersrc_ctx,"channel_layout",ch_layout,AV_OPT_SEARCH_CHILDREN);
        av_opt_set(abuffersrc_ctx,"sample_fmt",av_get_sample_fmt_name(INPUT_FORMAT),AV_OPT_SEARCH_CHILDREN);
        av_opt_set_q(abuffersrc_ctx,"time_base",(AVRational){1,INPUT_SAMPLERATE},AV_OPT_SEARCH_CHILDREN);
        av_opt_set_int(abuffersrc_ctx,"sample_rate",INPUT_SAMPLERATE,AV_OPT_SEARCH_CHILDREN);
        result= avfilter_init_str(abuffersrc_ctx, nullptr);
        if(result<0){
            cerr<<"Error:could not initialize the abuffer filter."<<endl;
            return -1;
        }
        //创建volume滤镜
        const AVFilter *volume= avfilter_get_by_name("volume");
        if(!volume){
            cerr<<"Error:could not find volume filter."<<endl;
            return -1;
        }
        volume_ctx= avfilter_graph_alloc_filter(filter_graph,volume,"volume");
        if(!volume_ctx){
            cerr<<"Error:could not allocate volume filter instance."<<endl;
            return -1;
        }
        av_dict_set(&options_dict,"volume",volume_factor,0);
        result= avfilter_init_dict(volume_ctx,&options_dict);
        av_dict_free(&options_dict);
        if(result<0){
            cerr<<"Error:could not initialize volume filter instance."<<endl;
            return -1;
        }
        //创建aformat滤镜
        const AVFilter *aformat=avfilter_get_by_name("aformat");
        if(!aformat){
            cerr<<"Error:could not find aformat filter."<<endl;
            return -1;
        }
        aformat_ctx= avfilter_graph_alloc_filter(filter_graph,aformat,"aformat");
        if(!aformat_ctx){
            cerr<<"Error:could not allocate aformat filter instance."<<endl;
            return -1;
        }
        snprintf(options_str,sizeof(options_str),"sample_fmts=%s:sample_rates=%d:channel_layouts=stereo",av_get_sample_fmt_name(AV_SAMPLE_FMT_S16),44100);
        result= avfilter_init_str(aformat_ctx,options_str);
        if(result<0){
            cerr<<"Error:could not initialize aformat filter."<<endl;
            return -1;
        }
        //创建abuffersink滤镜
        const AVFilter *abuffersink= avfilter_get_by_name("abuffersink");
        if(!abuffersink){
            cerr<<"Error:could not find abuffersink filter."<<endl;
            return -1;
        }
        abuffersink_ctx= avfilter_graph_alloc_filter(filter_graph,abuffersink,"sink");
        if(!abuffersink_ctx){
            cerr<<"Error:could not allocate abuffersink filter instance."<<endl;
            return -1;
        }
        result= avfilter_init_str(abuffersink_ctx, nullptr);
        if(result<0){
            cerr<<"Error:could not initialize abuffersink filter."<<endl;
            return -1;
        }
        //连接创建好的滤镜
        result=avfilter_link(abuffersrc_ctx,0,volume_ctx,0);
        if(result>=0){
            result=avfilter_link(volume_ctx,0,aformat_ctx,0);
        }
        if(result>=0){
            result=avfilter_link(aformat_ctx,0,abuffersink_ctx,0);
        }
        if(result<0){
            fprintf(stderr,"Error connecting filters\n");
            return -1;
        }
        //配置滤镜图
        result=avfilter_graph_config(filter_graph, nullptr);
        if(result<0){
            cerr<<"Error:Error configuring the filter graph."<<endl;
            return -1;
        }
        //创建输入输出帧
        input_frame=av_frame_alloc();
        output_frame=av_frame_alloc();
        if(!input_frame||!output_frame){
            cerr<<"Error:frame allocation failed."<<endl;
            return -1;
        }
        return 0;
    }
    复制代码

    二.初始化输入音频帧

      在这一步需要给输入音频帧设置一些参数,包括采样率,采样点个数,声道布局,音频帧格式等,然后就可以给音频帧分配内存空间了。代码如下:

    复制代码
    //audio_filter_core.cpp
    static int32_t init_frames(){
        int result=0;
        input_frame->sample_rate=44100;
        input_frame->format=AV_SAMPLE_FMT_FLTP;
        input_frame->channel_layout=AV_CH_LAYOUT_STEREO;
        input_frame->nb_samples=1024;
        result= av_frame_get_buffer(input_frame,0);
        if(result<0){
            cerr<<"Error:av_frame_get_buffer failed."<<endl;
            return -1;
        }
        result= av_frame_make_writable(input_frame);
        if(result<0){
            cerr<<"Error:av_frame_make_writable failed."<<endl;
            return -1;
        }
        return 0;
    }
    复制代码

    三.循环编辑音频帧

      在这一步需要注意的是,每次将输入音频帧放入滤镜图前,都要做一次初始化音频帧操作,否则会报错:filter context - fmt: fltp r: 44100 layout: 3 ch: 2, incoming frame - fmt: (null) r: 0 layout: 3 ch: 2 pts_time: NOPTS【Changing audio frame properties on the fly is not supported.】。注意一定是每次,不要只初始化一次,这样只有第一帧初始化了,后面的帧还是会报错,因为输入帧的格式要和滤镜上下文保持一致,如果没有每次都初始化,后面的帧的格式和采样率就识别不到,为null了。下面给出代码:

    复制代码
    //audio_filter_core.cpp
    static int32_t filter_frame(){
        int32_t result= av_buffersrc_add_frame(abuffersrc_ctx,input_frame);
        if(result<0){
            cerr<<"Error:add frame to buffersrc failed."<<endl;
            return -1;
        }
        while(1){
            result=av_buffersink_get_frame(abuffersink_ctx,output_frame);
            if(result==AVERROR(EAGAIN)||result==AVERROR_EOF){
                return 1;
            }
            else if(result<0){
                cerr<<"Error:av_buffersink_get_frame failed."<<endl;
                return -1;
            }
            cout<<"Output channels:"<channels<<",nb_samples:"<nb_samples<<",sample_fmt:"<format<<endl;
            write_samples_to_pcm2(output_frame);
            av_frame_unref(output_frame);
        }
        return 0;
    }
    int32_t audio_filtering(){
        int32_t result=0;
        while(!end_of_input_file()){
            init_frames();//每次都要执行
            result=read_pcm_to_frame2(input_frame,INPUT_FORMAT,2);
            if(result<0){
                cerr<<"Error:read_pcm_to_frame2 failed."<<endl;
                return -1;
            }
            result=filter_frame();
            if(result<0){
                cerr<<"Error:filter_frame failed."<<endl;
                return -1;
            }
        }
        return 0;
    }
    复制代码

    四.将编辑后的数据写入输出文件

      在这一步需要注意的是,由于在滤镜图中有一个滤镜实例将音频帧的采样格式设置为了AV_SAMPLE_FMT_S16,这是packed格式的帧,左右声道的数据交错存储在frame->data[0]指向的内存单元中,所以在写入的时候,需要注意这一点。下面给出代码:

    复制代码
    //io_data.cpp
    int32_t write_samples_to_pcm2(AVFrame* frame){
        int16_t* samples = reinterpret_cast(frame->data[0]);
        int dataSize = frame->nb_samples * frame->channels * sizeof(int16_t);
        fwrite(samples, 1, dataSize, output_file);
        return 0;
    }
    复制代码

      数据读入代码:

    复制代码
    //io_data.cpp
    static FILE* input_file= nullptr;
    static FILE* output_file= nullptr;
    int32_t read_pcm_to_frame2(AVFrame *frame,enum AVSampleFormat sample_fmt,int channels){
        int data_size= av_get_bytes_per_sample(sample_fmt);
        if(data_size<0){
            cerr<<"Error:Failed to calculate data size."<<endl;
            return -1;
        }
        for(int i=0;inb_samples;i++){
            for(int ch=0;ch){
                fread(frame->data[ch]+i*data_size,1,data_size,input_file);
            }
        }
        return 0;
    }
    int32_t open_input_output_files(const char* input_name,const char* output_name){
        if(strlen(input_name)==0||strlen(output_name)==0){
            cout<<"Error:empty input or output file name."<<endl;
            return -1;
        }
        close_input_output_files();
        input_file=fopen(input_name,"rb");//rb:读取一个二进制文件,该文件必须存在
        if(input_file==nullptr){
            cerr<<"Error:failed to open input file."<<endl;
            return -1;
        }
        output_file=fopen(output_name,"wb");//wb:打开或新建一个二进制文件,只允许写
        if(output_file== nullptr){
            cout<<"Error:failed to open output file."<<endl;
            return -1;
        }
        return 0;
    }
    void close_input_output_files(){
        if(input_file!= nullptr){
            fclose(input_file);
            input_file= nullptr;
        }
        if(output_file!= nullptr){
            fclose(output_file);
            output_file= nullptr;
        }
    }
    int32_t end_of_input_file(){
        return feof(input_file);
    }
    复制代码

    五.销毁资源

    复制代码
    audio_filter_core.cpp
    static void free_frames(){
        av_frame_free(&input_frame);
        av_frame_free(&output_frame);
    }
    void destroy_audio_filter(){
        free_frames();
        avfilter_graph_free(&filter_graph);
    }
    复制代码

    六.main函数实现

    复制代码
    int main(){
        const char *input_file_name="../input.pcm";
        const char *output_file_name="../output.pcm";
        const char *volume_factor="0.9";
        int32_t result=0;
        result= open_input_output_files(input_file_name,output_file_name);
        if(result<0){
            return -1;
        }
        result= init_audio_filter(volume_factor);
        if(result<0){
            return -1;
        }
        result=audio_filtering();
        if(result<0){
            return -1;
        }
        destroy_audio_filter();
        close_input_output_files();
        return 0;
    }
    复制代码

      最后,可以使用下面的指令测试输出的pcm文件:

      ffplay -ac 2 -ar 44100 -f s16le -i output.pcm

     
  • 相关阅读:
    PostgreSQL | CTE | 使用with子句的通用表达式
    LOGO 8.3 Web Server功能
    软件测试/测试开发丨接口自动化测试学习笔记,整体结构响应断言
    软件工程:阿姆达尔定律,性能设计和优化的指导原则
    BOM系列之Navigator对象
    如何实现上拉加载,下拉刷新?
    目标检测YOLO实战应用案例100讲-森林野火预警的小目标检测
    Excel·VBA使用ADO合并工作簿
    初识Nginx + Linux 中安装Nginx
    Spring Cloud OpenFeign - - - > 契约配置
  • 原文地址:https://www.cnblogs.com/luqman/p/audio_filter.html