前言
如今,以抖音和快手为代表的短视频节目无处不在。例如,它们的一个非常常见的功能是用流行音乐代替视频背景音乐。在视频中,音频通常是AAC形成存在,但大多数流行音乐都是mp3.格式传播, 因此,背景音乐替换功能需要完成,步骤之一需要完成mp3转aac这样的音频转码过程。
根据我们以往的经验,转码的一般流程应该是这样的:
解封装->提取音频流->解码成PCM->重新编码成AAC
这个过程是对的,但是内部的细节是什么呢?mp3解码后的AVFrame函数可以通过avcodec_send_frame
送进aac编码器可以吗? 很明显,这是不可能的,因为mp3每帧1152个采样点,aac每帧为1024个采样点。每帧采样点不同,不能直接通过avcodec_send_frame
进行编码。
AVAudioFifo·
AVAudioFifo是一个。使用它可以很容易地存储我们的音频缓冲数据,mp3转码aac在这个过程中,由于它们的采样点不同,我们可以把mp3解码出来的 pcm数据放入到AVAudioFifo然后每次都去AVAudioFifo获得1024个采样点aac编码器,这种做法使我们的音频转码非常方便灵活。AVAudioFifo让我们在采样层面进行操作,而不关心底层的字节层面;它支持多种格式的单次采样,如支持planar或packed采样格式,支持不同的通道数等。
AVAudioFifo的API使用也很简单,主要包括分配、释放、获取可读空间长度、写入音频数据、读取音频数据等相关函数:
一是分配和释放操作:
//分配一个AVAudioFifo。 //sample_fmt指定采样格式 //nb_samples则指定AVAudioFifo缓冲区的大小可以通过av_audio_fifo_realloc重新分配 AVAudioFifo *av_audio_fifo_alloc(enum AVSampleFormat sample_fmt, int channels,int nb_samples); ////重新分配缓冲区的大小 ///成功返回0,失败返回负的错误值 int av_audio_fifo_realloc(AVAudioFifo *af, int nb_samples); //释放AVAudioFifo void av_audio_fifo_free(AVAudioFifo *af);
查询操作:
//返回fifo当前存储在中间的采样数量 int av_audio_fifo_size(AVAudioFifo *af); //返回fifo当前可写采样数量,即未使用的空间数量 int av_audio_fifo_space(AVAudioFifo *af); // 上述两个函数的返回值之和等于AVAudioFifo缓冲区大小
读取操作:
///将采样写入AVAudioFifo //成功则返回实际写入的采样数,如果写入成功,返回值必定等于nb_samples,失败返回负的错误值 int av_audio_fifo_write(AVAudioFifo *af, void **data, int nb_samples); //peek:读取数据,但读取的数据不会从fifo中删除 int av_audio_fifo_peek(AVAudioFifo *af, void **data, int nb_samples); ////从指定的偏移位置peek数据 int av_audio_fifo_peek_at(AVAudioFifo *af, void **data, int nb_samples, int offset); //读取数据,读取的数据将从fifo中删除 int av_audio_fifo_read(AVAudioFifo *af, void **data, int nb_samples); //从fifo中删除nb_samples个采样 int av_audio_fifo_drain(AVAudioFifo *af, int nb_samples); //删除fifo所有采样,清空 void av_audio_fifo_reset(AVAudioFifo *af);
音频转码
有了AVAudioFifo,然后我们的音频转码过程变成了以下几种方式:
解封装 -> 提取音频流 -> 解码成PCM->将PCM数据写入AVAudioFifo -> 每次从AVAudioFifo送1024个采样点aac编码器 -> 重新编码成AAC
如果最后没有可输入的话PCM数据,但是AVAudioFifo可读取的采样点数仍然不满意aac如果有1024个采样点,可以通过填充静音来补充…
上代码:
#include <iostream> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/audio_fifo.h> #include <libavutil/channel_layout.h> } class Mp3ToAAC { public: void mp3_to_aac(const char *mp3, const char *aac) { avFormatContext = avformat_alloc_context(); int ret = avformat_open_input(&avFormatContext, mp3, nullptr, nullptr); if (ret < 0) { std::cout << "打开mp3输入流失败" << std::endl; return; } av_dump_format(avFormatContext, 0, mp3, 0); std::cout << "流的类型:" << avFormatContext->streams[0]->codecpar->codec_type << std::endl; int audio_index = av_find_best_stream(avFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); if (audio_index < 0) { std::cout << "没有找到音频流,另一种搜索方式" << std::endl; for (int i = 0; i < avFormatContext->nb_streams; i) { if (AVMEDIA_TYPE_AUDIO == avFormatContext->streams[i]->codecpar->codec_type) { audio_index = i; std::cout << "找到音频流,audio_index:" << audio_index << std::endl; break; } } if (audio_index < 0) { return; } } // 初始化输出 out_format_context = avformat_alloc_context(); const AVOutputFormat *avOutputFormat = av_guess_format(nullptr, aac, nullptr); out_format_context->oformat = avOutputFormat; const AVCodec *aac_encoder = avcodec_find_encoder(AV_CODEC_ID_AAC); encode_CodecContext = avcodec_alloc_context3(aac_encoder); encode_CodecContext->channel_layout = AV_CH_LAYOUT_STEREO; encode_CodecContext->channels = av_get_channel_layout_nb_channels(encode_CodecContext->channel_layout); // 若解码pcm不是44100需要重采样,重采样需要主要音频持续时间不变 encode_CodecContext->sample_rate = 44100; // 比如使用 编码22050的采样率,编码后的时间明显比实际音频长 // encode_CodecContext->sample_rate = 22050; encode_CodecContex->codec_type = AVMEDIA_TYPE_AUDIO;
encode_CodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
encode_CodecContext->profile = FF_PROFILE_AAC_LOW;
//ffmpeg默认的aac是不带adts,而fdk_aac默认带adts,这里我们强制不带
encode_CodecContext->flags = AV_CODEC_FLAG_GLOBAL_HEADER;
// 打开编码器
ret = avcodec_open2(encode_CodecContext, aac_encoder, nullptr);
if (ret < 0) {
char error[1024];
av_strerror(ret, error, 1024);
std::cout << "编码器打开失败:" << error << std::endl;
return;
}
AVStream *aac_stream = avformat_new_stream(out_format_context, aac_encoder);
aac_index = aac_stream->index;
avcodec_parameters_from_context(aac_stream->codecpar, encode_CodecContext);
ret = avio_open(&out_format_context->pb, aac, AVIO_FLAG_WRITE);
if (ret < 0) {
std::cout << "输出流打开失败" << std::endl;
return;
}
ret = avformat_write_header(out_format_context, nullptr);
if (ret < 0) {
std::cout << "文件头写入失败" << std::endl;
return;
}
// 解码相关
const AVCodec *decoder = avcodec_find_decoder(avFormatContext->streams[audio_index]->codecpar->codec_id);
avCodecContext = avcodec_alloc_context3(decoder);
avcodec_parameters_to_context(avCodecContext, avFormatContext->streams[audio_index]->codecpar);
ret = avcodec_open2(avCodecContext, decoder, nullptr);
if (ret < 0) {
std::cout << "解码器打开失败" << std::endl;
return;
}
// 分配frame和pack
AVPacket *avPacket = av_packet_alloc();
avFrame = av_frame_alloc();
out_pack = av_packet_alloc();
encode_frame = av_frame_alloc();
encode_frame->nb_samples = encode_CodecContext->frame_size;
encode_frame->sample_rate = encode_CodecContext->sample_rate;
encode_frame->channel_layout = encode_CodecContext->channel_layout;
encode_frame->channels = encode_CodecContext->channels;
encode_frame->format = encode_CodecContext->sample_fmt;
av_frame_get_buffer(encode_frame, 0);
// 初始化audiofifo
audiofifo = av_audio_fifo_alloc(encode_CodecContext->sample_fmt, encode_CodecContext->channels,
encode_CodecContext->frame_size);
while (true) {
ret = av_read_frame(avFormatContext, avPacket);
if (ret < 0) {
std::cout << "read end" << std::endl;
break;
} else if (avPacket->stream_index == audio_index) {
decode_to_pcm(avPacket);
}
av_packet_unref(avPacket);
}
// todo 需要冲刷数据,不然可能会漏掉几帧数据 [aac @ 0x125e05f50] 2 frames left in the queue on closing
ret = av_write_trailer(out_format_context);
if (ret < 0) {
std::cout << "文件尾写入失败" << std::endl;
}
}
~Mp3ToAAC() {
if (nullptr != avFormatContext) {
avformat_close_input(&avFormatContext);
}
avcodec_free_context(&avCodecContext);
if (nullptr != out_format_context) {
avformat_close_input(&out_format_context);
}
avcodec_free_context(&encode_CodecContext);
}
private:
// 解码
AVFormatContext *avFormatContext = nullptr;
AVCodecContext *avCodecContext = nullptr;
AVFrame *avFrame = nullptr;
// 编码
AVFormatContext *out_format_context = nullptr;
AVCodecContext *encode_CodecContext = nullptr;
AVPacket *out_pack = nullptr;
AVFrame *encode_frame = nullptr;
AVAudioFifo *audiofifo = nullptr;
int aac_index = 0;
int64_t cur_pts = 0;
void encode_to_aac() {
av_frame_make_writable(encode_frame);
// todo 如果是冲刷最后几帧数据,不够的可以填充静音 av_samples_set_silence
while (av_audio_fifo_size(audiofifo) > encode_CodecContext->frame_size) {
int ret = av_audio_fifo_read(audiofifo, reinterpret_cast<void **>(encode_frame->data),
encode_CodecContext->frame_size);
if (ret < 0) {
std::cout << "audiofifo 读取数据失败" << std::endl;
return;
}
cur_pts += encode_frame->nb_samples;
encode_frame->pts = cur_pts;
ret = avcodec_send_frame(encode_CodecContext, encode_frame);
if (ret < 0) {
std::cout << "发送编码失败" << std::endl;
return;
}
while (true) {
ret = avcodec_receive_packet(encode_CodecContext, out_pack);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
std::cout << "avcodec_receive_packet end:" << ret << std::endl;
break;
} else if (ret < 0) {
std::cout << "avcodec_receive_packet fail:" << ret << std::endl;
return;
} else {
out_pack->stream_index = aac_index;
ret = av_write_frame(out_format_context, out_pack);
if (ret < 0) {
std::cout << "av_write_frame fail:" << ret << std::endl;
return;
} else {
std::cout << "av_write_frame success:" << ret << std::endl;
}
}
}
}
}
void decode_to_pcm(const AVPacket *avPacket) {
int ret = avcodec_send_packet(avCodecContext, avPacket);
if (ret < 0) {
char error[1024];
av_strerror(ret, error, 1024);
std::cout << "发送解码失败:" << error << std::endl;
}
while (true) {
ret = avcodec_receive_frame(avCodecContext, avFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
std::cout << "avcodec_receive_frame end:" << ret << std::endl;
break;
} else if (ret < 0) {
std::cout << "获取解码数据失败" << std::endl;
return;
} else {
std::cout << "获取解码数据成功 nb_samples:" << avFrame->nb_samples << std::endl;
/**
* mp3解码出来的pcm无法直接编码成aac,
* 因为mp3每帧是1152个采样点,而aac每帧需要1024个采样点
* 解决方案是使用AVAudioFifo缓冲起来
*/
int cache_size = av_audio_fifo_size(audiofifo);
std::cout << "cache_size:" << cache_size << std::endl;
av_audio_fifo_realloc(audiofifo, cache_size + avFrame->nb_samples);
av_audio_fifo_write(audiofifo, reinterpret_cast<void **>(avFrame->data), avFrame->nb_samples);
encode_to_aac();
}
}
}
};
转自:FFmpeg连载7-mp3转码aac及AVAudioFifo的使用_FlyerGo的博客-CSDN博客_ffmpeg mp3转aac