一.初始化音訊濾鏡
初始化音訊濾鏡的方法基本上和初始化視訊濾鏡的方法相同,不懂的可以看上篇部落格,這裡直接給出程式碼:
//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:"<<output_frame->channels<<",nb_samples:"<<output_frame->nb_samples<<",sample_fmt:"<<output_frame->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<int16_t*>(frame->data[0]); int dataSize = frame->nb_samples * frame->channels * sizeof(int16_t); fwrite(samples, 1, dataSize, output_file); return 0; }
資料讀入程式碼:
//io_data.cpp
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;i<frame->nb_samples;i++){ for(int ch=0;ch<channels;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