一、前言
本系列文章是对音视频技能入门常识的整理和复习,为进一步深入体系研究音视频技能稳固根底。文章列表:
- 01-音视频技能中心常识|了解音频技能【移动通信技能的开展、声响的实质、深入了解音频】
- 02-音视频技能中心常识|建立开发环境【FFmpeg与Qt、Windows开发环境建立、Mac开发环境建立、Qt开发根底】
- 03-音视频技能中心常识|Qt开发根底【
.pro
文件的装备、Qt控件根底、信号与槽】 - 04-音视频技能中心常识|音频录制【命令行、C++编程】
- 05-音视频技能中心常识|音频播映【播映PCM、WAV、PCM转WAV、PCM转WAV、播映WAV】
- 06-音视频技能中心常识|音频重采样【音频重采样简介、用命令行进行重采样、经过编程重采样】
- 07-音视频技能中心常识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】
- 08-音视频技能中心常识|成像技能【重识图片、详解YUV、视频录制、显现BMP图片、显现YUV图片】
- 09-音视频技能中心常识|视频编码解码【了解H.264编码、H.264编码、H.264编码解码】
- 10-音视频技能中心常识|RTMP服务器建立【流媒体、服务器环境】
二、AAC编码
AAC(Advanced Audio Coding,译为:高档音频编码),是由Fraunhofer IIS、杜比实验室、AT&T、Sony、Nokia等公司共同开发的有损音频编码和文件格局。
1. 对比MP3
AAC被规划为MP3格局的后继产品,通常在相同的比特率下能够获得比MP3更高的声响质量,是iPhone、iPod、iPad、iTunes的标准音频格局。
AAC相较于MP3的改善包括:
- 更多的采样率挑选:8kHz ~ 96kHz,MP3为16kHz ~ 48kHz
- 更高的声道数上限:48个,MP3在MPEG-1形式下为最多双声道,MPEG-2形式下5.1声道
- 改善的紧缩功用:以较小的文件巨细供给更高的质量
- 改善的解码效率:需求较少的处理能力进行解码
- ……
2. 标准
AAC是一个巨大家族,为了适应不同场合的需求,它有很多种标准可供挑选。下面列举其中的9种标准(Profile):
- MPEG-2 AAC LC:低复杂度标准(Low Complexity)
- MPEG-2 AAC Main:主标准
- MPEG-2 AAC SSR:可变采样率标准(Scaleable Sample Rate)
- MPEG-4 AAC LC:低复杂度标准(Low Complexity)
- 现在的手机比较常见的MP4文件中的音频部分运用了该标准
- MPEG-4 AAC Main:主标准
- MPEG-4 AAC SSR:可变采样率标准(Scaleable Sample Rate)
- MPEG-4 AAC LTP:长时期预测标准(Long Term Predicition)
- MPEG-4 AAC LD:低延迟标准(Low Delay)
- MPEG-4 AAC HE:高效率标准(High Efficiency)
最早是根据MPEG-2标准,称为:MPEG-2 AAC。后来MPEG-4标准在本来根底上增加了一些新技能,称为:MPEG-4 AAC。
3. LC和HE
尽管上面列举了9种标准,但咱们现在只需求把留意力放在常用的LC和HE上。下图很好的展现了从LC到HE的开展进程。
3.1 LC
LC适宜中等比特率,比方96kbps ~ 192kbps之间。
MPEG-4 AAC LC等价于:
- MPEG-2 AAC LC + PNS
PNS(Perceptual Noise Substitution)译为:感知噪声替代。
- PNS能够进步AAC的编码效率
3.2 HE
HE有v1和v2两个版本,适宜低比特率:
- v1:适宜48kbps ~ 64kbps
- v2:适宜低于32kbps,可在低至32kbps的比特率下供给接近CD质量的声响
3.2.1 v1
MPEG-4 AAC HE v1的别号:
- aacPlus v1
- eAAC
- AAC+
- CT-aacPlus(Coding Technologies)
- Coding Technologies是瑞典是一家技能公司,率先在AAC中运用了SBR技能
- 在2007年,被杜比实验室(Dolby Laboratories)以2.5亿美元收买
MPEG-4 AAC HE v1等价于:
- MPEG-4 AAC LC + SBR
SBR(Spectral Band Replication)译为:频段仿制。
- 是一种增强的紧缩技能
- 能够将高频信号存储在少数的SBR data中
- 解码器能够依据SBR data恢复出高频信号
3.2.2 v2
MPEG-4 AAC HE v2的别号:
- aacPlus v2
- AAC++
- eAAC+、Enhanced AAC+
MPEG-4 AAC HE v2等价于:
- MPEG-4 AAC HE v1 + PS
PS(Parametric Stereo)译为:参数立体声。
- 是一种有损的音频紧缩算法,能够进一步进步紧缩率
- 能够将左右声道信号组合成单声道信号,声道之间的差异信息存储到少数的PS data中(大概占2 ~ 3kbps)
- 解码器能够依据PS data中恢复出立体声信号
4. 编解码器
假如想对PCM数据进行AAC编码紧缩,那么就要用到AAC编码器(encoder)。 假如想将AAC编码后的数据解压出PCM数据,那么就要用到AAC解码器(decoder)。
这儿只列举几款常用的AAC编解码器:
-
Nero AAC
- 支撑LC/HE标准
- 现在现已停止开发维护
-
FFmpeg AAC
- 支撑LC标准
- FFmpeg官方内置的AAC编解码器,在libavcodec库中
- 编解码器姓名叫做aac
- 在开发过程中经过这个姓名找到编解码器
-
FAAC(Freeware Advanced Audio Coder)
- 支撑LC标准
- 能够集成到FFmpeg的libavcodec中
- 编解码器姓名叫做libfaac
- 在开发过程中经过这个姓名找到编解码器,终究调用FAAC库的功用
- 从2016年开端,FFmpeg现已移除了对FAAC的支撑
-
Fraunhofer FDK AAC
- 支撑LC/HE标准
- 现在质量最高的AAC编解码器
- 能够集成到FFmpeg的libavcodec中
- 编解码器姓名叫做libfdk_aac
- 在开发过程中经过这个姓名找到编解码器,终究调用FDK AAC库的功用
编码质量排名:Fraunhofer FDK AAC > FFmpeg AAC > FAAC。
5. FDK AAC
在网上下载的编译版FFmpeg,通常都是没有集成libfdk_aac的。能够经过命令行查看FFmpeg现在集成的AAC编解码器。
# windows
ffmpeg -codecs | findstr aac
# mac
ffmpeg -codecs | grep aac
我这边的输出成果是:
DEAIL. aac AAC (Advanced Audio Coding) (decoders: aac aac_fixed )
D.AIL. aac_latm AAC LATM (Advanced Audio Coding LATM syntax)
很显然,并没有包括libfdk_aac。
这儿给出1个比较推荐的计划:自己手动编译FFmpeg源码,将libfdk_aac集成到FFmpeg中。
- 自己手动编译的话,想集成啥就集成啥
- 能够把你想要的东西都塞到FFmpeg中,不想要的就删掉
- 也便是依据自己的需求对FFmpeg进行裁剪
三、编译FFmpeg
本文来详细解说一下:如安在Mac、Windows环境下成功编译FFmpeg。
1. 方针
这儿先提早阐明一下,终究希望到达的效果:
- 编译出ffmpeg、ffprobe、ffplay三个命令行东西
- 只发生动态库,不发生静态库
- 将fdk-aac、x264、x265集成到FFmpeg中
- x264、x265会在以后解说的视频模块中用到
2. 下载源码
下载源码ffmpeg-4.3.2.tar.xz,然后解压。
3. Mac编译
3.1 依靠项
-
brew install yasm
- ffmpeg的编译过程依靠yasm
- 若未装置yasm会呈现过错:nasm/yasm not found or too old. Use –disable-x86asm for a crippled build.
-
brew install sdl2
- ffplay依靠于sdl2
- 假如短少sdl2,就无法编译出ffplay
-
brew install fdk-aac
- 否则会呈现过错:ERROR: libfdk_aac not found
-
brew install x264
- 否则会呈现过错:ERROR: libx264 not found
-
brew install x265
- 否则会呈现过错:ERROR: libx265 not found
其实x264、x265、sdl2都在从前履行brew install ffmpeg的时分装置过了。
- 能够经过brew list的成果查看是否装置过
- brew list | grep fdk
- brew list | grep x26
- brew list | grep -E ‘fdk|x26’
- 假如现已装置过,能够不必再履行brew install
3.2 configure
首先进入源码目录。
# 我的源码放在了Downloads目录下
cd ~/Downloads/ffmpeg-4.3.2
然后履行源码目录下的configure脚本,设置一些编译参数,做一些编译前的准备工作。
./configure --prefix=/usr/local/ffmpeg --enable-shared --disable-static --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265
-
–prefix
- 用以指定编译好的FFmpeg装置到哪个目录
- 一般放到**/usr/local/ffmpeg**中即可
-
–enable-shared
- 生成动态库
-
–disable-static
- 不生成静态库
-
–enable-libfdk-aac
- 将fdk-aac内置到FFmpeg中
-
–enable-libx264
- 将x264内置到FFmpeg中
-
–enable-libx265
- 将x265内置到FFmpeg中
-
–enable-gpl
- x264、x265要求敞开GPL License
-
–enable-nonfree
- fdk-aac与GPL不兼容,需求经过敞开nonfree进行装备
你能够经过configure –help命令查看每一个装备项的效果。
./configure --help | grep static
# 成果如下所示
--disable-static do not build static libraries [no]
3.3 编译
接下来开端解析源代码目录中的Makefile文件,进行编译。-j8表明答应一起履行8个编译任务。
make -j8
对于经常在类Unix体系下触摸C/C++开发的小伙伴来说,Makefile必定是不陌生的。这儿给不了解Makefile的小伙伴简略科普一下:
- Makefile描述了整个项目的编译和链接等规矩
- 比方哪些文件需求编译?哪些文件不需求编译?哪些文件需求先编译?哪些文件需求后编译?等等
- Makefile能够使项目的编译变得自动化,不需求每次都手动输入一堆源文件和参数
- 比方本来需求这么写:gcc test1.c test2.c test3.c -o test
3.4 装置
将编译好的库装置到指定的方位:/usr/local/ffmpeg。
make install
装置结束后,/usr/local/ffmpeg的目录结构如下所示。
3.5 装备PATH
为了让bin目录中的ffmpeg、ffprobe、ffplay在恣意方位都能够运用,需求先将bin目录装备到环境变量PATH中。
# 修改.zprofile
vim ~/.zprofile
# .zprofile文件中写入以下内容
export PATH=/usr/local/ffmpeg/bin:$PATH
# 让.zprofile生效
source ~/.zprofile
假如你用的是bash,而不是zsh,只需求将上面的**.zprofile换成.bash_profile**。
3.6 验证
接下来,在命令行上进行验证。
ffmpeg -version
# 成果如下所示
ffmpeg version 4.3.2 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 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
此刻,你完全能够经过brew uninstall ffmpeg卸载以前装置的FFmpeg。
4. Windows编译
configure、Makefile这一套东西是用在类Unix体系上的(Linux、Mac等),默许无法直接用在Windows上。
这儿介绍其中一种可行的解决计划:
- 运用MSYS2软件在Windows上模拟出Linux环境
- 结合运用MinGW对FFmpeg进行编译
4.1 下载装置MSYS2
进入MSYS2官网下载装置包(我这边下载的是:msys2-x86_64-20210228.exe),然后进行装置。
装置结束后翻开命令行东西mingw64.exe。
4.2 装置依靠
pacman(Package Manager)是一个包办理东西。
- pacman -Sl:查找有哪些包能够装置
- pacman -S:装置
- pacman -R:卸载
# 查看是否有fdk、SDL2相关包(E表明跟一个正则表达式,i表明不区分巨细写)
pacman -Sl | grep -Ei 'fdk|sdl2'
# 成果如下所示
mingw32 mingw-w64-i686-SDL2 2.0.14-2
mingw32 mingw-w64-i686-SDL2_gfx 1.0.4-1
mingw32 mingw-w64-i686-SDL2_image 2.0.5-1
mingw32 mingw-w64-i686-SDL2_mixer 2.0.4-2
mingw32 mingw-w64-i686-SDL2_net 2.0.1-1
mingw32 mingw-w64-i686-SDL2_ttf 2.0.15-1
mingw32 mingw-w64-i686-fdk-aac 2.0.1-1
mingw64 mingw-w64-x86_64-SDL2 2.0.14-2
mingw64 mingw-w64-x86_64-SDL2_gfx 1.0.4-1
mingw64 mingw-w64-x86_64-SDL2_image 2.0.5-1
mingw64 mingw-w64-x86_64-SDL2_mixer 2.0.4-2
mingw64 mingw-w64-x86_64-SDL2_net 2.0.1-1
mingw64 mingw-w64-x86_64-SDL2_ttf 2.0.15-1
mingw64 mingw-w64-x86_64-fdk-aac 2.0.1-1
接下来,装置各种依靠包。
# 编译东西链
pacman -S mingw-w64-x86_64-toolchain
pacman -S mingw-w64-x86_64-yasm
pacman -S mingw-w64-x86_64-SDL2
pacman -S mingw-w64-x86_64-fdk-aac
pacman -S mingw-w64-x86_64-x264
pacman -S mingw-w64-x86_64-x265
# 需求独自装置make
pacman -S make
关于软件包相关的默许路径:
- 下载目录:%MSYS2_HOME%/var/cache/pacman/pkg
- 装置目录:%MSYS2_HOME%/mingw64
- **%MSYS2_HOME%**是指MSYS2的装置目录
4.3 configure
我的源码是放在F:/Dev/ffmpeg-4.3.1,输入cd /f/dev/ffmpeg-4.3.1即可进入源码目录。然后履行configure。
./configure --prefix=/usr/local/ffmpeg --enable-shared --disable-static --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libx264 --enable-libx265
4.4 编译、装置
make -j8 && make install
FFmpeg终究会被装置到**%MSYS2_HOME%/usr/local/ffmpeg**目录中。
4.5 bin
此刻bin目录中的ffmpeg、ffprobe、ffplay还是没法运用的,因为短少相关的dll,需求从**%MSYS2_HOME%/mingw64/bin中复制,或者将%MSYS2_HOME%/mingw64/bin**装备到环境变量Path中。
需求复制的dll有:libwinpthread-1、SDL2、zlib1.dll、liblzma-5、libbz2-1、libiconv-2、libgcc_s_seh-1、libstdc++-6、libx265、libx264-159、libfdk-aac-2。
4.6 Path
终究主张将**%FFMPEG_HOME%/bin**目录装备到环境变量Path中。
在命令行输入ffmpeg -version,一切大功告成!
四、AAC编码实战
本文将分别经过命令行、编程2种方法进行AAC编码实战,运用的编码库是libfdk_aac。
1. 要求
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 #0:0 - maybe incorrect parameters such as bit_rate, rate, width or height
Conversion failed!
1.1 采样格局
必须是16位整数PCM。
1.2 采样率
支撑的采样率有(Hz):
- 8000、11025、12000、16000、22050、24000、32000
- 44100、48000、64000、88200、96000
2. 命令行
2.1 根本运用
最简略的用法如下所示:
# pcm -> aac
ffmpeg -ar 44100 -ac 2 -f s16le -i in.pcm -c:a libfdk_aac out.aac
# wav -> aac
# 为了简化指令,本文后面会尽量运用in.wav取代in.pcm
ffmpeg -i in.wav -c:a libfdk_aac out.aac
-
-ar 44100 -ac 2 -f s16le
- PCM输入数据的参数
-
-c:a
- 设置音频编码器
- c表明codec(编解码器),a表明audio(音频)
- 等价写法
- -codec:a
- -acodec
- 需求留意的是:这个参数要写在aac文件那儿,也便是归于输出参数
默许生成的aac文件是LC标准的。
ffprobe out.aac
# 输出成果如下所示
Audio: aac (LC), 44100 Hz, stereo, fltp, 120 kb/s
2.2 常用参数
-
-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形式(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
2.3 文件格局
我曾在《重识音频》中提到,AAC编码的文件扩展名首要有3种:aac、m4a、mp4。
# m4a
ffmpeg -i in.wav -c:a libfdk_aac out.m4a
# mp4
ffmpeg -i in.wav -c:a libfdk_aac out.mp4
3. 编程
需求用到2个库:
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}
// 过错处理
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
3.1 函数声明
咱们终究会将PCM转AAC的操作封装到一个函数中。
extern "C" {
#include <libavcodec/avcodec.h>
}
// 参数
typedef struct {
const char *filename;
int sampleRate;
AVSampleFormat sampleFmt;
int chLayout;
} AudioEncodeSpec;
class FFmpegs {
public:
FFmpegs();
static void aacEncode(AudioEncodeSpec &in,
const char *outFilename);
};
3.2 函数完成
3.2.1 变量定义
// 编码器
AVCodec *codec = nullptr;
// 上下文
AVCodecContext *ctx = nullptr;
// 用来寄存编码前的数据
AVFrame *frame = nullptr;
// 用来寄存编码后的数据
AVPacket *pkt = nullptr;
// 回来成果
int ret = 0;
// 输入文件
QFile inFile(in.filename);
// 输出文件
QFile outFile(outFilename);
3.2.2 获取编码器
下面的代码能够获取FFmpeg默许的AAC编码器(并不是libfdk_aac)。
AVCodec *codec1 = avcodec_find_encoder(AV_CODEC_ID_AAC);
AVCodec *codec2 = avcodec_find_encoder_by_name("aac");
// true
qDebug() << (codec1 == codec2);
// aac
qDebug() << codec1->name;
不过咱们终究要获取的是libfdk_aac。
// 获取fdk-aac编码器
codec = avcodec_find_encoder_by_name("libfdk_aac");
if (!codec) {
qDebug() << "encoder libfdk_aac not found";
return;
}
3.2.3 查看采样格局
接下来查看编码器是否支撑当前的采样格局。
// 查看采样格局
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的完成如下所示。
// 查看编码器codec是否支撑采样格局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;
}
3.2.4 创立上下文
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;
3.2.5 翻开编码器
// 翻开编码器
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);
3.2.6 创立AVFrame
AVFrame用来寄存编码前的数据。
// 创立AVFrame
frame = av_frame_alloc();
if (!frame) {
qDebug() << "av_frame_alloc error";
goto end;
}
// 样本帧数量(由frame_size决定)
frame->nb_samples = ctx->frame_size;
// 采样格局
frame->format = ctx->sample_fmt;
// 声道布局
frame->channel_layout = ctx->channel_layout;
// 创立AVFrame内部的缓冲区
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "av_frame_get_buffer error" << errbuf;
goto end;
}
3.2.7 创立AVPacket
// 创立AVPacket
pkt = av_packet_alloc();
if (!pkt) {
qDebug() << "av_packet_alloc error";
goto end;
}
3.2.8 翻开文件
// 翻开文件
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;
}
3.2.9 开端编码
// frame->linesize[0]是缓冲区的巨细
// 读取文件数据
while ((ret = inFile.read((char *) frame->data[0],
frame->linesize[0])) > 0) {
// 终究一次读取文件数据时,有或许并没有填满frame的缓冲区
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;
}
}
// flush编码器
encode(ctx, nullptr, pkt, outFile);
encode函数专门用来进行编码,它的完成如下所示。
// 音频编码
// 回来负数:半途呈现了过错
// 回来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 (true) {
// 从编码器中获取编码后的数据
ret = avcodec_receive_packet(ctx, pkt);
// packet中现已没有数据,需求重新发送数据到编码器(send frame)
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;
}
3.2.10 资源收回
end:
// 封闭文件
inFile.close();
outFile.close();
// 开释资源
av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_free_context(&ctx);
3.3 函数调用
AudioEncodeSpec in;
in.filename = "F:/in.pcm";
in.sampleRate = 44100;
in.sampleFmt = AV_SAMPLE_FMT_S16;
in.chLayout = AV_CH_LAYOUT_STEREO;
FFmpegs::aacEncode(in, "F:/out.aac");
五、AAC解码实战
本文首要解说:如何将AAC编码后的数据解码成PCM。
1. 命令行
用法十分简略:
ffmpeg -c:a libfdk_aac -i in.aac -f s16le out.pcm
-
-c:a libfdk_aac
- 运用fdk-aac解码器
- 需求留意的是:这个参数要写在aac文件那儿,也便是归于输入参数
-
-f s16le
- 设置PCM文件终究的采样格局
2. 编程
需求用到2个库:
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
2.1 函数声明
咱们终究会将AAC解码的操作封装到一个函数中。
// 解码后的PCM参数
typedef struct {
const char *filename;
int sampleRate;
AVSampleFormat sampleFmt;
int chLayout;
} AudioDecodeSpec;
class FFmpegs {
public:
FFmpegs();
static void aacDecode(const char *inFilename,
AudioDecodeSpec &out);
};
2.2 函数完成
2.2.1 变量定义
// 输入缓冲区的巨细
#define IN_DATA_SIZE 20480
// 需求再次读取输入文件数据的阈值
#define REFILL_THRESH 4096
// 回来成果
int ret = 0;
// 每次从输入文件中读取的长度
int inLen = 0;
// 是否现已读取到了输入文件的尾部
int inEnd = 0;
// 用来寄存读取的文件数据
// 加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化过的reader一次性读取过多导致越界
char inDataArray[IN_DATA_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
char *inData = inDataArray;
// 文件
QFile inFile(inFilename);
QFile outFile(out.filename);
// 解码器
AVCodec *codec = nullptr;
// 上下文
AVCodecContext *ctx = nullptr;
// 解析器上下文
AVCodecParserContext *parserCtx = nullptr;
// 寄存解码前的数据
AVPacket *pkt = nullptr;
// 寄存解码后的数据
AVFrame *frame = nullptr;
2.2.2 获取解码器
// 获取解码器
codec = avcodec_find_decoder_by_name("libfdk_aac");
if (!codec) {
qDebug() << "decoder libfdk_aac not found";
return;
}
2.2.3 初始化解析器上下文
// 初始化解析器上下文
parserCtx = av_parser_init(codec->id);
if (!parserCtx) {
qDebug() << "av_parser_init error";
return;
}
2.2.4 创立上下文
// 创立上下文
ctx = avcodec_alloc_context3(codec);
if (!ctx) {
qDebug() << "avcodec_alloc_context3 error";
goto end;
}
2.2.5 创立AVPacket
// 创立AVPacket
pkt = av_packet_alloc();
if (!pkt) {
qDebug() << "av_packet_alloc error";
goto end;
}
2.2.6 创立AVFrame
// 创立AVFrame
frame = av_frame_alloc();
if (!frame) {
qDebug() << "av_frame_alloc error";
goto end;
}
2.2.7 翻开解码器
// 翻开解码器
ret = avcodec_open2(ctx, codec, nullptr);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avcodec_open2 error" << errbuf;
goto end;
}
2.2.8 翻开文件
// 翻开文件
if (!inFile.open(QFile::ReadOnly)) {
qDebug() << "file open error:" << inFilename;
goto end;
}
if (!outFile.open(QFile::WriteOnly)) {
qDebug() << "file open error:" << out.filename;
goto end;
}
2.2.9 解码
// 读取数据
inLen = inFile.read(inData, IN_DATA_SIZE);
while (inLen > 0) {
// 经过解析器上下文处理
ret = av_parser_parse2(parserCtx, ctx,
&pkt->data, &pkt->size,
(uint8_t *) inData, inLen,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "av_parser_parse2 error" << errbuf;
goto end;
}
// 越过现已解析过的数据
inData += ret;
// 减去现已解析过的数据巨细
inLen -= ret;
// 解码
if (pkt->size > 0 && decode(ctx, pkt, frame, outFile) < 0) {
goto end;
}
// 假如数据不够了,再次读取文件
if (inLen < REFILL_THRESH && !inEnd) {
// 剩下数据移动到缓冲区前
memmove(inDataArray, inData, inLen);
inData = inDataArray;
// 跨过已有数据,读取文件数据
int len = inFile.read(inData + inLen, IN_DATA_SIZE - inLen);
if (len > 0) {
inLen += len;
} else {
inEnd = 1;
}
}
}
// flush解码器
// pkt->data = NULL;
// pkt->size = 0;
decode(ctx, nullptr, frame, outFile);
详细的解码操作在decode函数中。
static int decode(AVCodecContext *ctx,
AVPacket *pkt,
AVFrame *frame,
QFile &outFile) {
// 发送紧缩数据到解码器
int ret = avcodec_send_packet(ctx, pkt);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avcodec_send_packet error" << errbuf;
return ret;
}
while (true) {
// 获取解码后的数据
ret = avcodec_receive_frame(ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avcodec_receive_frame error" << errbuf;
return ret;
}
// 将解码后的数据写入文件
outFile.write((char *) frame->data[0], frame->linesize[0]);
}
}
2.2.10 设置输出参数
// 设置输出参数
out.sampleRate = ctx->sample_rate;
out.sampleFmt = ctx->sample_fmt;
out.chLayout = ctx->channel_layout;
2.2.11 开释资源
end:
inFile.close();
outFile.close();
av_frame_free(&frame);
av_packet_free(&pkt);
av_parser_close(parserCtx);
avcodec_free_context(&ctx);
2.3 函数调用
AudioDecodeSpec out;
out.filename = "F:/out.pcm";
FFmpegs::aacDecode("F:/in.aac", out);
// 44100
qDebug() << out.sampleRate;
// s16
qDebug() << av_get_sample_fmt_name(out.sampleFmt);
// 2
qDebug() << av_get_channel_layout_nb_channels(out.chLayout);
专题系列文章
1. 前常识
- 01-探求iOS底层原理|综述
- 02-探求iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探求iOS底层原理|LLDB
- 04-探求iOS底层原理|ARM64汇编
2. 根据OC言语探索iOS底层原理
- 05-探求iOS底层原理|OC的实质
- 06-探求iOS底层原理|OC目标的实质
- 07-探求iOS底层原理|几种OC目标【实例目标、类目标、元类】、目标的isa指针、superclass、目标的方法调用、Class的底层实质
- 08-探求iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 履行、关联目标
- 09-探求iOS底层原理|KVO
- 10-探求iOS底层原理|KVC
- 11-探求iOS底层原理|探索Block的实质|【Block的数据类型(实质)与内存布局、变量捕获、Block的种类、内存办理、Block的修饰符、循环引证】
- 12-探求iOS底层原理|Runtime1【isa详解、class的结构、方法缓存cache_t】
- 13-探求iOS底层原理|Runtime2【音讯处理(发送、转发)&&动态方法解析、super的实质】
- 14-探求iOS底层原理|Runtime3【Runtime的相关应用】
- 15-探求iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探求iOS底层原理|RunLoop的应用
- 17-探求iOS底层原理|多线程技能的底层原理【GCD源码剖析1:主行列、串行行列&&并行行列、全局并发行列】
- 18-探求iOS底层原理|多线程技能【GCD源码剖析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探求iOS底层原理|多线程技能【GCD源码剖析2:栅栏函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探求iOS底层原理|多线程技能【GCD源码剖析3:线程调度组dispatch_group、事件源dispatch Source】
- 21-探求iOS底层原理|多线程技能【线程锁:自旋锁、互斥锁、递归锁】
- 22-探求iOS底层原理|多线程技能【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探求iOS底层原理|内存办理【Mach-O文件、Tagged Pointer、目标的内存办理、copy、引证计数、weak指针、autorelease
3. 根据Swift言语探索iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、特点
、方法
、swift多态原理
、String
、Array
、Dictionary
、引证计数
、MetaData
等Swift根本语法和相关的底层原理文章有如下几篇:
- 01-Swift5常用中心语法|了解Swift【Swift简介、Swift的版本、Swift编译原理】
- 02-Swift5常用中心语法|根底语法【Playground、常量与变量、常见数据类型、字面量、元组、流程操控、函数、枚举、可选项、guard语句、区间】
- 03-Swift5常用中心语法|面向目标【闭包、结构体、类、枚举】
- 04-Swift5常用中心语法|面向目标【特点、inout、类型特点、单例形式、方法、下标、承继、初始化】
- 05-Swift5常用中心语法|高档语法【可选链、协议、过错处理、泛型、String与Array、高档运算符、扩展、访问操控、内存办理、字面量、形式匹配】
- 06-Swift5常用中心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码剖析】
4. C++中心语法
- 01-C++中心语法|C++概述【C++简介、C++来源、可移植性和标准、为什么C++会成功、从一个简略的程序开端知道C++】
- 02-C++中心语法|C++对C的扩展【::效果域运算符、姓名操控、struct类型加强、C/C++中的const、引证(reference)、函数】
- 03-C++中心语法|面向目标1【 C++编程标准、类和目标、面向目标程序规划事例、目标的构造和析构、C++面向目标模型初探】
- 04-C++中心语法|面向目标2【友元、内部类与局部类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转换、 C++标准、过错&&异常、智能指针】
- 05-C++中心语法|面向目标3【 承继和派生、多态、静态成员、const成员、引证类型成员、VS的内存窗口】
5. Vue全家桶
- 01-Vue全家桶中心常识|Vue根底【Vue概述、Vue根本运用、Vue模板语法、根底事例、Vue常用特性、归纳事例】
- 02-Vue全家桶中心常识|Vue常用特性【表单操作、自定义指令、核算特点、侦听器、过滤器、生命周期、归纳事例】
- 03-Vue全家桶中心常识|组件化开发【组件化开发思想、组件注册、Vue调试东西用法、组件间数据交互、组件插槽、根据组件的
- 04-Vue全家桶中心常识|多线程与网络【前后端交互形式、promise用法、fetch、axios、归纳事例】
- 05-Vue全家桶中心常识|Vue Router【根本运用、嵌套路由、动态路由匹配、命名路由、编程式导航、根据vue-router的事例】
- 06-Vue全家桶中心常识|前端工程化【模块化相关标准、webpack、Vue 单文件组件、Vue 脚手架、Element-UI 的根本运用】
- 07-Vue全家桶中心常识|Vuex【Vuex的根本运用、Vuex中的中心特性、vuex事例】
6. 音视频技能中心常识
- 01-音视频技能中心常识|了解音频技能【移动通信技能的开展、声响的实质、深入了解音频】
- 02-音视频技能中心常识|建立开发环境【FFmpeg与Qt、Windows开发环境建立、Mac开发环境建立、Qt开发根底】
- 03-音视频技能中心常识|Qt开发根底【
.pro
文件的装备、Qt控件根底、信号与槽】 - 04-音视频技能中心常识|音频录制【命令行、C++编程】
- 05-音视频技能中心常识|音频播映【播映PCM、WAV、PCM转WAV、PCM转WAV、播映WAV】
- 06-音视频技能中心常识|音频重采样【音频重采样简介、用命令行进行重采样、经过编程重采样】
- 07-音视频技能中心常识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】
- 08-音视频技能中心常识|成像技能【重识图片、详解YUV、视频录制、显现BMP图片、显现YUV图片】
- 09-音视频技能中心常识|视频编码解码【了解H.264编码、H.264编码、H.264编码解码】
- 10-音视频技能中心常识|RTMP服务器建立【流媒体、服务器环境】
其它底层原理专题
1. 底层原理相关专题
- 01-核算机原理|核算机图形烘托原理这篇文章
- 02-核算机原理|移动终端屏幕成像与卡顿
2. iOS相关专题
- 01-iOS底层原理|iOS的各个烘托结构以及iOS图层烘托原理
- 02-iOS底层原理|iOS动画烘托原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
- 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和解决计划
3. webApp相关专题
- 01-Web和类RN大前端的烘托原理
4. 跨渠道开发计划相关专题
- 01-Flutter页面烘托原理
5. 阶段性总结:Native、WebApp、跨渠道开发三种计划功能比较
- 01-Native、WebApp、跨渠道开发三种计划功能比较
6. Android、HarmonyOS页面烘托专题
- 01-Android页面烘托原理
- 02-HarmonyOS页面烘托原理 (
待输出
)
7. 小程序页面烘托专题
- 01-小程序结构烘托原理