本文将分别通过命令行、编程2种方式进行AAC编码实战,使用的编码库是libfdk_aac。
要求
fdk-aac对输入的PCM数据是有参数要求 的,如果参数不对,就会出现以下错误:
[libfdk_aac @ 0x7fa3db033000] Unable to initialize the encoder: SBR library initialization error
Error initializing output stream 0:0 -- Error while opening encoder for output stream
Conversion failed!
采样格式
必须是16位整数PCM。
采样率
支持的采样率有(Hz):
8000、11025、12000、16000、22050、24000、32000
44100、48000、64000、88200、96000
命令行
基本使用
最简单的用法如下所示:
ffmpeg -ar 44100 -ac 2 -f s16le -i in.pcm -c:a libfdk_aac out.aac
ffmpeg -i in.wav -c:a libfdk_aac out.aac
-ar 44100 -ac 2 -f s16le
-c:a
设置音频编码器
c 表示codec(编解码器),a 表示audio(音频)
等价写法
需要注意的是:这个参数要写在aac文件那边,也就是属于输出 参数
默认生成的aac文件是LC规格的。
ffprobe out.aac
Audio: aac (LC), 44100 Hz, stereo, fltp, 120 kb/s
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)
一旦设置了输出规格,会自动设置一个合适的输出比特率
ffmpeg -i in.wav -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k out.aac
-vbr
开启VBR 模式(Variable Bit Rate,可变比特率)
如果开启了VBR模式,-b:a 选项将会被忽略,但*-profile:a*选项仍然有效
取值范围是0 ~ 5
0:默认值 ,关闭VBR模式,开启CBR模式(Constant Bit Rate,固定比特率)
1:质量最低(但是音质仍旧很棒)
5:质量最高
VBR
kbps/channel
AOTs
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
AOT是Audio Object Type的简称。
ffmpeg -i in.wav -c:a libfdk_aac -vbr 1 out.aac
文件格式
我曾在《重识音频》 中提到,AAC编码的文件扩展名主要有3种:aac、m4a、mp4。
ffmpeg -i in.wav -c:a libfdk_aac out.m4a
ffmpeg -i in.wav -c:a libfdk_aac out.mp4
编程
AAC 编码流程:
需要用到2个库:
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
函数声明
我们最终会将PCM转AAC的操作封装到一个函数中。
extern "C" {
#include <libavcodec/avcodec.h>
}
typedef struct {
const char *filename;
int sampleRate;
AVSampleFormat sampleFmt;
int chLayout;
} AudioEncodeSpec;
class FFmpegUtil {
public :
FFmpegUtil ();
static void aacEncode (AudioEncodeSpec &in,
const char *outFilename);
};
函数实现
变量定义
AVCodec *codec = nullptr ;
AVCodecContext *ctx = nullptr ;
AVFrame *frame = nullptr ;
AVPacket *pkt = nullptr ;
int ret = 0 ;
QFile inFile (in.filename) ;
QFile outFile (outFilename) ;
获取编码器
下面的代码可以获取FFmpeg默认的AAC编码器(并不是libfdk_aac)。
AVCodec *codec1 = avcodec_find_encoder (AV_CODEC_ID_AAC);
AVCodec *codec2 = avcodec_find_encoder_by_name ("aac" );
qDebug () << (codec1 == codec2);
qDebug () << codec1->name;
不过我们最终要获取的是libfdk_aac。
codec = avcodec_find_encoder_by_name ("libfdk_aac" );
if (!codec) {
qDebug () << "encoder libfdk_aac not found" ;
return ;
}
检查采样格式
接下来检查编码器是否支持当前的采样格式。
if (!check_sample_fmt (codec, in.sampleFmt)) {
qDebug () << "Encoder does not support sample format"
<< av_get_sample_fmt_name (in.sampleFmt);
return ;
}
检查函数check_sample_fmt 的实现如下所示。
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 ;
}
创建上下文
avcodec_alloc_context3 后面的3 说明这已经是第3版API,取代了此前的avcodec_alloc_context 和avcodec_alloc_context2 。
ctx = avcodec_alloc_context3 (codec);
if (!ctx) {
qDebug () << "avcodec_alloc_context3 error" ;
return ;
}
ctx->sample_fmt = in.sampleFmt;
ctx->sample_rate = in.sampleRate;
ctx->channel_layout = in.chLayout;
ctx->bit_rate = 32000 ;
ctx->profile = FF_PROFILE_AAC_HE_V2;
打开编码器
ret = avcodec_open2 (ctx, codec, nullptr );
if (ret < 0 ) {
ERROR_BUF (ret);
qDebug () << "avcodec_open2 error" << errbuf;
goto end;
}
如果是想设置一些libfdk_aac特有的参数(比如vbr),可以通过options参数传递。
AVDictionary *options = nullptr ;
av_dict_set (&options, "vbr" , "1" , 0 );
ret = avcodec_open2 (ctx, codec, &options);
创建AVFrame
AVFrame用来存放编码前的数据。
frame = av_frame_alloc ();
if (!frame) {
qDebug () << "av_frame_alloc error" ;
goto end;
}
frame->nb_samples = ctx->frame_size;
frame->format = ctx->sample_fmt;
frame->channel_layout = ctx->channel_layout;
ret = av_frame_get_buffer (frame, 0 );
if (ret < 0 ) {
ERROR_BUF (ret);
qDebug () << "av_frame_get_buffer error" << errbuf;
goto end;
}
创建AVPacket
pkt = av_packet_alloc ();
if (!pkt) {
qDebug () << "av_packet_alloc error" ;
goto end;
}
打开文件
if (!inFile.open (QFile::ReadOnly)) {
qDebug () << "file open error" << in.filename;
goto end;
}
if (!outFile.open (QFile::WriteOnly)) {
qDebug () << "file open error" << outFilename;
goto end;
}
开始编码
while ((ret = inFile.read ((char *) frame->data[0 ],
frame->linesize[0 ])) > 0 ) {
if (ret < frame->linesize[0 ]) {
int chs = av_get_channel_layout_nb_channels (frame->channel_layout);
int bytes = av_get_bytes_per_sample ((AVSampleFormat) frame->format);
frame->nb_samples = ret / (chs * bytes);
}
if (encode (ctx, frame, pkt, outFile) < 0 ) {
goto end;
}
}
encode (ctx, nullptr , pkt, outFile);
encode 函数专门用来进行编码,它的实现如下所示。
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 (true ) {
ret = avcodec_receive_packet (ctx, pkt);
if (ret == AVERROR (EAGAIN) || ret == AVERROR_EOF) {
return 0 ;
} else if (ret < 0 ) {
ERROR_BUF (ret);
qDebug () << "avcodec_receive_packet error" << errbuf;
return ret;
}
outFile.write ((char *) pkt->data, pkt->size);
av_packet_unref (pkt);
}
return 0 ;
}
资源回收
end:
inFile.close ();
outFile.close ();
av_frame_free (&frame);
av_packet_free (&pkt);
avcodec_free_context (&ctx);
函数调用
#ifdef Q_OS_WIN
#define IN_FILENAME "../test/44100_s16le_2.pcm"
#define OUT_FILENAME "../test/out.aac"
#else
#define IN_FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/44100_s16le_2.pcm"
#define OUT_FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/out.acc"
#endif
AudioEncodeSpec in;
in.filename = IN_FILENAME;
in.sampleFmt = AV_SAMPLE_FMT_S16;
in.sampleRate = 44100 ;
in.chLayout = AV_CH_LAYOUT_STEREO;
FFmpegUtil::aacEncode (in,OUT_FILENAME);
注意
上面的开始编码步骤的while循环里最开始没有下面的代码运行代码生成out1.aac文件
if (ret < frame->linesize[0 ]) {
int chs = av_get_channel_layout_nb_channels (frame->channel_layout);
int bytes = av_get_bytes_per_sample ((AVSampleFormat) frame->format);
frame->nb_samples = ret / (chs * bytes);
}
然后我们在使用ffmpeg命令方式生成out2.aac文件
ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -c:a libfdk_aac -b:a 32k -profile:a aac_he_v2 out2.aac
可以发现代码中生成的和ffmpeg命令生成的多5个字节,这是怎么回事呢? 这是因为,在读取pcm文件的时候,当最后一次读取的时候填不满AVFrame缓存区,例如缓存区大小是4096字节,但是最后一次读取pcm文件可能是1024字节,没法填满缓冲区的4096字节,因此在送入编码器的时候,编码器直接把缓冲区的4096全部进行编码,就会导致多余一些无效字节。
代码链接
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!