使用libswresample庫實現音訊重取樣

2023-07-05 18:00:15

一.初始化音訊重取樣器

  在音訊重取樣時,用到的核心結構是SwrContext,我們可以通過swr_alloc()獲取swr_ctx範例,然後通過av_opt_set_int()函數和av_opt_set_sample_fmt()函數來設定音訊重取樣的引數,最後通過swr_init()函數初始化SwrContext範例即可。下面給出程式碼:

//audio_resampler_core.cpp
#define SRC_NB_SAMPLES 1152
static SwrContext *swr_ctx;
static AVFrame *input_frame= nullptr;
int32_t dst_nb_samples,max_dst_nb_samples,dst_nb_channels,dst_rate,src_rate;
enum AVSampleFormat src_sample_fmt=AV_SAMPLE_FMT_NONE,dst_sample_fmt=AV_SAMPLE_FMT_NONE;
uint8_t **dst_data= nullptr;
int32_t dst_linesize=0;
static int32_t init_frame(int sample_rate,int sample_format,uint64_t channel_layout){
    int32_t result=0;
    input_frame->sample_rate=sample_rate;
    input_frame->nb_samples=SRC_NB_SAMPLES;
    input_frame->format=sample_format;
    input_frame->channel_layout=channel_layout;
    result= av_frame_get_buffer(input_frame,0);
    if(result<0){
        cerr<<"Error:av_frame_get_buffer failed."<<endl;
        return -1;
    }
    return 0;
}
int32_t init_audio_resampler(int32_t in_sample_rate,const char *in_sample_fmt,const char *in_ch_layout,int32_t out_sample_rate,const char *out_sample_fmt,const char *out_ch_layout){
    int32_t result=0;
    swr_ctx=swr_alloc();
    if(!swr_ctx){
        cerr<<"Error:swr_alloc failed."<<endl;
        return -1;
    }
    int64_t src_ch_layout=-1,dst_ch_layout=-1;
    if(!strcasecmp(in_ch_layout,"MONO")){
        src_ch_layout=AV_CH_LAYOUT_MONO;
    }
    else if(!strcasecmp(in_ch_layout,"STEREO")){
        src_ch_layout=AV_CH_LAYOUT_STEREO;
    }
    else if(!strcasecmp(in_ch_layout,"SURROUND")){
        src_ch_layout=AV_CH_LAYOUT_SURROUND;
    }
    else{
        cerr<<"ERROR:unsupported input channel layout."<<endl;
        return -1;
    }
    if(!strcasecmp(out_ch_layout,"MONO")){
        dst_ch_layout=AV_CH_LAYOUT_MONO;
    }
    else if(!strcasecmp(out_ch_layout,"STEREO")){
        dst_ch_layout=AV_CH_LAYOUT_STEREO;
    }
    else if(!strcasecmp(out_ch_layout,"SURROUND")){
        dst_ch_layout=AV_CH_LAYOUT_SURROUND;
    }
    else{
        cerr<<"ERROR:unsupported output channel layout."<<endl;
        return -1;
    }
    if(!strcasecmp(in_sample_fmt,"fltp")){
        src_sample_fmt=AV_SAMPLE_FMT_FLTP;
    }
    else if(!strcasecmp(in_sample_fmt,"s16")){
        src_sample_fmt=AV_SAMPLE_FMT_S16P;
    }
    else{
        cerr<<"Error:unsupported input sample format."<<endl;
        return -1;
    }
    if(!strcasecmp(out_sample_fmt,"fltp")){
        dst_sample_fmt=AV_SAMPLE_FMT_FLTP;
    }
    else if(!strcasecmp(out_sample_fmt,"s16")){
        dst_sample_fmt=AV_SAMPLE_FMT_S16P;
    }
    else{
        cerr<<"Error:unsupported output sample format."<<endl;
        return -1;
    }
    src_rate=in_sample_rate;
    dst_rate=out_sample_rate;
    av_opt_set_int(swr_ctx,"in_channel_layout",src_ch_layout,0);
    av_opt_set_int(swr_ctx,"in_sample_rate",src_rate,0);
    av_opt_set_sample_fmt(swr_ctx,"in_sample_fmt",src_sample_fmt,0);
    av_opt_set_int(swr_ctx,"out_channel_layout",dst_ch_layout,0);
    av_opt_set_int(swr_ctx,"out_sample_rate",dst_rate,0);
    av_opt_set_sample_fmt(swr_ctx,"out_sample_fmt",dst_sample_fmt,0);
    result=swr_init(swr_ctx);
    if(result<0){
        cerr<<"Error:failed to initialize SwrContext."<<endl;
        return -1;
    }
    input_frame=av_frame_alloc();
    if(!input_frame){
        cerr<<"Error:av_frame_alloc failed."<<endl;
        return -1;
    }
    result= init_frame(in_sample_rate,src_sample_fmt,src_ch_layout);
    if(result<0){
        cerr<<"Error:init_frame failed."<<endl;
        return -1;
    }
    max_dst_nb_samples=dst_nb_samples=av_rescale_rnd(SRC_NB_SAMPLES,out_sample_rate,in_sample_rate,AV_ROUND_UP);
    dst_nb_channels= av_get_channel_layout_nb_channels(dst_ch_layout);
    cout<<"max_dst_nb_samples:"<<max_dst_nb_samples<<",dst_nb_channels:"<<dst_nb_channels<<endl;
    return 0;
}

二.迴圈對音訊幀進行重取樣

  音訊重取樣用到的核心函數是swr_convert(),不過在進行重取樣的時候,需要注意每次要去判斷目標取樣點個數是否大於最大目標取樣點個數,如果大於,需要重新給輸出緩衝區分配記憶體空間。下面給出程式碼:

audio_resampler_core.cpp
static int32_t resampling_frame(){
    int32_t result=0;
    int32_t dst_bufsize=0;
    dst_nb_samples=av_rescale_rnd(swr_get_delay(swr_ctx,src_rate)+SRC_NB_SAMPLES,dst_rate,src_rate,AV_ROUND_UP);
    if(dst_nb_samples>max_dst_nb_samples){
        av_freep(&dst_data[0]);
        result=av_samples_alloc(dst_data,&dst_linesize,dst_nb_channels,dst_nb_samples,dst_sample_fmt,1);
        if(result<0){
            cerr<<"Error:failed to reallocate dst_data."<<endl;
            return -1;
        }
        cout<<"nb_samples exceeds max_dst_nb_samples,buffer reallocated."<<endl;
        max_dst_nb_samples=dst_nb_samples;
    }
    result=swr_convert(swr_ctx,dst_data,dst_nb_samples,(const uint8_t **)input_frame->data,SRC_NB_SAMPLES);
    if(result<0){
        cerr<<"Error:swr_convert failed."<<endl;
        return -1;
    }
    dst_bufsize= av_samples_get_buffer_size(&dst_linesize,dst_nb_channels,result,dst_sample_fmt,1);
    if(dst_bufsize<0){
        cerr<<"Error:Could not get sample buffer size."<<endl;
        return -1;
    }
    write_packed_data_to_file(dst_data[0],dst_bufsize);
    return 0;
}
int32_t audio_resampling(){
    int32_t result= av_samples_alloc_array_and_samples(&dst_data,&dst_linesize,dst_nb_channels,dst_nb_samples,dst_sample_fmt,0);
    if(result<0){
        cerr<<"Error:av_samples_alloc_array_and_samples failed."<<endl;
        return -1;
    }
    cout<<"dst_linesize:"<<dst_linesize<<endl;
    while(!end_of_input_file()){
        result= read_pcm_to_frame2(input_frame,src_sample_fmt,2);//這個函數的程式碼請看我上篇部落格
        if(result<0){
            cerr<<"Error:read_pcm_to_frame2 failed."<<endl;
            return -1;
        }
        result=resampling_frame();
        if(result<0){
            cerr<<"Error:resampling_frame failed."<<endl;
            return -1;
        }
    }
    return 0;
}

三.將重取樣後的資料寫入輸出檔案

  在初始化重取樣器的時候,我們設定了目標取樣格式為s16p,聲道數量為1,所以只需要將dst_data[0]的資料寫入輸出檔案即可,下面給出程式碼:

//io_data.cpp
int32_t write_packed_data_to_file(uint8_t *data,int32_t size){
    fwrite(data,1,size,output_file);
}

四.銷燬音訊重取樣器

//audio_resampler_core.cpp
void destroy_audio_resampler(){
    av_frame_free(&input_frame);
    if(dst_data){
        av_freep(&dst_data[0]);
    }
    av_freep(&dst_data);
    swr_free(&swr_ctx);
}

五.main函數實現

int main(){
    const char *input_file_name="../input.pcm";
    int32_t in_sample_rate=44100;
    const char *in_sample_fmt="fltp";
    const char *in_sample_layout="STEREO";
    const char *output_file_name="../output.pcm";
    int32_t out_sample_rate=22050;
    const char *out_sample_fmt="s16";
    const char *out_sample_layout="MONO";
    int32_t result=open_input_output_files(input_file_name,output_file_name);
    if(result<0){
        return -1;
    }
    result=init_audio_resampler(in_sample_rate,in_sample_fmt,in_sample_layout,out_sample_rate,out_sample_fmt,out_sample_layout);
    if(result<0){
        return -1;
    }
    result=audio_resampling();
    if(result<0){
        return -1;
    }
    destroy_audio_resampler();
    close_input_output_files();
    return 0;
}

  最後,使用以下指令可以測試輸出的output.pcm檔案:

  ffplay -f s16le -ac 1 -ar 22050 -i output.pcm

  沒有給出的函數程式碼請看我前面的部落格