/*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* simple audio converter
*
* @example transcode_aac.c
* Convert an input audio file to AAC in an MP4 container using FFmpeg.
* @author Andreas Unterweger (dustsigns@gmail.com)
*/
#include <stdio.h>
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#include "libavutil/audio_fifo.h"
#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/frame.h"
#include "libavutil/opt.h"
#include "libswresample/swresample.h"
/** 输出比特率 kbit/s */
#define OUTPUT_BIT_RATE 96000
/** 输出声道数 */
#define OUTPUT_CHANNELS 2
/** 打开输入文件和请求解码器 */
static int open_input_file(const char *filename,
AVFormatContext **input_format_context,
AVCodecContext **input_codec_context)
{
AVCodecContext *avctx;
AVCodec *input_codec;
int error;
/** 读取输入文件 */
if ((error = avformat_open_input(input_format_context, filename, NULL,
NULL)) < 0) {
fprintf(stderr, "Could not open input file '%s' (error '%s')\n",
filename, av_err2str(error));
*input_format_context = NULL;
return error;
}
/** 从输入文件读取信息,如数据流数等 */
if ((error = avformat_find_stream_info(*input_format_context, NULL)) < 0) {
fprintf(stderr, "Could not open find stream info (error '%s')\n",
av_err2str(error));
avformat_close_input(input_format_context);
return error;
}
/** 确保只有一个输入流 */
if ((*input_format_context)->nb_streams != 1) {
fprintf(stderr, "Expected one audio input stream, but found %d\n",
(*input_format_context)->nb_streams);
avformat_close_input(input_format_context);
return AVERROR_EXIT;
}
/** 根据音频数据流中编码方式查找对应解码器 */
if (!(input_codec = avcodec_find_decoder((*input_format_context)->streams[0]->codecpar->codec_id))) {
fprintf(stderr, "Could not find input codec\n");
avformat_close_input(input_format_context);
return AVERROR_EXIT;
}
/** 创建一个解码器上下文 */
avctx = avcodec_alloc_context3(input_codec);
if (!avctx) {
fprintf(stderr, "Could not allocate a decoding context\n");
avformat_close_input(input_format_context);
return AVERROR(ENOMEM);
}
/** 用混合器参数初始化解码器上下文参数 */
error = avcodec_parameters_to_context(avctx, (*input_format_context)->streams[0]->codecpar);
if (error < 0) {
avformat_close_input(input_format_context);
avcodec_free_context(&avctx);
return error;
}
/** 为音频流打开一个编码器稍后使用 */
if ((error = avcodec_open2(avctx, input_codec, NULL)) < 0) {
fprintf(stderr, "Could not open input codec (error '%s')\n",
av_err2str(error));
avcodec_free_context(&avctx);
avformat_close_input(input_format_context);
return error;
}
/** 保存解码器指针 */
*input_codec_context = avctx;
return 0;
}
/**
* 打开一个输出文件并请求一个编码器
* 同时设置一些基础参数
* 有一些参数是介于输入文件的参数
*/
static int open_output_file(const char *filename,
AVCodecContext *input_codec_context,
AVFormatContext **output_format_context,
AVCodecContext **output_codec_context)
{
AVCodecContext *avctx = NULL;
AVIOContext *output_io_context = NULL;
AVStream *stream = NULL;
AVCodec *output_codec = NULL;
int error;
/** 创建一个输出文件,稍后用于写入 */
if ((error = avio_open(&output_io_context, filename,
AVIO_FLAG_WRITE)) < 0) {
fprintf(stderr, "Could not open output file '%s' (error '%s')\n",
filename, av_err2str(error));
return error;
}
/** 为格式创建一个格式上下文 */
if (!(*output_format_context = avformat_alloc_context())) {
fprintf(stderr, "Could not allocate output format context\n");
return AVERROR(ENOMEM);
}
/** 将输出文件(指针)与容器格式上下文关联 */
(*output_format_context)->pb = output_io_context;
/** 根据文件扩展名猜测所需的容器格式 */
if (!((*output_format_context)->oformat = av_guess_format(NULL, filename,
NULL))) {
fprintf(stderr, "Could not find output file format\n");
goto cleanup;
}
av_strlcpy((*output_format_context)->filename, filename,
sizeof((*output_format_context)->filename));
/** 按编码器名称查找要使用的编码器 */
if (!(output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC))) {
fprintf(stderr, "Could not find an AAC encoder.\n");
goto cleanup;
}
/** 在输出文件容器中创建一个新的音频流 */
if (!(stream = avformat_new_stream(*output_format_context, NULL))) {
fprintf(stderr, "Could not create new stream\n");
error = AVERROR(ENOMEM);
goto cleanup;
}
avctx = avcodec_alloc_context3(output_codec);
if (!avctx) {
fprintf(stderr, "Could not allocate an encoding context\n");
error = AVERROR(ENOMEM);
goto cleanup;
}
/**
* 设置基础编码器参数
* 输入文件的采样率用于避免采样率转换
*/
avctx->channels = OUTPUT_CHANNELS;
avctx->channel_layout = av_get_default_channel_layout(OUTPUT_CHANNELS);
avctx->sample_rate = input_codec_context->sample_rate;
avctx->sample_fmt = output_codec->sample_fmts[0];
avctx->bit_rate = OUTPUT_BIT_RATE;
/** 允许使用实验性AAC编码器 */
avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
/** 为容器设置采样率 */
stream->time_base.den = input_codec_context->sample_rate;
stream->time_base.num = 1;
/**
* 一些容器格式 (像 MP4) 要求设置全局头部
* 标记编码器使其相应运行
*/
if ((*output_format_context)->oformat->flags & AVFMT_GLOBALHEADER)
avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
/** 为音频流打开编码器以至于稍后用它 */
if ((error = avcodec_open2(avctx, output_codec, NULL)) < 0) {
fprintf(stderr, "Could not open output codec (error '%s')\n",
av_err2str(error));
goto cleanup;
}
// 设置编码器参数
error = avcodec_parameters_from_context(stream->codecpar, avctx);
if (error < 0) {
fprintf(stderr, "Could not initialize stream parameters\n");
goto cleanup;
}
/** 保存编码器上下文以便于以后访问 */
*output_codec_context = avctx;
return 0;
cleanup:
avcodec_free_context(&avctx);
avio_closep(&(*output_format_context)->pb);
avformat_free_context(*output_format_context);
*output_format_context = NULL;
return error < 0 ? error : AVERROR_EXIT;
}
/** 初始化一个packet以便一会读取或写入 */
static void init_packet(AVPacket *packet)
{
av_init_packet(packet);
/**设置包的数据为空和大小为0以便后面被识别 */
packet->data = NULL;
packet->size = 0;
}
/** 初始化读取文件的一帧 */
static int init_input_frame(AVFrame **frame)
{
if (!(*frame = av_frame_alloc())) {
fprintf(stderr, "Could not allocate input frame\n");
return AVERROR(ENOMEM);
}
return 0;
}
/**
* 根据输入和输出编码器设置初始化音频重采样器
* 如果输入和输出的格式不同,则需要进行转换
* libswresample会处理这个问题,但需要初始化
*/
static int init_resampler(AVCodecContext *input_codec_context,
AVCodecContext *output_codec_context,
SwrContext **resample_context)
{
int error;
/**
* 为转换创建一个重采样器上下文
* 设置转换参数
* 基于默认通道式初始化通道布局
* 为简单起见,假设(有时他们不能正确检查
* 被解复用器或解码器).
*/
*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);
if (!*resample_context) {
fprintf(stderr, "Could not allocate resample context\n");
return AVERROR(ENOMEM);
}
/**
* 执行健全性检查,以便转换的样本数不大于要转换的样本数
* 如果采样率不同,则必须以不同的方式处理此情况
*/
av_assert0(output_codec_context->sample_rate == input_codec_context->sample_rate);
/** 使用指定的参数打开重采样器 */
if ((error = swr_init(*resample_context)) < 0) {
fprintf(stderr, "Could not open resample context\n");
swr_free(resample_context);
return error;
}
return 0;
}
/** 初始化FIFO缓存区用于被编码的音频样本 */
static int init_fifo(AVAudioFifo **fifo, AVCodecContext *output_codec_context)
{
/** 基于特殊输出格式创建IFIO缓存区 */
if (!(*fifo = av_audio_fifo_alloc(output_codec_context->sample_fmt,
output_codec_context->channels, 1))) {
fprintf(stderr, "Could not allocate FIFO\n");
return AVERROR(ENOMEM);
}
return 0;
}
/** 在输出文件上下文写入头部 */
static int write_output_file_header(AVFormatContext *output_format_context)
{
int error;
if ((error = avformat_write_header(output_format_context, NULL)) < 0) {
fprintf(stderr, "Could not write output file header (error '%s')\n",
av_err2str(error));
return error;
}
return 0;
}
/** 从输入文件中解码一帧音频数据 */
static int decode_audio_frame(AVFrame *frame,
AVFormatContext *input_format_context,
AVCodecContext *input_codec_context,
int *data_present, int *finished)
{
/** Packet 用于临时存储 */
AVPacket input_packet;
int error;
init_packet(&input_packet);
/** 从输入文件中读取到的一帧数据转入到临时Packet中 */
if ((error = av_read_frame(input_format_context, &input_packet)) < 0) {
/** 如果我们在文件的末尾,刷新下面的解码器 */
if (error == AVERROR_EOF)
*finished = 1;
else {
fprintf(stderr, "Could not read frame (error '%s')\n",
av_err2str(error));
return error;
}
}
/**
* 解码存储在临时Packet中的音频帧
* 这个输入音频流解码器是用来干这活儿的
* 如果我们在文件的末尾,将一个空包传递给解码器以刷新它
*/
if ((error = avcodec_decode_audio4(input_codec_context, frame,
data_present, &input_packet)) < 0) {
fprintf(stderr, "Could not decode frame (error '%s')\n",
av_err2str(error));
av_packet_unref(&input_packet);
return error;
}
/**
* 如果解码器没有被完全清空,则我们没有完成
* 所以这个方法必须被再次调用一遍
*/
if (*finished && *data_present)
*finished = 0;
av_packet_unref(&input_packet);
return 0;
}
/**
* 为特殊音频采样率数初始化一个临时存储
* 由于不同的格式,转换器需要要一个临时存储
* 要分配的音频样本数以帧大小指定
*/
static int init_converted_samples(uint8_t ***converted_input_samples,
AVCodecContext *output_codec_context,
int frame_size)
{
int error;
/**
* 分配尽可能多的指针,因为有音频通道
* 每个指针稍后将指向相应频道的音频样本(尽管对于交错格式可能为空)
*/
if (!(*converted_input_samples = calloc(output_codec_context->channels,
sizeof(**converted_input_samples)))) {
fprintf(stderr, "Could not allocate converted input sample pointers\n");
return AVERROR(ENOMEM);
}
/**
* 为方便起见,为一个连续块中所有通道的样本分配内存
*/
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 '%s')\n",
av_err2str(error));
av_freep(&(*converted_input_samples)[0]);
free(*converted_input_samples);
return error;
}
return 0;
}
/**
* 将输入音频样本转换为输出样本格式
* 转换以每帧为基础进行,其大小由帧大小指定
*/
static int convert_samples(const uint8_t **input_data,
uint8_t **converted_data, const int frame_size,
SwrContext *resample_context)
{
int error;
/** 用重采样器转换采样 */
if ((error = swr_convert(resample_context,
converted_data, frame_size,
input_data , frame_size)) < 0) {
fprintf(stderr, "Could not convert input samples (error '%s')\n",
av_err2str(error));
return error;
}
return 0;
}
/** 将转换后的输入音频样本添加到FIFO缓冲区,以便以后处理 */
static int add_samples_to_fifo(AVAudioFifo *fifo,
uint8_t **converted_input_samples,
const int frame_size)
{
int error;
/**
* 确保这个FIFO足够大,因为它要保持新的和旧的采样
*/
if ((error = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame_size)) < 0) {
fprintf(stderr, "Could not reallocate FIFO\n");
return error;
}
/** 保存新采样到FIFO缓存区 */
if (av_audio_fifo_write(fifo, (void **)converted_input_samples,
frame_size) < frame_size) {
fprintf(stderr, "Could not write data to FIFO\n");
return AVERROR_EXIT;
}
return 0;
}
/**
* 从输入文件中读取一个音频帧,解码、转换并将其存储在FIFO缓冲区中
*/
static int read_decode_convert_and_store(AVAudioFifo *fifo,
AVFormatContext *input_format_context,
AVCodecContext *input_codec_context,
AVCodecContext *output_codec_context,
SwrContext *resampler_context,
int *finished)
{
/** 从文件读取的帧的输入样本的临时存储。*/
AVFrame *input_frame = NULL;
/** 转换输入采样的临时存储 */
uint8_t **converted_input_samples = NULL;
int data_present;
int ret = AVERROR_EXIT;
/** 为一帧输入帧初始化临时存储 */
if (init_input_frame(&input_frame))
goto cleanup;
/** 解码一帧的音频样本 */
if (decode_audio_frame(input_frame, input_format_context,
input_codec_context, &data_present, finished))
goto cleanup;
/**
* 如果到文件结尾而且没有更多的样本(在解码器中那些延迟的样本)
* 我们实际上结束了
* 不得将此视为错误
*/
if (*finished && !data_present) {
ret = 0;
goto cleanup;
}
/** 如果这是解码数据, 则进行转换并保存 */
if (data_present) {
/** 为转换后的输入帧初始化存储 */
if (init_converted_samples(&converted_input_samples, output_codec_context,
input_frame->nb_samples))
goto cleanup;
/**
* 将这个输入采样转换到期望的输出采样格式
* 需要由 converted_input_samples提供临时变量
*/
if (convert_samples((const uint8_t**)input_frame->extended_data, converted_input_samples,
input_frame->nb_samples, resampler_context))
goto cleanup;
/** 为后面的程序,将转换后的输入帧采样添加到FIFO缓存中 */
if (add_samples_to_fifo(fifo, converted_input_samples,
input_frame->nb_samples))
goto cleanup;
ret = 0;
}
ret = 0;
cleanup:
if (converted_input_samples) {
av_freep(&converted_input_samples[0]);
free(converted_input_samples);
}
av_frame_free(&input_frame);
return ret;
}
/**
* 为写入到输出文件,初始化一帧输入帧
* 这帧应该刚好是样本大小
*/
static int init_output_frame(AVFrame **frame,
AVCodecContext *output_codec_context,
int frame_size)
{
int error;
/** 创建一帧存储音频样本 */
if (!(*frame = av_frame_alloc())) {
fprintf(stderr, "Could not allocate output frame\n");
return AVERROR_EXIT;
}
/**
* 设置帧的参数,特别是大小和格式.
* av_frame_get_buffer需要用来创建音频帧采样
* 为简单起见,假如默认通道布局基于通道数
*/
(*frame)->nb_samples = frame_size;
(*frame)->channel_layout = output_codec_context->channel_layout;
(*frame)->format = output_codec_context->sample_fmt;
(*frame)->sample_rate = output_codec_context->sample_rate;
/**
* 为新创建的帧创建样本, This call will make
* 这个调用后确保可以容纳指定数量的样本
*/
if ((error = av_frame_get_buffer(*frame, 0)) < 0) {
fprintf(stderr, "Could not allocate output frame samples (error '%s')\n",
av_err2str(error));
av_frame_free(frame);
return error;
}
return 0;
}
/** 音频帧全局时间戳 */
static int64_t pts = 0;
/** 将一帧音频编码到输出文件 */
static int encode_audio_frame(AVFrame *frame,
AVFormatContext *output_format_context,
AVCodecContext *output_codec_context,
int *data_present)
{
/** 用于临时存储的Packet */
AVPacket output_packet;
int error;
init_packet(&output_packet);
/** 根据容器的采样率设置时间戳. */
if (frame) {
frame->pts = pts;
pts += frame->nb_samples;
}
/**
* 编码音频帧并存储到临时Packet
* 输出音频流编码器就是干这玩意儿的.
*/
if ((error = avcodec_encode_audio2(output_codec_context, &output_packet,
frame, data_present)) < 0) {
fprintf(stderr, "Could not encode frame (error '%s')\n",
av_err2str(error));
av_packet_unref(&output_packet);
return error;
}
/** Write one audio frame from the temporary packet to the output file. */
if (*data_present) {
if ((error = av_write_frame(output_format_context, &output_packet)) < 0) {
fprintf(stderr, "Could not write frame (error '%s')\n",
av_err2str(error));
av_packet_unref(&output_packet);
return error;
}
av_packet_unref(&output_packet);
}
return 0;
}
/**
* 从FIFO缓存区中读取一帧音频数据并进行编码,然后写入到输出文件中
*/
static int load_encode_and_write(AVAudioFifo *fifo,
AVFormatContext *output_format_context,
AVCodecContext *output_codec_context)
{
/** 用于写到文件中的输出帧采样的临时存储 */
AVFrame *output_frame;
/**
* 使用每帧可能的最大采样数
* 如果FIFO缓冲区中的帧大小小于最大可能帧大小,则使用此数字。否则,请使用可能的最大帧大小
*/
const int frame_size = FFMIN(av_audio_fifo_size(fifo),
output_codec_context->frame_size);
int data_written;
/** 为一帧输出帧初始化临时变量 */
if (init_output_frame(&output_frame, output_codec_context, frame_size))
return AVERROR_EXIT;
/**
* 根据需要从FIFO缓冲区读取尽可能多的样本以填充帧。
* 样本临时存储在帧中
*/
if (av_audio_fifo_read(fifo, (void **)output_frame->data, frame_size) < frame_size) {
fprintf(stderr, "Could not read data from FIFO\n");
av_frame_free(&output_frame);
return AVERROR_EXIT;
}
/** 将采样编码成一帧音频数据 */
if (encode_audio_frame(output_frame, output_format_context,
output_codec_context, &data_written)) {
av_frame_free(&output_frame);
return AVERROR_EXIT;
}
av_frame_free(&output_frame);
return 0;
}
/** 写入输出文件上下文的尾部r. */
static int write_output_file_trailer(AVFormatContext *output_format_context)
{
int error;
if ((error = av_write_trailer(output_format_context)) < 0) {
fprintf(stderr, "Could not write output file trailer (error '%s')\n",
av_err2str(error));
return error;
}
return 0;
}
/** 将音频文件转换为MP4容器中的AAC文件 */
int main(int argc, char **argv)
{
AVFormatContext *input_format_context = NULL, *output_format_context = NULL;
AVCodecContext *input_codec_context = NULL, *output_codec_context = NULL;
SwrContext *resample_context = NULL;
AVAudioFifo *fifo = NULL;
int ret = AVERROR_EXIT;
if (argc < 3) {
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
exit(1);
}
/** 注册所有编解码和格式,以便可以使用 */
av_register_all();
/** 打开输入文件以备读取 */
if (open_input_file(argv[1], &input_format_context,
&input_codec_context))
goto cleanup;
/** 创建输出文件以备写入 */
if (open_output_file(argv[2], input_codec_context,
&output_format_context, &output_codec_context))
goto cleanup;
/** 初始化用于转换音频的重采样器 */
if (init_resampler(input_codec_context, output_codec_context,
&resample_context))
goto cleanup;
/** 初始化用于存储编码音频帧采样的FIFO缓存区 */
if (init_fifo(&fifo, output_codec_context))
goto cleanup;
/** 写输出文件上下文的头部 */
if (write_output_file_header(output_format_context))
goto cleanup;
/**
* 循环,只要我们有输入样本读取或输出样本写入
* 如果两个都没有则结束
*/
while (1) {
/** 使用编码器的期望帧大小 */
const int output_frame_size = output_codec_context->frame_size;
int finished = 0;
/**
* 确保有一帧采样数据在FIFO缓存区一遍编码器正常工作
* 因为编码器和和解码器帧数据大小可能不同,我们需要一个FIFO缓存区去保存足够多的
* 输入帧采样数据,直到它能够凑够一帧输出数据
*/
while (av_audio_fifo_size(fifo) < output_frame_size) {
/**
* 编码一帧音频样本数据,转换到输出采样格式,并且放到FIFO缓存区中
*/
if (read_decode_convert_and_store(fifo, input_format_context,
input_codec_context,
output_codec_context,
resample_context, &finished))
goto cleanup;
/**
* 如果到了文件结束,继续编码这些需要编码的音频采样到输出文件中
*/
if (finished)
break;
}
/**
* 如果我们有足够多的样本给编码器,我们编码他们If we have enough samples for the encoder, we encode them.
* 在文件结尾,我们将剩余的样本传递给编码器。
*/
while (av_audio_fifo_size(fifo) >= output_frame_size ||
(finished && av_audio_fifo_size(fifo) > 0))
/**
* 从FIFO缓存区中获取一帧音频采样数据Take one frame worth of audio samples from the FIFO buffer,
* 编码并写入到输出文件
*/
if (load_encode_and_write(fifo, output_format_context,
output_codec_context))
goto cleanup;
/**
* 如果到了文件结尾,并且所有帧已经编码完毕,则退出循环
*/
if (finished) {
int data_written;
/** 如果还有延时帧,清空编码器 */
do {
if (encode_audio_frame(NULL, output_format_context,
output_codec_context, &data_written))
goto cleanup;
} while (data_written);
break;
}
}
/** 为输出文件上下文写入尾部 */
if (write_output_file_trailer(output_format_context))
goto cleanup;
ret = 0;
cleanup:
if (fifo)
av_audio_fifo_free(fifo);
swr_free(&resample_context);
if (output_codec_context)
avcodec_free_context(&output_codec_context);
if (output_format_context) {
avio_closep(&output_format_context->pb);
avformat_free_context(output_format_context);
}
if (input_codec_context)
avcodec_free_context(&input_codec_context);
if (input_format_context)
avformat_close_input(&input_format_context);
return ret;
}
源码地址