资讯详情

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_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 

标签: pcm260变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台