前言
现在以抖音、快手为代表的短视频秀无处不在,比方它们一个很一般的性能就是应用流行音乐替换作为视频的背景音乐。而在视频中音频个别都是以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); //开释AVAudioFifovoid 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_CodecContext->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连载1-开发环境搭建
FFmpeg连载2-拆散视频和音频
FFmpeg连载3-视频解码
FFmpeg连载4-音频解码
FFmpeg连载5-音视频编码
FFmpeg连载6-音频重采样
关注我,一起提高,人生不止coding!!!