使用libswresample库实现音频重采样

2023-10-22 13:58:21 浏览数 (2)

一.初始化音频重采样器

  在音频重采样时,用到的核心结构是SwrContext,我们可以通过swr_alloc()获取swr_ctx实例,然后通过av_opt_set_int()函数和av_opt_set_sample_fmt()函数来设置音频重采样的参数,最后通过swr_init()函数初始化SwrContext实例即可。下面给出代码:

代码语言:javascript复制
//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(),不过在进行重采样的时候,需要注意每次要去判断目标采样点个数是否大于最大目标采样点个数,如果大于,需要重新给输出缓冲区分配内存空间。下面给出代码:

代码语言:javascript复制
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]的数据写入输出文件即可,下面给出代码:

代码语言:javascript复制
//io_data.cpp
int32_t write_packed_data_to_file(uint8_t *data,int32_t size){
    fwrite(data,1,size,output_file);
}

四.销毁音频重采样器

代码语言:javascript复制
//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函数实现

代码语言:javascript复制
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

  没有给出的函数代码请看我前面的博客

0 人点赞