采樣格式
必須是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ù)寫入文件中去
編碼完成