【相关博客】
【FFmpeg(2016)】PCM编码AAC
【前言】
这两天在做一些音频的编码,但FFmpeg的编码库avcodec有20M这么大,所以决定使用其他库进行编码。网上发现faac体积小,直接编解码,于是决定使用faac库作为编码模块。
但是从faac的源码发现,它只支持如下格式的PCM编码:
PCM Sample Input Format
0 FAAC_INPUT_NULL invalid, signifies a misconfigured config
1 FAAC_INPUT_16BIT native endian 16bit
2 FAAC_INPUT_24BIT native endian 24bit in 24 bits (not implemented)
3 FAAC_INPUT_32BIT native endian 24bit in 32 bits (DEFAULT)
4 FAAC_INPUT_FLOAT 32bit 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 encoder
pkt.size = 0;
//Read raw data
if (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)