采样格式
必须是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) 从缓冲区读取数据写入文件中去
编码完成