mp3转码aac及AVAudioFifo的使用
前言
如今以抖音、快手為代表的短視頻秀無處不在,比如它們一個很普通的功能就是使用流行音樂替換作為視頻的背景音樂。而在視頻中音頻一般都是以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_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和packAVPacket *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);// 初始化audiofifoaudiofifo = 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 closingret = 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_silencewhile (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?
總結
以上是生活随笔為你收集整理的mp3转码aac及AVAudioFifo的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【2016年第6期】众包模式在大规模遥感
- 下一篇: h5游戏优化方案