音視頻-AAC編碼

采樣格式

必須是16位整數(shù)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輸入數(shù)據(jù)的參數(shù)

-ar 44100 -ac 2 -f s16le

設(shè)置音頻編碼器, -c:a

c表示codec(編解碼器),a表示audio(音頻)

等價寫法
  • -codec:a
  • -acodec

需要注意的是:這個參數(shù)要寫在aac文件那邊,也就是屬于輸出參數(shù)
默認(rèn)生成的aac文件是LC規(guī)格的。

Win平臺下, 命令行轉(zhuǎn)碼

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文件大?。?70200字節(jié)
aac文件大小 : 89411字節(jié)

970200 / 89411 ≈ 10.85

壓縮了10-11倍

PS : 圖片內(nèi)名稱有誤, 名字就先忽略吧, 正確的是pcm_to_aac

播放器播放



MAC平臺下, 命令行轉(zhuǎn)碼

~/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%


常用參數(shù)

設(shè)置輸出比特率 -b:a

比如 -b:a 96k

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

設(shè)置輸出規(guī)格 -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)

一旦設(shè)置了輸出規(guī)格,會自動設(shè)置一個合適的輸出比特率,也可以用過-b:a自行設(shè)置輸出比特率。

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:默認(rèn)值,關(guān)閉VBR模式,開啟CBR模式(Constant Bit Rate,固定比特率)
  • 1:質(zhì)量最低(但是音質(zhì)仍舊很棒)
  • 5:質(zhì)量最高
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編碼相關(guān)函數(shù)

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 {
    ......
}
(原始)音頻或視頻數(shù)據(jù) AVFrame
/**
* 此結(jié)構(gòu)描述解碼(原始)音頻或視頻數(shù)據(jù)。
 *
 * 必須使用 av_frame_alloc() 分配 AVFrame。
 * 請注意,這僅分配 AVFrame 本身,必須通過其他方式管理數(shù)據(jù)緩沖區(qū)(見下文)。
 * 必須使用 av_frame_free() 釋放 AVFrame。
 *
 * AVFrame 通常分配一次,然后多次重用以保存不同的數(shù)據(jù)
 * (例如,單個 AVFrame 用于保存從解碼器接收到的幀)。
 * 在這種情況下,av_frame_unref() 將釋放框架持有的任何引用,
 * 并將其重置為原始干凈狀態(tài),然后再重新使用。
 *
 * AVFrame 描述的數(shù)據(jù)通常通過 AVBuffer API 進行引用計數(shù)。
 * 底層緩沖區(qū)引用存儲在 AVFrame.buf / AVFrame.extended_buf 中。
 * 如果設(shè)置了至少一個引用,即如果 AVFrame.buf[0] != NULL,則認(rèn)為 AVFrame 被引用計數(shù)。在這種情況下,
 * 每個數(shù)據(jù)平面都必須包含在 AVFrame.buf 或 AVFrame.extended_buf 中的緩沖區(qū)之一中。
 * 可能有一個用于所有數(shù)據(jù)的緩沖區(qū),或者每個平面有一個單獨的緩沖區(qū),或者介于兩者之間。
 *
 * sizeof(AVFrame) 不是公共 ABI 的一部分,因此可能會在末尾添加新字段,并稍作改動。
 *
 * 字段可以通過 AVOptions 訪問,所使用的名稱字符串與可通過 AVOptions 訪問的字段的 C 結(jié)構(gòu)字段名稱相匹配。 
 * AVFrame 的 AVClass 可以從 avcodec_get_frame_class() 獲得
 */
typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8
    ......
}

存儲壓縮數(shù)據(jù)結(jié)構(gòu) AVPacket
/**
 * 此結(jié)構(gòu)存儲壓縮數(shù)據(jù)。
 * 它通常由demuxers導(dǎo)出,然后作為輸入傳遞給decoders,或作為encoders的輸出接收,然后傳遞給muxers。
 *
 * 對于視頻,它通常應(yīng)包含一個壓縮幀。對于音頻,它可能包含多個壓縮幀。Encoders可以輸出空包,沒有壓縮數(shù)據(jù),只包含邊數(shù)據(jù)(例如,在編碼結(jié)束時更新一些流參數(shù))。
 *
 * AVPacket 是 FFmpeg 中為數(shù)不多的結(jié)構(gòu)體之一,其大小是公共 ABI 的一部分。因此,它可以在堆棧上分配,并且在沒有 libavcodec 和 libavformat 主要碰撞的情況下不能向其中添加新字段。
 *
 * 數(shù)據(jù)所有權(quán)的語義取決于 buf 字段。
 * 如果設(shè)置,則數(shù)據(jù)包數(shù)據(jù)是動態(tài)分配的,并且無限期有效,直到調(diào)用 av_packet_unref() 將引用計數(shù)減少到 0。
 *
 * 如果 buf 字段未設(shè)置 av_packet_ref() 將復(fù)制而不是增加引用計數(shù)。
 *
 * 邊數(shù)據(jù)總是用av_malloc()分配,由av_packet_ref()復(fù)制,由av_packet_unref()釋放。
 *
 * @see av_packet_ref
 * @see av_packet_unref
 */
typedef struct AVPacket{
    ......
}

查找編碼器 avcodec_find_encoder
/**
  * 根據(jù)解碼器ID值查找匹配的編碼器。
  *
  * @param id 請求編碼器的 AVCodecID
  * @return 如果找到一個編碼器,否則為 NULL。
  */
AVCodec *avcodec_find_encoder(enum AVCodecID id);
編碼器上下文 avcodec_alloc_context3
/**
  * 分配一個 AVCodecContext 并將其字段設(shè)置為默認(rèn)值。 這
  * 應(yīng)使用 avcodec_free_context() 釋放結(jié)果結(jié)構(gòu)。
  *
  * @param codec 
  *  如果非空,分配私有數(shù)據(jù)并初始化給定編解碼器的默認(rèn)值。  然后使用不同的編解碼器調(diào)用 avcodec_open2() 是非法的。
  *  如果為 NULL,則不會初始化特定于編解碼器的默認(rèn)值,這可能會導(dǎo)致默認(rèn)設(shè)置欠佳(這主要對編碼器很重要,例如 libx264)。
  *
  * @return 一個 AVCodecContext 填充默認(rèn)值或失敗時為 NULL。
  */
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);


代碼實現(xiàn) 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

// 檢查編碼器是否支持當(dāng)前編碼格式
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;
}
// 音頻編碼
// 返回負(fù)數(shù):中途出現(xiàn)了錯誤
// 返回0:編碼操作正常完成
static int encode(AVCodecContext *ctx,
                  AVFrame *frame,
                  AVPacket *pkt,
                  QFile &outFile)
{
    // 發(fā)送數(shù)據(jù)到編碼器
    int ret = avcodec_send_frame(ctx, frame);
    if (ret < 0) {
        ERROR_BUF(ret);
        qDebug() << "avcodec_send_frame error" << errbuf;
        return ret;
    }

    // 不斷從編碼器中取出編碼后的數(shù)據(jù)
    // while (ret >= 0)
    while (true) {
        ret = avcodec_receive_packet(ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            // 繼續(xù)讀取數(shù)據(jù)到frame,然后送到編碼器
            return 0;
        } else if (ret < 0) { // 其他錯誤
            return ret;
        }

        // 成功從編碼器拿到編碼后的數(shù)據(jù)
        // 將編碼后的數(shù)據(jù)寫入文件
        outFile.write((char *) pkt->data, pkt->size);

        // 釋放pkt內(nèi)部的資源
        av_packet_unref(pkt);
    }

}

void AACEncodeThread::run() {
    qDebug() << "AACEncodeThread run ";

    // 輸入輸出文件
    const char *infilename;
    const char *outfilename;

    // 編碼器
    const AVCodec *codec;
    // 編碼器上下文
    AVCodecContext *codecCtx= NULL;
    // 源文件數(shù)據(jù)源存儲結(jié)構(gòu)指針
    AVFrame *frame;
    // 編碼文件數(shù)據(jù)源存儲結(jié)構(gòu)指針
    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);


    // 打開編碼器 , 因為已經(jīng)編譯過FFmpeg, 所以拿到的aac是 fdk-aac
    codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    CHECK_IF_ERROR_BUF_END(!codec, "avcodec_find_encoder");

    // 創(chuàng)建編碼器上下文
    codecCtx = avcodec_alloc_context3(codec);
    CHECK_IF_ERROR_BUF_END(!codecCtx, "avcodec_alloc_context3");

    // 設(shè)置編碼器上下文參數(shù)
    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");

    // 創(chuàng)建輸出Packet
    pkt = av_packet_alloc();
    CHECK_IF_ERROR_BUF_END(!pkt, "av_packet_alloc");

    // 創(chuàng)建AVFrame結(jié)構(gòu)體本身
    frame = av_frame_alloc();
    CHECK_IF_ERROR_BUF_END(!frame, "av_frame_alloc");

    // 設(shè)置frame必要信息
    frame->format = codecCtx->sample_fmt;//樣本格式
    frame->nb_samples = codecCtx->frame_size;//每個聲道的樣本數(shù)量大小
    frame->channel_layout = codecCtx->channel_layout; //聲道布局


    // 為音頻或視頻數(shù)據(jù)分配新的緩沖區(qū)。
    // 在調(diào)用此函數(shù)之前,必須在框架上設(shè)置以下字段:
    // - 格式(視頻的像素格式,音頻的樣本格式)
    // - 視頻的寬度和高度
    // - 用于音頻的 nb_samples 和 channel_layout
    //
    // 所以需要先設(shè)置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)輸入緩沖區(qū) ==> 編碼器 ==> (AVPacket)輸出緩沖區(qū) ==> 輸出文件
    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); // 通道數(shù)
            frame->nb_samples = readFile_Ret / (bytes * ch); // 樣本數(shù)量 /  每個樣本的總大小

        } else {
            // 編碼
            CHECK_IF_ERROR_BUF_END(encode(codecCtx, frame, pkt, outFile) < 0, "encode");
        }

    }

    // 在讀取最后一次, 沖刷最后一次緩沖區(qū)數(shù)據(jù)
    encode(codecCtx,nullptr,pkt,outFile);

end:

    // 關(guān)閉文件
    inFile.close();
    outFile.close();

    // 釋放資源
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&codecCtx);
    qDebug() << "AACEncodeThread end ";
}


總結(jié) :

AAC編碼的簡略邏輯 : 源文件 ==》 AVFrame ==》編碼器 ==》AVPacket ==> 輸出文件

細(xì)致化:

源文件

inFile.open(QFile::ReadOnly) 打開文件讀取文件內(nèi)容到緩沖區(qū)

AVFrame 發(fā)送處理avcodec_send_frame

AVCodec 編碼器

AVPacket 接受處理avcodec_receive_packet

outFile.open(QFile::WriteOnly) 從緩沖區(qū)讀取數(shù)據(jù)寫入文件中去

編碼完成

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容