Android音视频开发音频编码原理
Posted JesseAndroid
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android音视频开发音频编码原理相关的知识,希望对你有一定的参考价值。
文章变更表
文章版本号 | 变更内容 | 变更日期 | 备注 |
---|---|---|---|
0.0.1 | 创建 | 2022/9/29 | 初版 |
0.0.2 | 补充编码原理和音频格式等内容 | 2022/9/30 |
1. 前言
在【Android音视频开发】 这一系列文章的开头,介绍一下音视频编码的原理是很有必要的,有利于对后续出现的概念的理解。此处先讲音频,视频编码的原理将在另一篇文章中讲述。
2. 正文
2.1 声音的本质
声音的本质是在介质中传递的声波,既然是一种波,那么它就会具有以下几个波的特征:频率、振幅和波长。频率越大声音越尖细,振幅越大声音越洪亮。
2.2 声音的数字化
自然界中的声音波形都是连续的,称为模拟音频(模拟信号),人们通过收音设备采集到的模拟信号,会以0、1这样的二进制数据进行存储,通过这种形式存储的音频称为数字音频(数字信号)。下面介绍一下采样的概念和几个描述数字音频的关键参数。
- 采样:将模拟音频通过收音设备进行采集,从而得到数字音频的过程称为“采样”。
- 采样率:单位时间(1秒)内对模拟音频采集的次数称为“采样率”,采样率越大,声音的还原度越高、越真实,常见的采样率有16kHz、44.1kHz和48kHz。
- 采样位数:可以理解数字音频设备处理声音的解析度,即对声音的辨析度。就像表示颜色的位数一样(8位表示256种颜色,16位表示65536种颜色),有8位,16位,24位等。这个数值越大,解析度就越高,录制和回放的声音就越真实。
- 比特率:表示单位时间(1秒)内传送的比特数bps(bit per second,位/秒)的速度。作为一种数字音乐压缩效率的参考性指标,通常使用kbps(通俗地讲就是每秒钟1024比特)作为单位。
2.3 音频编码的重要性
根据2.2中提到的概念,我们可以得到一个数字音频文件占用空间大小的算法,用S表示占用存储空间大小的话,S=(采样率x采样位数x声道数)/8x音频时长。
假设现在有一段音频,采样率为44.1KHZ,采样位数为16位,左右双声道(立体声),时长为5分钟。那么它占用存储空间的大小S=(44100162)/8*(5*60)=52920000字节,转为MB为 52920000/1024/1024≈50.47MB(兆字节),也就是说,我们存储这样一段5分钟的音频就需要占用50MB的空间,这在大容量存储设备普及前以及传输带宽很小的古早时期是很难接受的,因此对数字音频进行编码的需求迫在眉睫。
2.4 编码原理
数字音频压缩编码是在保证信号在听觉方面不产生失真的前提下,对音频数据信号尽可能大的压缩,降低数据量。数字音频压缩编码采取去除声音信号中的冗余成分的方法来实现。所谓冗余成分指的是音频中不能被人耳感知到的信号,即[20, 20k]Hz以外频率的信号,这些信号对确定声音的音色,音调等信息没有任何的帮助。
此外,根据人耳听觉的生理和心理声学现象,当一个强音信号与一个弱音信号同时存在时,弱音信号将被强音信号所遮蔽而听不见,这样弱音信号就可以视为冗余信号而不用传送。这就是人耳听觉的掩蔽效应,主要表现在频谱掩蔽效应和时域掩蔽效应。
2.4.1 频谱掩蔽效应
一个频率的声音音量(db)小于某个阈值,人耳就会听不到。当有另外能量较大的声音出现的时候,该声音频率附近的阈值就会提高很多,即所谓的掩蔽效应,如上图所示。
由图中我们可以看出人耳对2KHz~5KHz的声音最敏感,而对频率太低或太高的声音信号都很迟钝,当有一个频率为0.2KHz、强度内60db的声音出现时,其附近的阈值提高了很多。
由图中我们可以看出在0.1KHz以下、1KHz以上的部分,由于离0.2KHz强信号较远,不受0.2KHz强信号影响,阈值不受影响。而在0.1KHz-1KHz范围,由于0.2KHz强音的出现,阈值有较大的提升,人耳在此范围所能感觉到的最小声音强度大幅提升。如果0.1KHz-1KHz范围内的声音信号的强度在被提升的阈值曲线之下,由于它被0.2KHz强音信号所掩蔽,那么此时我们人耳只能听到0.2KHz的强音信号而根本听不见其它弱信号,这些与0.2KHz强音信号同时存在的弱音信号就可视为冗余信号而不必传送。
20hz和20khz左右音量需要非常大才能听见,如果在0.2khz时音量很大,就会出现掩蔽效应,即在0.2khz音量很大的点的附近频率的声音也需要很大才能听得见。于是我们不需要编码掩蔽阈值以下部分的声音,因为这部分的声音人很难听到。
2.4.2 时域掩蔽效应
在强音和弱音信号同时出现时,还存在时域掩蔽效应。即两者发生时间很接近的时候,也会发生掩蔽效应。时域掩蔽过程曲线如图所示,分为前掩蔽、同时掩蔽和后掩蔽三部分。
-
前掩蔽
人耳在听到强信号之前的短暂时间内,已经存在的弱信号或被掩蔽而听不到 -
同时掩蔽
当强信号与弱信号同时存在时,弱信号会被强信号所掩蔽而听不到 -
后掩蔽
强信号消失后,需经过较长的一段时间才能重新听见弱信号
这些被掩蔽的弱信号可以视为冗余信号。
突然有强音,这时弱信号听不见
强音消失后一段时间后才能听见弱信号
2.5 压缩编码方法
当前数字音频编码领域存在不同编码方案和实现方式,基本编码思路大同小异,如图所示:
对每一个音频声道中的音频采样信号进行以下处理:
- 将他们映射到频域中,这种时域到频域的映射可通过子带滤波器实现。每个声道中的音频采样块首先要根据心理声学模型来计算掩蔽门限值(人耳对1kHz-7kHz较为敏感)
- 由计算出的掩蔽门限值决定从公共比特池中分配给该声道不同频率域中多少比特数,接着进行量化以及编码工作
- 将控制参数及辅助数据加入数据之中,产生编码后的数据流
2.6 常用音频编码格式
这里只介绍两种在开发中用的比较多的音频编码格式:PCM 和 AAC
2.6.1 PCM
PCM全称Pulse-Code Modulation,翻译一下是脉冲调制编码。在音视频中,PCM是一种用数字表示采样模拟信号的方法。
要将一段音频模拟信号转换为数字表示,包含如下三个步骤:
- Sampling(采样)
- Quantization(量化)
- Coding(编码)
2.6.2 AAC
AAC,全称Advanced Audio Coding,中文名:高级音频编码,是一种专为声音数据设计的文件压缩格式。与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性价比”。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。苹果ipod、诺基亚手机支持AAC格式的音频文件。
优点:相对于mp3,AAC格式的音质更佳,文件更小。
不足:AAC属于有损压缩的格式,与时下流行的APE、FLAC等无损格式相比音质存在“本质上”的差距。加之,传输速度更快的USB3.0和16G以上大容量MP3正在加速普及,也使得AAC头上“小巧”的光环不复存在。
2.7 音频帧长
其实,音频的帧的概念没有视频帧那么清晰,几乎所有视频编码格式都可以简单的认为一帧就是编码后的一副图像。但音频帧跟编码格式相关,它是各个编码标准自己实现的。因为如果以PCM(未经编码的音频数据)来说,它根本就不需要帧的概念,根据采样率和采样精度就可以播放了。
比如采样率为44.1kHZ,采样精度为16位的音频,你可以算出bitrate(比特率)是44100 x 16kbps,每秒的音频数据是固定的44100 x 16 / 8 字节。
对采样率为44.1kHz的AAC(Advanced Audio Coding)音频进行解码时,一帧的解码时间须控制在23.22毫秒内。通常是按1024个采样点为一帧。
2.8 音频播放过程
播放音乐时,应用程序从存储介质中读取音频数据(MP3、WMA、AAC),进过解码后,最终送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得到的PCM数据送回给应用程序,由应用程序完成压缩、存储等任务。所以,音频驱动的两大核心任务就是:
playback:把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频。
capture:把mic拾取得到的模拟信号,经过采样、量化,转化为PCM信号送回给用户空间的应用程序。
参考文献
Android音视频开发FFmpeg库编译详解(2022最新版)
文章版本号 | 变更内容 | 变更日期 | 备注 |
---|---|---|---|
0.0.1 | 创建 | 2022/10/10 | 初版 |
0.0.2 | 补充环境搭建内容 | 2022/10/11 | 无 |
0.0.3 | 补充编译内容 | 2022/10/13 | 无 |
0.0.4 | 更换编译用的ndk版本,完成初版编写 | 2022/10/17 | 无 |
1 前言
前两篇文章分别介绍了音频和视频的编码原理(还没看的可以戳音频编码原理,视频编码原理进行阅读),接下来进入开发阶段。进入到音视频开发阶段后,有个库是绕不开的,那就是有名的FFmpeg(读作ef ef em peg,也有人读作ef em peg)开源库,它十分强大,提供了很多功能API,包括但不限于:音视频重采样、音视频编码格式转换、音视频播放、素材(字幕等)添加、视频转场动画、音视频推拉流、录像等等,音视频开发过程中需要用到的功能它基本都有。
鉴于FFmpeg库如此“全能”,所以给大家介绍一下它的用法是很有必要的,而在使用前,我们需要对它进行编译,得到so库和头文件后才能在项目中进行使用。
2 正文
2.1 环境搭建
所需资源:
2.1.1 安装ubuntu系统
双击打开下载好的Virtualbox安装程序-VirtualBox-6.1.36-152435-Win.exe,全部点击下一步安装即可,也可根据自己需求进行修改。 安装完成后运行VirtualBox:
上图中可以看到,博主这里已经添加了一个ubuntu系统了,还没有添加的点击“新建”按钮,在弹出框的名称栏中输入ubuntu后,类型和版本会自动设置,之后可以再自行修改名称,如ubuntu22.04.1等。
设置完名称后点击下一步,进入设置内存大小的页面,这里的内存大小是指分配给虚拟电脑的内存大小,根据自己电脑的内存大小和需要进行设置即可。博主的电脑内存是32GB,这里设置的是8GB:
设置完内存大小后点击下一步,创建虚拟磁盘,点击“创建”按钮。
磁盘类型选择VDI(或根据自己需求进行选择),点击下一步。
选择磁盘空间分配方式,有两种方式可选,动态分配和固定大小,具体描述可以看窗口中的介绍文本,博主选择的是固定大小,选择好后点击下一步。
选择虚拟磁盘放置的位置和设置磁盘大小,这里根据自己的需求进行配置即可,博主设置的磁盘大小是30GB。
配置完后点击创建,会弹出等待弹窗,这个创建的过程比较耗时,博主等了大概5-8分钟左右。
虚拟磁盘创建完成后,会回到首页,首页右侧会显示配置的虚拟电脑的信息。
接下来就需要把我们之前下载的ubuntu系统iso文件配置进去了。点击“启动”按钮,在弹出的页面中点击“注册”。
在文件夹中找到自己下载的ubuntu-xxx.iso文件,点击打开。
将iso文件添加到列表中后,选中它并点击选择。
回到首页后再次点击启动按钮,这时会弹出“选择启动盘”的弹窗,选中我们刚才添加的启动盘后点击启动按钮即可启动ubuntu系统。
启动报错解决:
博主在最后一步点击启动后遇到了报错,报错弹窗如下:
报错的原因是我的电脑BIOS中没有开启VT-x功能,如果你也悲催的遇到了这个报错,那么请看下面的解决方法。
博主的电脑是win10系统,其他系统的不敢保证能用同样的方式解决(不过也可以尝试一下)。
解决方法:
重启电脑,在重启时不停按F2键(有些电脑是F8/F12,如果F2不管用可以查一下自己电脑进入BIOS的方式)进入BIOS设置页面,切换到Advanced选项卡,找到VTX virtualization(或类似选项),将disable状态改为enable状态,按F10保存并退出。电脑重启后就可以正常启动ubuntu系统了。
正常运行截图:
就算是正常启动也需要等待几分钟的时间,期间可能会黑屏一阵子,请耐心等待加载。
最后,附上官方配置文档,需要梯子才能加载出页面中的图片,否则只能看到纯文本描述。
其他踩坑记录:
- 无法访问共享文件夹,报错You do not have the permissions necessary to view the contents of ‘shared_folder’:解决方案
- 无法复制文本粘贴到ubuntu系统:解决方案
- 安装增强功能报错“Could not mount the media/drive…”:
打开文件夹,找到箭头所指的目录,右键点击它,在弹出的选项框中点击Properties。
复制文件夹路径,打开Terminal,按照顺序输入图上的命令,进到增强功能文件所在的目录,然后运行它即可手动安装增强功能。
2.1.2 配置编译环境
2.1.2.1 下载NDK
博主使用的ndk版本为r22b,如果你已经下载了别的版本的ndk,也可以直接使用,不过版本建议不要太低,推荐r21+。
ndk下载链接
下载完成后,通过共享文件夹将ndk压缩包转移到ubuntu系统中。打开终端,使用cd命令进入到存放压缩包的目录,然后使用以下命令对压缩包进行解压:
unzip android-ndk-r22b-linux.zip
配置环境变量:
右键点击ndk解压后生成的目录,在弹出菜单中点击Properties,将文件夹路径复制下来。
然后打开终端,输入
vim ~/.bashrc
翻到文件的末尾,按i键进入编辑模式,输入
export NDK_HOME=刚才复制的path
export PATH=$NDK_HOME:$PATH
然后按ESC键退出编辑模式,输入:wq回车,保存修改内容。
退出vim后,使用命令
source ~/.bashrc
使改动生效,至此,ndk的环境变量就配置好了。
2.1.2.2 下载所需编译软件
首先更新包列表:
sudo apt update
然后下载需要的编译软件
sudo apt install autoconf \\
automake \\
build-essential \\
cmake \\
git-core \\
libass-dev \\
libfreetype6-dev \\
libgnutls28-dev \\
libmp3lame-dev \\
libsdl2-dev \\
libtool \\
libva-dev \\
libvdpau-dev \\
libvorbis-dev \\
libxcb1-dev \\
libxcb-shm0-dev \\
libxcb-xfixes0-dev \\
meson \\
ninja-build \\
pkg-config \\
texinfo \\
wget \\
yasm \\
zlib1g-dev \\
libunistring-dev \\
libaom-dev \\
libdav1d-dev \\
pkg-config
到这一步编译所需的准备工作就做完了,接下来终于可以开始编译了。
2.2 编译FFmpeg
2.2.1 下载FFmpeg源码
创建一个目录用来存放FFmpeg源码,打开Terminal,使用cd命令进入该目录下,执行
git clone https://github.com/FFmpeg/FFmpeg.git
将FFmpeg源码下载到本地,如下图:
源码下载完成后,正式开始编译。
2.2.2 编译
修改configure文件:
首先,我们需要对configure文件进行修改,目的是为了不生成版本号(如libavcodec7.so),因为android平台识别不了!
将:
SLIBNAME_WITH_MAJOR=‘$(SLIBNAME).$(LIBMAJOR)’
LIB_INSTALL_EXTRA_CMD=‘$$(RANLIB)“$(LIBDIR)/$(LIBNAME)”’
SLIB_INSTALL_NAME=‘$(SLIBNAME_WITH_VERSION)’
SLIB_INSTALL_LINKS=‘$(SLIBNAME_WITH_MAJOR)$(SLIBNAME)’
修改为:
SLIBNAME_WITH_MAJOR=‘$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)’
LIB_INSTALL_EXTRA_CMD=‘$$(RANLIB)“$(LIBDIR)/$(LIBNAME)”’
SLIB_INSTALL_NAME=‘$(SLIBNAME_WITH_MAJOR)’
SLIB_INSTALL_LINKS=‘$(SLIBNAME)’
试运行configure文件:
进入FFmpeg源码根目录下,使用
sudo ./configure
命令试着编译一下,发现会报错:
按照提示,在上面的命令后面加上–disable-x86asm,命令如下
sudo ./configure --disable-x86asm
这次能够正常运行了,但是最后会报warning:
警报内容说的是没找到pkg-config,我们使用命令安装一下:
sudo apt install pkg-config
安装完成后再次执行之前的编译命令,这次能够成功运行了。博主已将pkg-config包的下载加入到了之前的“下载需要的编译软件”指令中,使用新的指令下载后应该就不会遇到这个warning了。
OK,configure文件成功运行后,接下来就可以开始筹备针对安卓平台的编译了。
创建、执行编译脚本:
在FFmpeg源码根目录下创建build_android.sh文件,并将下面的脚本代码copy进去后保存,注意要将NDK_ROOT修改成自己的ndk目录路径。
#!/bin/bash
# 以下路径需要修改成自己的NDK目录
export NDK_ROOT=/home/jessehao/Development/Android/ndk/android-ndk-r22b
export TOOLCHAIN=$NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64
echo "<<<<<<<<<<<<<基于NDK22b编译 FFmpeg 5.0 64位硬件解码版本>>>>>>>>>>>>>>"
#设置编译平台的相关参数
#armv7-a
ARCH=arm
CPU=armv7-a
#armv8-a
#ARCH=arm64
#CPU=armv8-a
API=21
OS=android
PLATFORM=$CPU-linux-androideabi
PREFIX=$(pwd)/android/$CPU
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
#指定输出路径
PREFIX=$(pwd)/android/arm
SYSROOT=$TOOLCHAIN/sysroot
CROSS_PREFIX=$TOOLCHAIN/bin/llvm-
#CROSS_PREFIX=$TOOLCHAIN/bin/$PLATFORM-
ANDROID_CROSS_PREFIX=$TOOLCHAIN/bin/$PLATFORM$API-
echo "开始编译 $CPU"
#该脚本命令中不能插入注释,每行命令必须以 \\ 结尾
./configure \\
--prefix=$PREFIX \\
--enable-shared \\
--enable-gpl \\
--enable-neon \\
--enable-hwaccels \\
--enable-postproc \\
--enable-jni \\
--enable-small \\
--enable-mediacodec \\
--enable-decoder=h264_mediacodec \\
--enable-ffmpeg \\
--disable-static \\
--disable-ffplay \\
--disable-ffprobe \\
--disable-ffplay \\
--disable-avdevice \\
--disable-debug \\
--disable-static \\
--disable-doc \\
--disable-symver \\
--cross-prefix=$CROSS_PREFIX \\
--target-os=$OS \\
--arch=$ARCH \\
--cpu=$CPU \\
--cc=$ANDROID_CROSS_PREFIXclang \\
--cxx=$ANDROID_CROSS_PREFIXclang++ \\
--enable-cross-compile \\
--sysroot=$SYSROOT \\
--extra-cflags="-Os -fPIC $OPTIMIZE_CFLAGS" \\
--extra-ldflags="$ADDI_LDFLAGS" \\
make clean
#编译
make
#安装
make install
echo "编译完成 $CPU"
然后使用
sudo ./build_android.sh
命令运行该脚本。
运行后发现并没有这么顺利,刚开始就报错了:
我们按ctrl+c终止脚本执行,因为继续执行下去也是有问题的。
进到ffbuild找到config.log并打开,拉到最下方,可以看到以下报错详情:
报错信息说的很清楚了,没有找到相应的clang文件。
我们进到报错信息中指出的目录,发现armv7-a中间多了个“-”,而实际的clang文件是没有“-”的,我们需要改一下脚本,将CPU中armv7-a中的“-”去掉,如下图所示:
修改后再次运行脚本,就没有之前的报错了:
但是出现了新的warning信息:
先不管这个warning,继续编译,发现能够正常编译完成,
且在我们指定的目录($pwd/android/arm)下生成了头文件和so库:
说明是有正常进行编译的。
接下来,我们就可以通过在项目中引入这些头文件和so库,调用FFmpeg提供的多功能API了!
如果你遇到了文章中没有提到的问题,请在评论区留言,博主看到后会尽快回复。
之后的文章会讲解如何利用上面编译出来的头文件和so库,利用FFmpeg提供的API进行音视频开发,我们下一篇文章见。:)
以上是关于Android音视频开发音频编码原理的主要内容,如果未能解决你的问题,请参考以下文章
《音视频开发进阶指南:基于android与iOS平台的实现》读书笔记
《音视频开发进阶指南:基于android与iOS平台的实现》读书笔记