FFmpeg 音频重采样的两种方法libavcodec和libswresample

对于很多播放器,在输出时会固定为一种格式(如44100hz,双声道,16bit signed),因为多数设备能够支持这些格式。这种情况下对于不同的多种输入源,即需要进行音频重采样。

1、libavcodec

libavcodec提供了重采样相关接口,该接口较老,一般配合FFmpeg 2版本的解码接口avcodec_decode_audio3使用,将解码数据转换为指定格式。新版本不建议使用该接口。

接口说明:

相关函数头文件定义,接口很清晰:
1)函数av_audio_resample_init()用来初始化重采样的参数,前6个参数很好理解,

  • @param output_channels 重采样后声道数
  • @param input_channels 原声道数
  • @param output_rate 重采样后采样率
  • @param input_rate 原采样率
  • @param sample_fmt_out 重采样后音频数据格式
  • @param sample_fmt_in 原采样数据格式

后4个参数基本是使用缺省参数(我也不太清楚这几个值的使用),分别为:16, 10, 0, 1

2)函数audio_resample()用来重采样,前3个参数分别为上下文/输出数据/输入数据,注意最后一个参数是指“原数据的采样个数”,而不是字节数。函数返回值也是重采样之后的采样个数。

3)函数audio_resample_close()用来清理重采样时分配的资源。

typedef struct ReSampleContext ReSampleContext;
/**
 *  Initialize audio resampling context.
 *
 * @param output_channels  number of output channels
 * @param input_channels   number of input channels
 * @param output_rate      output sample rate
 * @param input_rate       input sample rate
 * @param sample_fmt_out   requested output sample format
 * @param sample_fmt_in    input sample format
 * @param filter_length    length of each FIR filter in the filterbank relative to the cutoff frequency
 * @param log2_phase_count log2 of the number of entries in the polyphase filterbank
 * @param linear           if 1 then the used FIR filter will be linearly interpolated
                           between the 2 closest, if 0 the closest will be used
 * @param cutoff           cutoff frequency, 1.0 corresponds to half the output sampling rate
 * @return allocated ReSampleContext, NULL if error occurred
 */
attribute_deprecated
ReSampleContext *av_audio_resample_init(int output_channels, int input_channels,
                                        int output_rate, int input_rate,
                                        enum AVSampleFormat sample_fmt_out,
                                        enum AVSampleFormat sample_fmt_in,
                                        int filter_length, int log2_phase_count,
                                        int linear, double cutoff);

attribute_deprecated
int audio_resample(ReSampleContext *s, short *output, short *input, int nb_samples);

/**
 * Free resample context.
 *
 * @param s a non-NULL pointer to a resample context previously
 *          created with av_audio_resample_init()
 */
attribute_deprecated
void audio_resample_close(ReSampleContext *s);

示例代码:

在FFmpeg 3.2上,使用该接口其实并不方便,毕竟这个接口是配合老的音频解码接口avcodec_decode_audio3使用。而新的音频解码接口(avcodec_decode_audio4或者是更新的send/recive接口),会将解码后的音频数据存放在AVFrame 结构体的data中,对于planar类型的数据,是每个声道分辨存在一个数组中的,如data[0],data[1],data[2]…这就有一个问题,重采样时,还得再重新把各个声道数据合并到一起,比较麻烦,所以下面示例中,如果是planar类型的数据,我们只取其中一条声道做重采样。
需要注意的是,planar类型的数据以一条声道来采样,需要转换为对应的非planar类型,否则该接口会转换出错。

(代码只是简单的接口示例,很多异常未处理)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef __cplusplus
extern "C"
{
#endif
#define __STDC_CONSTANT_MACROS
#ifdef _STDINT_H
#undef _STDINT_H
#endif
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#define MAX_AUDIO_FRAME_SIZE 192000 //48khz 16bit audio 2 channels

int main(int argc, char **argv){
    if(argc < 2){
        return -1;
    }
    const char* in_file = argv[1];

    AVFormatContext *fctx = NULL;
    AVCodecContext *cctx = NULL;
    AVCodec *acodec = NULL;
	
    FILE *audio_dst_file1 = fopen("./before_resample.pcm", "wb");
    FILE *audio_dst_file2 = fopen("./after_resample.pcm", "wb");

    av_register_all();
    avformat_open_input(&fctx, in_file, NULL, NULL);
    avformat_find_stream_info(fctx, NULL);
    //get audio index
    int aidx = av_find_best_stream(fctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    printf("get aidx[%d]!!!\n",aidx);
    //open audio codec
    AVCodecParameters *codecpar = fctx->streams[aidx]->codecpar;
    acodec = avcodec_find_decoder(codecpar->codec_id);
    cctx = avcodec_alloc_context3(acodec);
    avcodec_parameters_to_context(cctx, codecpar);
    avcodec_open2(cctx, acodec, NULL);

    //init resample
    ReSampleContext * resample_ctx = NULL;
    int output_channels = 2;
    int output_rate = 48000;
    int input_channels = cctx->channels;
    int input_rate = cctx->sample_rate;
    int input_sample_fmt = cctx->sample_fmt;
    if(av_sample_fmt_is_planar((AVSampleFormat)input_sample_fmt)){//if planar, we just use one channel
    	input_sample_fmt = input_sample_fmt - (int)AV_SAMPLE_FMT_U8P;
        input_channels = 1;
    }
    AVSampleFormat output_sample_fmt = AV_SAMPLE_FMT_S16;
    printf("input_channels[%d=>%d],input_rate[%d=>%d],input_sample_fmt[%d=>%d]\n",
                    cctx->channels,input_channels,cctx->sample_rate,input_rate,cctx->sample_fmt,input_sample_fmt);
    resample_ctx = av_audio_resample_init(output_channels, input_channels, output_rate, input_rate, 
  											output_sample_fmt,(AVSampleFormat)input_sample_fmt,16, 10, 0, 1);
    if(!resample_ctx){
    	printf("av_audio_resample_init fail!!!\n");
    	return -1;
    }
	
    AVPacket *pkt =av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
	
    short *out_buffer=(short *)av_malloc(MAX_AUDIO_FRAME_SIZE);
    int size = 0;
    
    while(av_read_frame(fctx,pkt) == 0){//DEMUX
        if(pkt->stream_index == aidx){
            avcodec_send_packet(cctx, pkt);
            while(1){
            	int ret = avcodec_receive_frame(cctx, frame);
            	if(ret != 0){
                    break;
            	}else{
                    //before resample
                    size = frame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)frame->format);
                    if(frame->data[0] != NULL){
                        fwrite(frame->data[0], 1, size, audio_dst_file1);
                        memset(out_buffer,0x00,sizeof(out_buffer));
                        size = audio_resample(resample_ctx, out_buffer, (short *)frame->data[0], frame->nb_samples);
                        size = size* av_get_bytes_per_sample(output_sample_fmt) * output_channels ;//samples * byte * channels
                        if(size > 0){
                            fwrite(out_buffer, 1, size, audio_dst_file2);
                        }
                    }
            	}
            	av_frame_unref(frame);
            }
        }
        else{
            //printf("not audio frame!!!\n");
            av_packet_unref(pkt);
            continue;
        }
        av_packet_unref(pkt);
    }

    //close
    audio_resample_close(resample_ctx);
    av_packet_free(&pkt);
    av_frame_free(&frame);
    avcodec_close(cctx);
    avformat_close_input(&fctx);
    av_free(out_buffer);
    fclose(audio_dst_file1);
    fclose(audio_dst_file2);

    return 0;
}

采样结果,上面的例子中,保存了采样前和采样后的PCM。
我们拿一个源中的一条声道,采样率为48000,样本格式为AV_SAMPLE_FMT_FLTP。
《FFmpeg 音频重采样的两种方法libavcodec和libswresample》
按照示例代码,重采样后采样率为48000(48khz一般为DVD的采样率),双声道,采样数据类型为AV_SAMPLE_FMT_S16,即变成了非planar类型的两声道。
这个例子中,声道数/样本格式都发生变化,采样率刚好一样(当然,也可以选取其他频率来验证采样率的变化是否正确,如44100)
《FFmpeg 音频重采样的两种方法libavcodec和libswresample》

2、libswresample

libswresample接口提供了更为方便的重采样方法。

接口说明:

我只介绍几个重要的函数,其他的可以参考对应头文件libswresample/swresample.h
1)函数swr_alloc_set_opts(),申请重采样上下文,并可以将相关参数进行设置。

  • @param s 重采样上下文,如果为NULL,函数会自己生成
  • @param out_ch_layout 重采样的声道layout
  • @param out_sample_fmt 重采样的数据格式
  • @param out_sample_rate 重采样的采样率
  • @param in_ch_layout 源声道layout
  • @param in_sample_fmt 源数据格式
  • @param in_sample_rate 源采样率
/**
 * Allocate SwrContext if needed and set/reset common parameters.
 *
 * @param s               existing Swr context if available, or NULL if not
 * @param out_ch_layout   output channel layout (AV_CH_LAYOUT_*)
 * @param out_sample_fmt  output sample format (AV_SAMPLE_FMT_*).
 * @param out_sample_rate output sample rate (frequency in Hz)
 * @param in_ch_layout    input channel layout (AV_CH_LAYOUT_*)
 * @param in_sample_fmt   input sample format (AV_SAMPLE_FMT_*).
 * @param in_sample_rate  input sample rate (frequency in Hz)
 * @param log_offset      logging level offset
 * @param log_ctx         parent logging context, can be NULL
 *
 * @see swr_init(), swr_free()
 * @return NULL on error, allocated context otherwise
 */
struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
                                      int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
                                      int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
                                      int log_offset, void *log_ctx);

2)函数 int swr_init(struct SwrContext *s); // 初始化上下文。

3)函数 void swr_free(struct SwrContext **s); // 释放上下文空间。
swr_convert()

针对每一帧音频的处理。把一帧帧的音频作相应的重采样

4)函数int swr_conver

  • @param s 音频重采样的上下文
  • @param out 重采样后的数据
  • @param out_count 重采样输出的单通道的样本数量,注意不是字节数
  • @param in 重采样前的源数据
  • @param in_count 输入的单通道的样本数量
/** Convert audio.
 *
 * in and in_count can be set to 0 to flush the last few samples out at the
 * end.
 *
 * If more input is provided than output space, then the input will be buffered.
 * You can avoid this buffering by using swr_get_out_samples() to retrieve an
 * upper bound on the required number of output samples for the given number of
 * input samples. Conversion will run directly without copying whenever possible.
 *
 * @param s         allocated Swr context, with parameters set
 * @param out       output buffers, only the first one need be set in case of packed audio
 * @param out_count amount of space available for output in samples per channel
 * @param in        input buffers, only the first one need to be set in case of packed audio
 * @param in_count  number of input samples available in one channel
 *
 * @return number of samples output per channel, negative value on error
 */
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                const uint8_t **in , int in_count);

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef __cplusplus
extern "C"
{
#endif
#define __STDC_CONSTANT_MACROS
#ifdef _STDINT_H
#undef _STDINT_H
#endif
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#ifdef __cplusplus
};
#endif
#define MAX_AUDIO_FRAME_SIZE 192000 //48khz 16bit audio 2 channels

int main(int argc, char **argv){
    if(argc < 2){
        return -1;
    }
    const char* in_file = argv[1];

    AVFormatContext *fctx = NULL;
    AVCodecContext *cctx = NULL;
    AVCodec *acodec = NULL;
	
    FILE *audio_dst_file1 = fopen("./before_resample.pcm", "wb");
    FILE *audio_dst_file2 = fopen("./after_resample.pcm", "wb");

    av_register_all();
    avformat_open_input(&fctx, in_file, NULL, NULL);
    avformat_find_stream_info(fctx, NULL);
    //get audio index
    int aidx = av_find_best_stream(fctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    printf("get aidx[%d]!!!\n",aidx);
    //open audio codec
    AVCodecParameters *codecpar = fctx->streams[aidx]->codecpar;
    acodec = avcodec_find_decoder(codecpar->codec_id);
    cctx = avcodec_alloc_context3(acodec);
    avcodec_parameters_to_context(cctx, codecpar);
    avcodec_open2(cctx, acodec, NULL);

    //init resample
    int output_channels = 2;
    int output_rate = 48000;
    int input_channels = cctx->channels;
    int input_rate = cctx->sample_rate;
    AVSampleFormat input_sample_fmt = cctx->sample_fmt;
    AVSampleFormat output_sample_fmt = AV_SAMPLE_FMT_S16;
    printf("channels[%d=>%d],rate[%d=>%d],sample_fmt[%d=>%d]\n",
        input_channels,output_channels,input_rate,output_rate,input_sample_fmt,output_sample_fmt);
    
    SwrContext* resample_ctx = NULL;
    resample_ctx = swr_alloc_set_opts(resample_ctx, av_get_default_channel_layout(output_channels),output_sample_fmt,output_rate,
                            av_get_default_channel_layout(input_channels),input_sample_fmt, input_rate,0,NULL);
    if(!resample_ctx){
        printf("av_audio_resample_init fail!!!\n");
        return -1;
    }
    swr_init(resample_ctx);
    
    AVPacket *pkt =av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
    int size = 0;
    uint8_t* out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE);
    
    while(av_read_frame(fctx,pkt) == 0){//DEMUX
        if(pkt->stream_index == aidx){
            avcodec_send_packet(cctx, pkt);
            while(1){
            	int ret = avcodec_receive_frame(cctx, frame);
            	if(ret != 0){
                    break;
            	}else{
                    //before resample
                    size = frame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)frame->format);
                    if(frame->data[0] != NULL){
                        fwrite(frame->data[0], 1, size, audio_dst_file1);
                    }
                    //resample
                    memset(out_buffer,0x00,sizeof(out_buffer));
                    int out_samples = swr_convert(resample_ctx,&out_buffer,frame->nb_samples,(const uint8_t **)frame->data,frame->nb_samples);
                    if(out_samples > 0){
                        av_samples_get_buffer_size(NULL,output_channels ,out_samples, output_sample_fmt, 1);//out_samples*output_channels*av_get_bytes_per_sample(output_sample_fmt);
                        fwrite(out_buffer, 1, size, audio_dst_file2);
                    }
            	}
            	av_frame_unref(frame);
            }
        }
        else{
            //printf("not audio frame!!!\n");
            av_packet_unref(pkt);
            continue;
        }
        av_packet_unref(pkt);
    }

    //close
    swr_free(&resample_ctx);
    av_packet_free(&pkt);
    av_frame_free(&frame);
    avcodec_close(cctx);
    avformat_close_input(&fctx);
    av_free(out_buffer);
    fclose(audio_dst_file1);
    fclose(audio_dst_file2);

    return 0;
}

对比libavcodec提供的方法,libswresample可以很方便的将planar类型的数据都放到重采样流程中,将多个声道采样为我们想要的输出类型。
特别是对于有些planar类型的源,左右声道不一样的时候,使用libavcodec的重采样流程还需要对多个声道数据进行合并处理。而libswresample则可以省去这些麻烦。
如下图,使用libswresample对一个左右声道不一样的源进行重采样后,输出为采样率为48000hz,双声道,采样数据类型为AV_SAMPLE_FMT_S16。从图中,我们可以看到,左右声道是明显不同。
如果按照上面第一个示例代码,则只能对其中一条声道重采样,这样输出的结果左右声道一样,对于很多源,可能就失去了一些细腻的声道效果。
《FFmpeg 音频重采样的两种方法libavcodec和libswresample》

    原文作者:wusc'blog
    原文地址: https://blog.csdn.net/myvest/article/details/89442000
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞