关于c++:FFmpeg连载7mp3转码aac及AVAudioFifo的使用

7次阅读

共计 7996 个字符,预计需要花费 20 分钟才能阅读完成。

前言

现在以抖音、快手为代表的短视频秀无处不在,比方它们一个很一般的性能就是应用流行音乐替换作为视频的背景音乐。而在视频中音频个别都是以 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 和 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!!!

正文完
 0