【FFmpeg(2016)】SwrContext 转换PCM音频位数

来源:互联网 发布:好的健身软件 编辑:程序博客网 时间:2024/04/29 16:34



【相关博客】

【FFmpeg(2016)】PCM编码AAC


【FFmpeg(2016)】SwrContext重采样结构体


【前言】

这两天在做一些音频的编码,但FFmpeg的编码库avcodec有20M这么大,所以决定使用其他库进行编码。网上发现faac体积小,直接编解码,于是决定使用faac库作为编码模块。


但是从faac的源码发现,它只支持如下格式的PCM编码:


PCM Sample Input Format
0 FAAC_INPUT_NULLinvalid, signifies a misconfigured config
1 FAAC_INPUT_16BITnative endian 16bit
2 FAAC_INPUT_24BITnative endian 24bit in 24 bits (not implemented)
3 FAAC_INPUT_32BITnative endian 24bit in 32 bits (DEFAULT)
4 FAAC_INPUT_FLOAT32bit floating point

我要编码的PCM 是32bit float的,使用faac编码时却总是出错,于是决定使用ffmpeg的转码库进行位数转换。


【FFmpeg及音频相关概念】

enum AVSampleFormat {    AV_SAMPLE_FMT_NONE = -1,    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits    AV_SAMPLE_FMT_S16,         ///< signed 16 bits    AV_SAMPLE_FMT_S32,         ///< signed 32 bits    AV_SAMPLE_FMT_FLT,         ///< float    AV_SAMPLE_FMT_DBL,         ///< double    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar    AV_SAMPLE_FMT_FLTP,        ///< float, planar    AV_SAMPLE_FMT_DBLP,        ///< double, planar    AV_SAMPLE_FMT_S64,         ///< signed 64 bits    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically};
这是ffmpeg罗列的音频格式,带P的表示是 平面数据。

在介绍音频存储格式的两个概念: 交错立体声存储 和 双单声道。

交错立体声存储,即对于两声道的音频存储格式是:L(一个采样点)RLRLRLR.....

双单声道,即是:L(一个采样点)LLLLLLLLLLLLRRRRRRRRRRRRLLLLLLLLLLLLLLLRRRRRRRRRRRRRR。。。


目前为止,FFmpeg API中,我发现只有sample_fmt为 AV_SAMPLE_FMT_FLTP 的音频编码器能打开,也就说它只支持编码格式为AV_SAMPLE_FMT_FLTP的PCM音频。


它虽然并不支持其他格式PCM数据的编码,但是它支持所有格式的相互转换,所以不管你拥有什么格式的音频,只要使用SwrContext转换到AV_SAMPLE_FMT_FLTP,即可使用FFmpeg编码。


【代码】


原始PCM数据:

sample_rate = 48000

channels = 2

sample_fmt = AV_SAMPLE_FMT_FLT

目标PCM数据:

sample_rate = 48000

channels = 2

sample_fmt = AV_SAMPLE_FMT_U8


原始数据 及 目标数据 均是交错立体声存储(必须注意:FFmpeg PCM格式枚举中带P的后缀表明用此参数初始化的编码器,源数据必须是交错立体声存储)


SwrContext *swr_ctx = NULL;    FILE *fp_in = NULL;fp_in = fopen("in.pcm", "rb");    FILE *outpcm = NULL;outpcm = fopen("out.pcm", "wb");uint8_t **convert_data;uint8_t* frame_buf;    int framenum = 100000;    int nb_samples = 1024;    int channels = 2;    int samplerate = 48000;swr_ctx = swr_alloc_set_opts(NULL,AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_U8,samplerate,AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_FLT,samplerate,0, NULL);swr_init(swr_ctx);/* 分配空间 ,存储结果*/convert_data = (uint8_t**)calloc(        channels,sizeof(*convert_data));int sizes = av_samples_alloc(        convert_data, NULL,channels, nb_samples,AV_SAMPLE_FMT_U8, 0);    /* 存储原始数据 */size = av_samples_get_buffer_size(NULL, channels, nb_samples, AV_SAMPLE_FMT_FLT, 0);frame_buf = (uint8_t *)av_malloc(size);ret = avcodec_fill_audio_frame(pFrame, channels, AV_SAMPLE_FMT_FLT, (const uint8_t*)frame_buf, size, 0);if (ret < 0){qDebug() << "avcodec_fill_audio_frame error ";return 0;}for (i = 0; i < framenum; i++) {av_init_packet(&pkt);pkt.data = NULL;    // packet data will be allocated by the encoderpkt.size = 0;//Read raw dataif (fread(frame_buf, 1, size, fp_in) <= 0) {printf("Failed to read raw data! \n");return -1;}else if (feof(fp_in)) {break;}/* 转换数据,令各自声道的音频数据存储在不同的数组(分别由不同指针指向)*/swr_convert(swr_ctx, convert_data, nb_samples ,(const uint8_t**)pFrame->data, nb_samples );fwrite(convert_data[0], nb_samples * channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_U8), 1, outpcm);    }


【代码解析】


FFmpeg 在处理  交错立体声存储 以及  双声单声道 中结构体的存储方式稍有不同。


由于是由AV_SAMPLE_FMT_FLT 转 AV_SAMPLE_FMT_U8,那么目标数据的内存空间只需要源数据的1/4.



下面先看 av_sample_alloc 发生什么事,

int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,                     int nb_samples, enum AVSampleFormat sample_fmt, int align){    uint8_t *buf;    //计算总共内存大小    int size = av_samples_get_buffer_size(NULL, nb_channels, nb_samples,                                          sample_fmt, align);    if (size < 0)        return size;    buf = av_malloc(size);    if (!buf)        return AVERROR(ENOMEM);    size = av_samples_fill_arrays(audio_data, linesize, buf, nb_channels,                                  nb_samples, sample_fmt, align);    if (size < 0) {        av_free(buf);        return size;    }    //省略...    return size;}

int av_samples_fill_arrays(uint8_t **audio_data, int *linesize,                           const uint8_t *buf, int nb_channels, int nb_samples,                           enum AVSampleFormat sample_fmt, int align){    int ch, planar, buf_size, line_size;    planar   = av_sample_fmt_is_planar(sample_fmt);    buf_size = av_samples_get_buffer_size(&line_size, nb_channels, nb_samples,                                          sample_fmt, align);    if (buf_size < 0)        return buf_size;    audio_data[0] = (uint8_t *)buf;     // 这里对平面数据做多一些操作    for (ch = 1; planar && ch < nb_channels; ch++)        audio_data[ch] = audio_data[ch-1] + line_size;    if (linesize)        *linesize = line_size;    return buf_size;}


以上调用av_samples_get_buffer_size的第一个参数返回每个声道所占的字节数。

可以发现,如果是此PCM属于平面数据,那么内存将平均分配给每一个声道,此时有如audio_data[0] audio_data[1]....;

对于非平面数据,那么只需要将第一个元素audio_data[0]指向首指针


int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,                               enum AVSampleFormat sample_fmt, int align){    int line_size;    int sample_size = av_get_bytes_per_sample(sample_fmt);    int planar      = av_sample_fmt_is_planar(sample_fmt);    /* validate parameter ranges */    if (!sample_size || nb_samples <= 0 || nb_channels <= 0)        return AVERROR(EINVAL);    /* auto-select alignment if not specified */    if (!align) {        if (nb_samples > INT_MAX - 31)            return AVERROR(EINVAL);        align = 1;        nb_samples = FFALIGN(nb_samples, 32);    }    /* check for integer overflow */    if (nb_channels > INT_MAX / align ||        (int64_t)nb_channels * nb_samples > (INT_MAX - (align * nb_channels)) / sample_size)        return AVERROR(EINVAL);    line_size = planar ? FFALIGN(nb_samples * sample_size,               align) :   // 这里根据是否是平面数据                         FFALIGN(nb_samples * sample_size * nb_channels, align);    if (linesize)        *linesize = line_size;    return planar ? line_size * nb_channels : line_size;}


由于我们使用的是AV_SAMPLES_FMT_U8,所以处理结果数据时只需用 convert_data[0] 指针。


avcodec_fill_audio_frame 调用同理,由于是AV_SAMPLES_FMT_FLT非平面格式,pFrame->data[0]指向所有数据(左右声道)


其实它们的内存空间一样大,区别就是

非planr : data[0] -> [8192字节]

planr: data[0]->[4096字节] data[1]->[4096字节]


【对于平面与非平面格式的相互转换】


平面数据中,有多少声道,那么data二维指针中就有多少元素指向内存,打个比方,如果是三声道

那么AVFrame中的 data[0] data[1] data[2] 分别指向响应位置,以提供内存给每个声道(顺便说下,每个声道的数据大小是一样的)


如下是FFmpeg支持的layout


/** * @} * @defgroup channel_mask_c Audio channel layouts * @{ * */#define AV_CH_LAYOUT_MONO              (AV_CH_FRONT_CENTER)#define AV_CH_LAYOUT_STEREO            (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT)#define AV_CH_LAYOUT_2POINT1           (AV_CH_LAYOUT_STEREO|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_2_1               (AV_CH_LAYOUT_STEREO|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_SURROUND          (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)#define AV_CH_LAYOUT_3POINT1           (AV_CH_LAYOUT_SURROUND|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_4POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_4POINT1           (AV_CH_LAYOUT_4POINT0|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_2_2               (AV_CH_LAYOUT_STEREO|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)#define AV_CH_LAYOUT_QUAD              (AV_CH_LAYOUT_STEREO|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_5POINT0           (AV_CH_LAYOUT_SURROUND|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT)#define AV_CH_LAYOUT_5POINT1           (AV_CH_LAYOUT_5POINT0|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_5POINT0_BACK      (AV_CH_LAYOUT_SURROUND|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_5POINT1_BACK      (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_6POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT0_FRONT     (AV_CH_LAYOUT_2_2|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_HEXAGONAL         (AV_CH_LAYOUT_5POINT0_BACK|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT1_BACK      (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_BACK_CENTER)#define AV_CH_LAYOUT_6POINT1_FRONT     (AV_CH_LAYOUT_6POINT0_FRONT|AV_CH_LOW_FREQUENCY)#define AV_CH_LAYOUT_7POINT0           (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_7POINT0_FRONT     (AV_CH_LAYOUT_5POINT0|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_7POINT1           (AV_CH_LAYOUT_5POINT1|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_7POINT1_WIDE      (AV_CH_LAYOUT_5POINT1|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_7POINT1_WIDE_BACK (AV_CH_LAYOUT_5POINT1_BACK|AV_CH_FRONT_LEFT_OF_CENTER|AV_CH_FRONT_RIGHT_OF_CENTER)#define AV_CH_LAYOUT_OCTAGONAL         (AV_CH_LAYOUT_5POINT0|AV_CH_BACK_LEFT|AV_CH_BACK_CENTER|AV_CH_BACK_RIGHT)#define AV_CH_LAYOUT_HEXADECAGONAL     (AV_CH_LAYOUT_OCTAGONAL|AV_CH_WIDE_LEFT|AV_CH_WIDE_RIGHT|AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_RIGHT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_FRONT_CENTER|AV_CH_TOP_FRONT_LEFT|AV_CH_TOP_FRONT_RIGHT)#define AV_CH_LAYOUT_STEREO_DOWNMIX    (AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT)




0 0