AAC是mp4的音频格式,而安防摄像机基本上使用G711等编码,想要封装成mp4供web预览,就需要跨越g711转AAC的这个难关。
ffmpeg作为音视频界的泰斗,可以帮助我们实现这一功能。
代码流程如下:
open_input_file | 打开输入的文件供使用例如G711或者pcm |
open_output_file | 打开转码之后aac的音频文件供存储 |
init_resampler | 初始化在采用的各种音频格式和参数 |
init_fifo | 初始化换成队列为存储读取的音频文件存储为转码 |
write_output_file_header | 写入转码文件头 |
read_decode_convert_and_store | 解码读取的音频帧解码成pcm后保持带消息队列 |
load_encode_and_write | 从消息队列中读取pcm音频文件,并编码成AAC |
重点分析init_resampler/read_decode_convert_and_store/load_encode_and_write 这3个函数为转码的核心函数。
init_resampler主要是进行格式转换使用,AAC现在ffmpeg使用的格式是AV_SAMPLE_FMT_FLTP格式,其他格式好像都不行,而 一般PCM使用AV_SAMPLE_FMT_S16格式,所以需要进行格式转换。
/* Convert the input samples to the desired output sample format.
* This requires a temporary storage provided by converted_input_samples. */
if (convert_samples((const uint8_t**)input_frame->extended_data, converted_input_samples,
input_frame->nb_samples, resampler_context))
goto cleanup;
read_decode_convert_and_store的核心是把读取的音频格式解码成pcm
static int decode_audio_frame(AVFrame *frame,
AVFormatContext *input_format_context,
AVCodecContext *input_codec_context,
int *data_present, int *finished)
{
/* Packet used for temporary storage. */
AVPacket input_packet;
int error;
init_packet(&input_packet);
/* Read one audio frame from the input file into a temporary packet. */
if ((error = av_read_frame(input_format_context, &input_packet)) < 0) {
/* If we are at the end of the file, flush the decoder below. */
if (error == AVERROR_EOF)
*finished = 1;
else {
fprintf(stderr, "Could not read frame (error '%d')\n",
error);
return error;
}
}
/* Send the audio frame stored in the temporary packet to the decoder.
* The input audio stream decoder is used to do this. */
if ((error = avcodec_send_packet(input_codec_context, &input_packet)) < 0) {
fprintf(stderr, "Could not send packet for decoding (error '%d')\n",
error);
return error;
}
/* Receive one frame from the decoder. */
error = avcodec_receive_frame(input_codec_context, frame);
/* If the decoder asks for more data to be able to decode a frame,
* return indicating that no data is present. */
if (error == AVERROR(EAGAIN)) {
error = 0;
goto cleanup;
/* If the end of the input file is reached, stop decoding. */
}
else if (error == AVERROR_EOF) {
*finished = 1;
error = 0;
goto cleanup;
}
else if (error < 0) {
fprintf(stderr, "Could not decode frame (error '%d')\n",
error);
goto cleanup;
/* Default case: Return decoded data. */
}
else {
*data_present = 1;
goto cleanup;
}
cleanup:
av_packet_unref(&input_packet);
return error;
}
load_encode_and_write 的核心是把pcm编码成AAC编码
static int encode_audio_frame(AVFrame *frame,
AVFormatContext *output_format_context,
AVCodecContext *output_codec_context,
int *data_present)
{
/* Packet used for temporary storage. */
AVPacket output_packet;
int error;
init_packet(&output_packet);
/* Set a timestamp based on the sample rate for the container. */
if (frame) {
frame->pts = pts;
pts += frame->nb_samples;
}
/* Send the audio frame stored in the temporary packet to the encoder.
* The output audio stream encoder is used to do this. */
error = avcodec_send_frame(output_codec_context, frame);
/* The encoder signals that it has nothing more to encode. */
if (error == AVERROR_EOF) {
error = 0;
goto cleanup;
}
else if (error < 0) {
fprintf(stderr, "Could not send packet for encoding (error '%d')\n",
error);
return error;
}
/* Receive one encoded frame from the encoder. */
error = avcodec_receive_packet(output_codec_context, &output_packet);
/* If the encoder asks for more data to be able to provide an
* encoded frame, return indicating that no data is present. */
if (error == AVERROR(EAGAIN)) {
error = 0;
goto cleanup;
/* If the last frame has been encoded, stop encoding. */
}
else if (error == AVERROR_EOF) {
error = 0;
goto cleanup;
}
else if (error < 0) {
fprintf(stderr, "Could not encode frame (error '%d')\n",
error);
goto cleanup;
/* Default case: Return encoded data. */
}
else {
*data_present = 1;
}
/* Write one audio frame from the temporary packet to the output file. */
if (*data_present &&
(error = av_write_frame(output_format_context, &output_packet)) < 0) {
fprintf(stderr, "Could not write frame (error '%d')\n",
error);
goto cleanup;
}
cleanup:
av_packet_unref(&output_packet);
return error;
}
重点API解读:
格式转换,主要是对pcm格式进行2次采样,改变采样率,采样位数, 采样通道
swr_alloc_set_opts:设置格式转换的初始化参数
* Create a resampler context for the conversion.
* Set the conversion parameters.
* Default channel layouts based on the number of channels
* are assumed for simplicity (they are sometimes not detected
* properly by the demuxer and/or decoder).
*/
*resample_context = swr_alloc_set_opts(NULL,
av_get_default_channel_layout(output_codec_context->channels),
output_codec_context->sample_fmt,
output_codec_context->sample_rate,
av_get_default_channel_layout(input_codec_context->channels),
input_codec_context->sample_fmt,
input_codec_context->sample_rate,
0, NULL);
av_samples_alloc:分配音频空间,主要分配转换之后保持的音频
重点参数如下
output_codec_context->channels:音频通道数
frame_size:音频采样数
output_codec_context->sample_fmt:音频采样深度
/* Allocate as many pointers as there are audio channels.
* Each pointer will later point to the audio samples of the corresponding
* channels (although it may be NULL for interleaved formats).
*/
if (!((*converted_input_samples) = (uint8_t * *) calloc(output_codec_context->channels,
sizeof(**converted_input_samples)))) {
fprintf(stderr, "Could not allocate converted input sample pointers\n");
return AVERROR(ENOMEM);
}
/* Allocate memory for the samples of all channels in one consecutive
* block for convenience. */
if ((error = av_samples_alloc(*converted_input_samples, NULL,
output_codec_context->channels,
frame_size,
output_codec_context->sample_fmt, 0)) < 0) {
fprintf(stderr,
"Could not allocate converted input samples (error '%d')\n",
error);
av_freep(&(*converted_input_samples)[0]);
free(*converted_input_samples);
return error;
}
swr_convert :对音频进行二次采样,并输出结果
重点参数如下
resample_context:swr_alloc_set_opts产生的值
converted_data:av_samples_alloc分配的空间
frame_size:采样次数
input_data:输入的音频
frame_size:输入的采样次数
/* Convert the samples using the resampler. */
if ((error = swr_convert(resample_context,
converted_data, frame_size,
input_data, frame_size)) < 0) {
fprintf(stderr, "Could not convert input samples (error '%d')\n",
error);
return error;
}