音视频-AAC编码

作者: li_礼光 | 来源:发表于2021-07-07 17:13 被阅读0次

    采样格式

    必须是16位整数PCM。

    采样率

    支持的采样率有(Hz): 8000、11025、12000、16000、22050、24000、32000、44100、48000、64000、88200、96000

    命令行基本使用

    # pcm -> aac
    ffmpeg -ar 44100 -ac 2 -f s16le -i in.pcm -c:a libfdk_aac out.aac
     
    # wav -> aac
    ffmpeg -i in.wav -c:a libfdk_aac out.aac
    
    PCM输入数据的参数

    -ar 44100 -ac 2 -f s16le

    设置音频编码器, -c:a

    c表示codec(编解码器),a表示audio(音频)

    等价写法
    • -codec:a
    • -acodec

    需要注意的是:这个参数要写在aac文件那边,也就是属于输出参数
    默认生成的aac文件是LC规格的。

    Win平台下, 命令行转码

    ffmpeg -ar 44100 -ac 2 -f s16le -i record_to_pcm.pcm -c:a libfdk_aac pcm_to_aac.aac

    PS G:\Resource> ffmpeg -ar 44100 -ac 2 -f s16le -i record_to_pcm.pcm -c:a libfdk_aac pcm_to_aac.aac
    ffmpeg version 4.3.2 Copyright (c) 2000-2021 the FFmpeg developers
      built with gcc 10.2.0 (Rev6, Built by MSYS2 project)
      configuration: --prefix=/usr/local/ffmpeg --enable-shared --disable-static --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264
      libavutil      56. 51.100 / 56. 51.100
      libavcodec     58. 91.100 / 58. 91.100
      libavformat    58. 45.100 / 58. 45.100
      libavdevice    58. 10.100 / 58. 10.100
      libavfilter     7. 85.100 /  7. 85.100
      libswscale      5.  7.100 /  5.  7.100
      libswresample   3.  7.100 /  3.  7.100
      libpostproc    55.  7.100 / 55.  7.100
    [s16le @ 00000000006e9240] Estimating duration from bitrate, this may be inaccurate
    Guessed Channel Layout for Input Stream #0.0 : stereo
    Input #0, s16le, from 'record_to_pcm.pcm':
      Duration: 00:00:05.50, bitrate: 1411 kb/s
        Stream #0:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s
    Stream mapping:
      Stream #0:0 -> #0:0 (pcm_s16le (native) -> aac (libfdk_aac))
    Press [q] to stop, [?] for help
    Output #0, adts, to 'pcm_to_aac.aac':
      Metadata:
        encoder         : Lavf58.45.100
        Stream #0:0: Audio: aac (libfdk_aac), 44100 Hz, stereo, s16, 128 kb/s
        Metadata:
          encoder         : Lavc58.91.100 libfdk_aac
    size=      87kB time=00:00:05.50 bitrate= 130.0kbits/s speed= 124x
    video:0kB audio:87kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%
    

    查看aac文件信息

    PS G:\Resource> ffprobe .\pcm_to_aac.aac
    ffprobe version 4.3.2 Copyright (c) 2007-2021 the FFmpeg developers
      built with gcc 10.2.0 (Rev6, Built by MSYS2 project)
      configuration: --prefix=/usr/local/ffmpeg --enable-shared --disable-static --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264
      libavutil      56. 51.100 / 56. 51.100
      libavcodec     58. 91.100 / 58. 91.100
      libavformat    58. 45.100 / 58. 45.100
      libavdevice    58. 10.100 / 58. 10.100
      libavfilter     7. 85.100 /  7. 85.100
      libswscale      5.  7.100 /  5.  7.100
      libswresample   3.  7.100 /  3.  7.100
      libpostproc    55.  7.100 / 55.  7.100
    [aac @ 000000000078b240] Estimating duration from bitrate, this may be inaccurate
    Input #0, aac, from '.\pcm_to_aac.aac':
      Duration: 00:00:05.86, bitrate: 121 kb/s
        Stream #0:0: Audio: aac (LC), 44100 Hz, stereo, fltp, 121 kb/s
    

    pcm文件大小:970200字节
    aac文件大小 : 89411字节

    970200 / 89411 ≈ 10.85

    压缩了10-11倍

    PS : 图片内名称有误, 名字就先忽略吧, 正确的是pcm_to_aac

    播放器播放



    MAC平台下, 命令行转码

    ~/Desktop 6s ❯ ffmpeg -ar 44100 -ac 2 -f f32le -i record_to_pcm.pcm -c:a libfdk_aac out.aac
    ffmpeg version 4.4.git Copyright (c) 2000-2021 the FFmpeg developers
      built with Apple clang version 12.0.0 (clang-1200.0.32.29)
      configuration: --prefix=/usr/local/ffmpeg --enable-shared --disable-static --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265
      libavutil      57.  0.100 / 57.  0.100
      libavcodec     59.  3.101 / 59.  3.101
      libavformat    59.  4.100 / 59.  4.100
      libavdevice    59.  0.100 / 59.  0.100
      libavfilter     8.  0.103 /  8.  0.103
      libswscale      6.  0.100 /  6.  0.100
      libswresample   4.  0.100 /  4.  0.100
      libpostproc    56.  0.100 / 56.  0.100
    [f32le @ 0x7fbfa8c0dc00] Estimating duration from bitrate, this may be inaccurate
    Guessed Channel Layout for Input Stream #0.0 : stereo
    Input #0, f32le, from 'record_to_pcm.pcm':
      Duration: 00:00:06.75, bitrate: 2822 kb/s
      Stream #0:0: Audio: pcm_f32le, 44100 Hz, stereo, flt, 2822 kb/s
    Stream mapping:
      Stream #0:0 -> #0:0 (pcm_f32le (native) -> aac (libfdk_aac))
    Press [q] to stop, [?] for help
    Output #0, adts, to 'out.aac':
      Metadata:
        encoder         : Lavf59.4.100
      Stream #0:0: Audio: aac, 44100 Hz, stereo, s16, 128 kb/s
        Metadata:
          encoder         : Lavc59.3.101 libfdk_aac
    size=     107kB time=00:00:06.75 bitrate= 129.2kbits/s speed=75.9x    
    video:0kB audio:107kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%
    


    常用参数

    设置输出比特率 -b:a

    比如 -b:a 96k

    ffmpeg -i in.wav -c:a libfdk_aac -b:a 96k out.aac
    

    设置输出规格 -profile:a

    描述
    aac_low Low Complexity AAC (LC)
    aac_he High Efficiency AAC (HE-AAC)
    aac_he_v2 High Efficiency AAC version 2 (HE-AACv2)
    aac_ld Low Delay AAC (LD)
    aac_eld Enhanced Low Delay AAC (ELD)

    一旦设置了输出规格,会自动设置一个合适的输出比特率,也可以用过-b:a自行设置输出比特率。

    ffmpeg -i in.wav -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k out.aac
    

    开启VBR模式 -vbr

    如果开启了VBR模式,-b:a选项将会被忽略,但-profile:a选项仍然有效
    取值范围是0 ~ 5

    • 0:默认值,关闭VBR模式,开启CBR模式(Constant Bit Rate,固定比特率)
    • 1:质量最低(但是音质仍旧很棒)
    • 5:质量最高
    VBR kbps/channel Audio Object Type
    1 20-32 LC、HE、HEv2
    2 32-40 LC、HE、HEv2
    3 48-56 LC、HE、HEv2
    4 64-72 LC
    5 96-112 LC
    ffmpeg -i in.wav -c:a libfdk_aac -vbr 1 out.aac
    


    AAC编码相关函数

    avcodec_find_encoder_by_name
    avcodec_alloc_context3
    avcodec_open2
    av_frame_alloc
    av_frame_get_buffer
    av_packet_alloc
    avcodec_send_frame
    avcodec_receive_packet



    AAC编码逻辑

    源文件 ==》AVFrame ==》 编码器 ==> AVPacket ==》输出文件


    编码器 AVCodec
    typedef struct AVCodec {
        ......
    } AVCodec;
    
    
    编码上下文 AVCodecContext
    typedef struct AVCodecContext {
        ......
    }
    
    (原始)音频或视频数据 AVFrame
    /**
    * 此结构描述解码(原始)音频或视频数据。
     *
     * 必须使用 av_frame_alloc() 分配 AVFrame。
     * 请注意,这仅分配 AVFrame 本身,必须通过其他方式管理数据缓冲区(见下文)。
     * 必须使用 av_frame_free() 释放 AVFrame。
     *
     * AVFrame 通常分配一次,然后多次重用以保存不同的数据
     * (例如,单个 AVFrame 用于保存从解码器接收到的帧)。
     * 在这种情况下,av_frame_unref() 将释放框架持有的任何引用,
     * 并将其重置为原始干净状态,然后再重新使用。
     *
     * AVFrame 描述的数据通常通过 AVBuffer API 进行引用计数。
     * 底层缓冲区引用存储在 AVFrame.buf / AVFrame.extended_buf 中。
     * 如果设置了至少一个引用,即如果 AVFrame.buf[0] != NULL,则认为 AVFrame 被引用计数。在这种情况下,
     * 每个数据平面都必须包含在 AVFrame.buf 或 AVFrame.extended_buf 中的缓冲区之一中。
     * 可能有一个用于所有数据的缓冲区,或者每个平面有一个单独的缓冲区,或者介于两者之间。
     *
     * sizeof(AVFrame) 不是公共 ABI 的一部分,因此可能会在末尾添加新字段,并稍作改动。
     *
     * 字段可以通过 AVOptions 访问,所使用的名称字符串与可通过 AVOptions 访问的字段的 C 结构字段名称相匹配。 
     * AVFrame 的 AVClass 可以从 avcodec_get_frame_class() 获得
     */
    typedef struct AVFrame {
    #define AV_NUM_DATA_POINTERS 8
        ......
    }
    
    
    存储压缩数据结构 AVPacket
    /**
     * 此结构存储压缩数据。
     * 它通常由demuxers导出,然后作为输入传递给decoders,或作为encoders的输出接收,然后传递给muxers。
     *
     * 对于视频,它通常应包含一个压缩帧。对于音频,它可能包含多个压缩帧。Encoders可以输出空包,没有压缩数据,只包含边数据(例如,在编码结束时更新一些流参数)。
     *
     * AVPacket 是 FFmpeg 中为数不多的结构体之一,其大小是公共 ABI 的一部分。因此,它可以在堆栈上分配,并且在没有 libavcodec 和 libavformat 主要碰撞的情况下不能向其中添加新字段。
     *
     * 数据所有权的语义取决于 buf 字段。
     * 如果设置,则数据包数据是动态分配的,并且无限期有效,直到调用 av_packet_unref() 将引用计数减少到 0。
     *
     * 如果 buf 字段未设置 av_packet_ref() 将复制而不是增加引用计数。
     *
     * 边数据总是用av_malloc()分配,由av_packet_ref()复制,由av_packet_unref()释放。
     *
     * @see av_packet_ref
     * @see av_packet_unref
     */
    typedef struct AVPacket{
        ......
    }
    
    
    查找编码器 avcodec_find_encoder
    /**
      * 根据解码器ID值查找匹配的编码器。
      *
      * @param id 请求编码器的 AVCodecID
      * @return 如果找到一个编码器,否则为 NULL。
      */
    AVCodec *avcodec_find_encoder(enum AVCodecID id);
    
    编码器上下文 avcodec_alloc_context3
    /**
      * 分配一个 AVCodecContext 并将其字段设置为默认值。 这
      * 应使用 avcodec_free_context() 释放结果结构。
      *
      * @param codec 
      *  如果非空,分配私有数据并初始化给定编解码器的默认值。  然后使用不同的编解码器调用 avcodec_open2() 是非法的。
      *  如果为 NULL,则不会初始化特定于编解码器的默认值,这可能会导致默认设置欠佳(这主要对编码器很重要,例如 libx264)。
      *
      * @return 一个 AVCodecContext 填充默认值或失败时为 NULL。
      */
    AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
    


    代码实现 Demo

    目前针对于的是对PCM源文件进行AAC编码


    #define ERROR_BUF(ret) \
        char errbuf[1024]; \
        av_strerror(ret, errbuf, sizeof (errbuf));
    
    
    #define CHECK_IF_ERROR_BUF_END(ret, funcStr) \
        if (ret) { \
            ERROR_BUF(ret); \
            qDebug() << #funcStr << " error :" << errbuf; \
            goto end; \
        }
    
    
    
    #ifdef Q_OS_WIN
        #define IN_PCM_FILEPATH "G:/Resource/record_to_pcm.pcm"
        #define OUT_AAC_FILEPATH "G:/Resource/pcm_to_aac.aac"
    #else
        #define IN_PCM_FILEPATH "/Users/liliguang/Desktop/record_to_pcm.pcm"
        #define OUT_AAC_FILEPATH "/Users/liliguang/Desktop/pcm_to_aac.aac"
    #endif
    
    
    // 检查编码器是否支持当前编码格式
    static int check_sample_fmt(const AVCodec *codec,
                                enum AVSampleFormat sample_fmt)
    {
        const enum AVSampleFormat *p = codec->sample_fmts;
        while (*p != AV_SAMPLE_FMT_NONE) {
            if (*p == sample_fmt)
                return 1;
            p++;
        }
        return 0;
    }
    
    // 音频编码
    // 返回负数:中途出现了错误
    // 返回0:编码操作正常完成
    static int encode(AVCodecContext *ctx,
                      AVFrame *frame,
                      AVPacket *pkt,
                      QFile &outFile)
    {
        // 发送数据到编码器
        int ret = avcodec_send_frame(ctx, frame);
        if (ret < 0) {
            ERROR_BUF(ret);
            qDebug() << "avcodec_send_frame error" << errbuf;
            return ret;
        }
    
        // 不断从编码器中取出编码后的数据
        // while (ret >= 0)
        while (true) {
            ret = avcodec_receive_packet(ctx, pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                // 继续读取数据到frame,然后送到编码器
                return 0;
            } else if (ret < 0) { // 其他错误
                return ret;
            }
    
            // 成功从编码器拿到编码后的数据
            // 将编码后的数据写入文件
            outFile.write((char *) pkt->data, pkt->size);
    
            // 释放pkt内部的资源
            av_packet_unref(pkt);
        }
    
    }
    
    
    void AACEncodeThread::run() {
        qDebug() << "AACEncodeThread run ";
    
        // 输入输出文件
        const char *infilename;
        const char *outfilename;
    
        // 编码器
        const AVCodec *codec;
        // 编码器上下文
        AVCodecContext *codecCtx= NULL;
        // 源文件数据源存储结构指针
        AVFrame *frame;
        // 编码文件数据源存储结构指针
        AVPacket *pkt;
    
        int check_sample_fmt_Ret;
        int avcodec_open2_Ret;
        int av_frame_get_buffer_Ret;
    
        int infileOpen_Ret;
        int outfileOpen_Ret;
    
        int readFile_Ret;
    
    
        infilename = IN_PCM_FILEPATH;
        outfilename = OUT_AAC_FILEPATH;
    
        QFile inFile(infilename);
        QFile outFile(outfilename);
    
    
        // 打开编码器 , 因为已经编译过FFmpeg, 所以拿到的aac是 fdk-aac
        codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
        CHECK_IF_ERROR_BUF_END(!codec, "avcodec_find_encoder");
    
        // 创建编码器上下文
        codecCtx = avcodec_alloc_context3(codec);
        CHECK_IF_ERROR_BUF_END(!codecCtx, "avcodec_alloc_context3");
    
        // 设置编码器上下文参数
        codecCtx->sample_rate = 44100;
        codecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP; // planner格式
        codecCtx->channel_layout = AV_CH_LAYOUT_STEREO;
        codecCtx->channels = av_get_channel_layout_nb_channels(codecCtx->channel_layout);
    
        // 不同的比特率影响不同的编码大小. 
        // codecCtx->bit_rate = (44100 * 32 * codecCtx->channels);
        codecCtx->bit_rate = 64000;
    
        // 检查编码器支持的样本格式
        check_sample_fmt_Ret = check_sample_fmt(codec, codecCtx->sample_fmt);
        CHECK_IF_ERROR_BUF_END(!check_sample_fmt_Ret, "check_sample_fmt");
    
        // 打开编码器
        avcodec_open2_Ret = avcodec_open2(codecCtx,codec,nullptr);
        CHECK_IF_ERROR_BUF_END(avcodec_open2_Ret, "avcodec_open2");
    
        // 打开源文件
        infileOpen_Ret = !inFile.open(QFile::ReadOnly);
        CHECK_IF_ERROR_BUF_END(infileOpen_Ret, "sourceFile.open");
    
        // 打开源文件
        outfileOpen_Ret = !outFile.open(QFile::WriteOnly);
        CHECK_IF_ERROR_BUF_END(outfileOpen_Ret, "sourceFile.outFile");
    
        // 创建输出Packet
        pkt = av_packet_alloc();
        CHECK_IF_ERROR_BUF_END(!pkt, "av_packet_alloc");
    
        // 创建AVFrame结构体本身
        frame = av_frame_alloc();
        CHECK_IF_ERROR_BUF_END(!frame, "av_frame_alloc");
    
        // 设置frame必要信息
        frame->format = codecCtx->sample_fmt;//样本格式
        frame->nb_samples = codecCtx->frame_size;//每个声道的样本数量大小
        frame->channel_layout = codecCtx->channel_layout; //声道布局
    
    
        // 为音频或视频数据分配新的缓冲区。
        // 在调用此函数之前,必须在框架上设置以下字段:
        // - 格式(视频的像素格式,音频的样本格式)
        // - 视频的宽度和高度
        // - 用于音频的 nb_samples 和 channel_layout
        //
        // 所以需要先设置frame->format, frame->nb_samples , frame->channel_layout
        //
        av_frame_get_buffer_Ret = av_frame_get_buffer(frame,0);
        CHECK_IF_ERROR_BUF_END(av_frame_get_buffer_Ret < 0, "av_frame_get_buffer");
    
    
        // 编码
        // 源文件 ==> (AVFrame)输入缓冲区 ==> 编码器 ==> (AVPacket)输出缓冲区 ==> 输出文件
        while( (readFile_Ret = inFile.read((char *)frame->data[0],frame->linesize[0])) > 0 ) {
            if (readFile_Ret < frame->linesize[0] ) {
                
                int bytes = av_get_bytes_per_sample((AVSampleFormat) frame->format); //每个样本大小
                int ch = av_get_channel_layout_nb_channels(frame->channel_layout); // 通道数
                frame->nb_samples = readFile_Ret / (bytes * ch); // 样本数量 /  每个样本的总大小
    
            } else {
                // 编码
                CHECK_IF_ERROR_BUF_END(encode(codecCtx, frame, pkt, outFile) < 0, "encode");
            }
    
        }
    
        // 在读取最后一次, 冲刷最后一次缓冲区数据
        encode(codecCtx,nullptr,pkt,outFile);
    
    end:
    
        // 关闭文件
        inFile.close();
        outFile.close();
    
        // 释放资源
        av_frame_free(&frame);
        av_packet_free(&pkt);
        avcodec_free_context(&codecCtx);
        qDebug() << "AACEncodeThread end ";
    }
    


    总结 :

    AAC编码的简略逻辑 : 源文件 ==》 AVFrame ==》编码器 ==》AVPacket ==> 输出文件

    细致化:

    源文件

    inFile.open(QFile::ReadOnly) 打开文件读取文件内容到缓冲区

    AVFrame 发送处理avcodec_send_frame

    AVCodec 编码器

    AVPacket 接受处理avcodec_receive_packet

    outFile.open(QFile::WriteOnly) 从缓冲区读取数据写入文件中去

    编码完成

    相关文章

      网友评论

        本文标题:音视频-AAC编码

        本文链接:https://www.haomeiwen.com/subject/zfdnultx.html