一、前言

本系列文章是对音视频技能入门常识的整理和复习,为进一步深入体系研究音视频技能稳固根底。文章列表:

  • 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的开展进程。

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

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

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

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

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

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. 方针

这儿先提早阐明一下,终究希望到达的效果:

  • 编译出ffmpegffprobeffplay三个命令行东西
  • 只发生动态库,不发生静态库
  • fdk-aacx264x265集成到FFmpeg中
    • x264、x265会在以后解说的视频模块中用到

2. 下载源码

下载源码ffmpeg-4.3.2.tar.xz,然后解压。

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

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

其实x264x265sdl2都在从前履行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的目录结构如下所示。

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

3.5 装备PATH

为了让bin目录中的ffmpegffprobeffplay在恣意方位都能够运用,需求先将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编译

configureMakefile这一套东西是用在类Unix体系上的(Linux、Mac等),默许无法直接用在Windows上。

这儿介绍其中一种可行的解决计划:

  • 运用MSYS2软件在Windows上模拟出Linux环境
  • 结合运用MinGW对FFmpeg进行编译

4.1 下载装置MSYS2

进入MSYS2官网下载装置包(我这边下载的是:msys2-x86_64-20210228.exe),然后进行装置。

装置结束后翻开命令行东西mingw64.exe

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

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**目录中。

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

4.5 bin

此刻bin目录中的ffmpeg、ffprobe、ffplay还是没法运用的,因为短少相关的dll,需求从**%MSYS2_HOME%/mingw64/bin中复制,或者将%MSYS2_HOME%/mingw64/bin**装备到环境变量Path中。

需求复制的dll有:libwinpthread-1SDL2zlib1.dllliblzma-5libbz2-1libiconv-2libgcc_s_seh-1libstdc++-6libx265libx264-159libfdk-aac-2

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

4.6 Path

终究主张将**%FFMPEG_HOME%/bin**目录装备到环境变量Path中。

在命令行输入ffmpeg -version,一切大功告成!

07-音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】

四、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_contextavcodec_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多态原理StringArrayDictionary引证计数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-小程序结构烘托原理