1:相关基本概念。
实现一个在linux环境上可以播放WAV, AAC, Mp3格式的音频播放器,对基础做一些梳理:
1:计算公式及基本概念
- 样本长度/大小(sample):样本是记录音频数据最基本的单位,常见的有8位和16位,24bit。
==》每次采样中记录采样数据存储空间的大小,16bit就能精细到65536。
-
通道数(channel):该参数为1表示单声道,2则是立体声,四声道,5.1声道。
-
**桢(frame):**桢记录了一个声音单元,其长度为样本长度与通道数的乘积。
-
**采样率(rate):**每秒钟采样次数,该次数是针对桢而言。
22000(22kHz): 无线广播。
44100(44.1kHz): CD音质。
48000(48kHz): 数字电视,DVD。
96000(96kHz): 蓝光,高清DVD。
192000(192kHz): 蓝光,高清DVD。
-
**周期(period):**音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。
数据量(字节/秒) = 采样率(Hz) * 采样大小(bit) * 声道数 / 8
==》8是以bit为单位,转换为字节。
2:交错模式(interleaved)
是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。
而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。
不过多数情况下,我们只需要使用交错模式就可以了。
3:周期(period)
周期(period):硬件中中断间的间隔时间。它表示输入延时。
声卡接口中有一个指针来指示声卡硬件缓存区中当前的读写位置。只要接口在运行,这个指针将循环地指向缓存区中的某个位置。
frame size = sizeof(one sample) * nChannelsalsa中配置的缓存(buffer)和周期(size)大小
在runtime中是以帧(frames)形式存储的。
period_bytes = frames_to_bytes(runtime, runtime->period_size);
bytes_to_frames()
4:模/数(ADC)转换 , 数/模(DAC)转换
音频的接收,播放,实际上是模/数(ADC)转换 , 数/模(DAC)转换的过程,模拟信号和数字信号的相互转换。PCM 脉冲编码调制
5:声音缓存,存储以及alsa处理声音缓存
1:缓存逻辑
每个声卡都有一个硬件缓存区来保存记录下来的样本。
当缓存区足够满时,声卡将产生一个中断。
内核声卡驱动然后使用直接内存(DMA)访问通道将样本传送到内存中的应用程序缓存区。
类似地,对于回放,任何应用程序使用DMA将自己的缓存区数据传送到声卡的硬件缓存区中。
这样硬件缓存区是环缓存。
也就是说当数据到达缓存区末尾时将重新回到缓存区的起始位置。
ALSA维护一个指针来指向硬件缓存以及应用程序缓存区中数据操作的当前位置
从内核外部看,我们只对应用程序的缓存区感兴趣,所以本文只讨论应用程序缓存区。
2:缓存控制
应用程序缓存区的大小可以通过ALSA库函数调用来控制:
缓存区可以很大,一次传输操作可能会导致不可接受的延迟,我们把它称为延时(latency)。
为了解决这个问题,ALSA将缓存区拆分成一系列周期(period)(OSS/Free中叫片断fragments)。
ALSA以period为单元来传送数据。
一个周期(period)存储一些帧(frames)。
每一帧包含时间上一个点所抓取的样本。
对于立体声设备,一个帧会包含两个信道上的样本。
分解过程:
一个缓存区分解成周期,然后是帧,然后是样本。
左右信道信息被交替地存储在一个帧内。这称为交错 (interleaved)模式。
在非交错模式中,一个信道的所有样本数据存储在另外一个信道的数据之后。
6:Over and Under Run
当一个声卡活动时,数据总是连续地在硬件缓存区和应用程序缓存区间传输。
但是也有例外。
在录音例子中,如果应用程序读取数据不够快,循环缓存区将会被新的数据覆盖。
这种数据的丢失被称为over run.
在回放例子中,如果应用程序写入数据到缓存区中的速度不够快,缓存区将会"饿死"。
这样的错误被称为"under run"。
在ALSA文档中,有时将这两种情形统称为"XRUN"。
适当地设计应用程序可以最小化XRUN并且可以从中恢复过来。
7:脉冲编码调制(PCM)
我们知道在现实生活中,人耳听到的声音是模拟信号,PCM就是要把声音从模拟信号/数字信号转换的一种技术
将输入的模拟信号进行采样、量化和编码,用二进制进行编码的数来代表模拟信号的幅度 ;
接收端再将这些编码还原为原来的模拟信号。
即数字音频的 A/D 转换包括三个过程 :采样,量化,编码。
我的理解是PCM是最基本的播放格式。
2:alsa实现播放
1:alsa了解:
alsa(Advanced Linux Sound Architecture,高级Linux声音架构)声音子系统替换2.4系列内核中的OSS(Open Sound System,开放声音系统)。
现在更多的用alsa进行驱动播放,alsa内部也支持oss,我没有过多测试oss模块。
Linux内核中自带Alsa的部分驱动:
alsa三大硬件设备驱动模块:
PlatForm ==》把dma buffer中的音频数据搬运到FIFO,把音频数据从FIFO中搬运到CODEX
Codex ==>转换成模拟信号输出到外放或者耳机中
Mechine :指某款特定的机器
2:alsa提供一些列的工具集
alsa api:
控制接口:管理声卡注册和请求可用设备的通用功能。
PCM接口:管理数字音频回放(playback)和录音(capture)的接口. !!!(最常用)
Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。
这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。
定时器(Timer)接口:为同步音频事件提供对声卡上时间处理硬件的访问。
时序器(Sequencer)接口
混音器(Mixer)接口
3:alsa调用音频设备
API库使用逻辑设备名而不是设备文件。
设备名字可以是真实的硬件名字也可以是插件名字。
硬件名字使用hw:i,j这样的格式。其中i是卡号,j是这块声卡上的设备号。
第一个声音设备是hw:0,0.这个别名默认引用第一块声音设备并且在本文示例中一真会被用到。
插件使用另外的唯一名字,比如 plughw:,表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转换这样的软件特性,硬件本身并不支持这样的特性。
3:总结
1:如何实现音频的播放。
通过了解,我们可以使用oss,或者alsa进行音频驱动的控制实现播放,因为oss比较老,很多新版本可能支持不友好,我主要参考alsa来实现音频的播放。
同时,了解到SDL基于alsa实现的,也可以用sdl实现音频的播放。
音频有很多中不同的格式,WAV, AAC, MP3要想支持多格式的音频播放,需要编解码,这里据了解,可以使用FFmpeg对编解码进行处理。
2:了解linux环境中的alsa
alsa属于开源项目:http://www.alsa-project.org/.
1:alsa软件架构
在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制.
上图可以看出,用户空间的alsa-lib对应用程序提供统一的API接口,
这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度
内核空间中,alsa-soc其实是对alsa-driver的进一步封装,他针对嵌入式设备提供了一些列增强的功能.
2:alsa设备文件结构:
hlp@ubuntu:/dev/snd$ ll
drwxr-xr-x 2 root root 60 Apr 13 09:51 by-path/
crw-rw----+ 1 root audio 116, 6 Apr 13 09:55 controlC0
crw-rw----+ 1 root audio 116, 5 Apr 13 09:55 midiC0D0
crw-rw----+ 1 root audio 116, 3 Apr 13 09:55 pcmC0D0c
crw-rw----+ 1 root audio 116, 2 Apr 13 09:55 pcmC0D0p
crw-rw----+ 1 root audio 116, 4 Apr 13 09:55 pcmC0D1p
crw-rw----+ 1 root audio 116, 1 Apr 13 09:55 seq
crw-rw----+ 1 root audio 116, 33 Apr 13 09:55 timer
我们可以看到以下设备文件:
- controlC0 --> 用于声卡的控制,例如通道选择,混音,麦克风的控制等
- midiC0D0 --> 用于播放midi音频
- pcmC0D0c --> 用于录音的pcm设备
- pcmC0D0p --> 用于播放的pcm设备
- seq --> 音序器
- timer --> 定时器
3:linux的proc目录存储一些虚拟文件,音频的proc目录如下:
hlp@ubuntu:/proc/asound$ ll
lrwxrwxrwx 1 root root 5 Apr 15 03:09 AudioPCI -> card0/
dr-xr-xr-x 9 root root 0 Apr 15 03:09 card0/
-r--r--r-- 1 root root 0 Apr 15 03:09 cards
-r--r--r-- 1 root root 0 Apr 15 03:09 devices
-r--r--r-- 1 root root 0 Apr 15 03:09 modules
dr-xr-xr-x 4 root root 0 Apr 15 03:09 oss/
-r--r--r-- 1 root root 0 Apr 15 03:09 pcm
dr-xr-xr-x 3 root root 0 Apr 15 03:09 seq/
-r--r--r-- 1 root root 0 Apr 15 03:09 timers
-r--r--r-- 1 root root 0 Apr 15 03:09 version
card0: 其中0代表的是声卡号,每个声卡系统都存在这样的目录。
cards: 列出系统中可用的,注册的声卡。
hlp@ubuntu:/proc/asound$ cat cards
0 [AudioPCI ]: ENS1371 - Ensoniq AudioPCI
Ensoniq AudioPCI ENS1371 at 0x2040, irq 16
devices: 列出系统card下所有注册的device,包括control,pcm,timer,seq等等
hlp@ubuntu:/proc/asound$ cat devices
2: [ 0- 0]: digital audio playback
3: [ 0- 0]: digital audio capture
4: [ 0- 1]: digital audio playback
5: [ 0- 0]: raw midi
6: [ 0] : control
33: : timer
hwdep: 列出所有硬件依赖(hardward dependent)的设备。此设备不是所有系统上都存在
modoles: 列出所有ALSA声卡驱动模块列表。
oss: 此目录下包含了ALSA用来模拟OSS的模拟仿真模块。
pcm: 列去出系统的cpm设备,包括capture和playback。
hlp@ubuntu:/proc/asound$ cat pcm
00-00: ES1371/1 : ES1371 DAC2/ADC : playback 1 : capture 1
00-01: ES1371/2 : ES1371 DAC1 : playback 1
seq: 此目录保护一些音序相关的信息。
timers: 描述一些ALSA相关的定时器信息。
version: 描述ALSA版本信息。
hlp@ubuntu:/proc/asound$ cat version
Advanced Linux Sound Architecture Driver Version k5.4.0-71-generic.
4:Proc目录下devices的解释:
5: dev接口信息
hlp@ubuntu:/proc/asound$ ll /dev/snd/
drwxr-xr-x 2 root root 60 Apr 13 09:51 by-path/
crw-rw----+ 1 root audio 116, 6 Apr 13 09:55 controlC0
crw-rw----+ 1 root audio 116, 5 Apr 13 09:55 midiC0D0
crw-rw----+ 1 root audio 116, 3 Apr 13 09:55 pcmC0D0c
crw-rw----+ 1 root audio 116, 2 Apr 13 09:55 pcmC0D0p
crw-rw----+ 1 root audio 116, 4 Apr 13 09:55 pcmC0D1p
crw-rw----+ 1 root audio 116, 1 Apr 13 09:55 seq
crw-rw----+ 1 root audio 116, 33 Apr 13 09:55 timer
介绍写各个设备文件的功能:
control: 用于声卡的控制。
pcmC0D0c: 用于录音的pcm设备。
pcmC0D0p: 用于播音的pcm设备。
seq: 音序器接口。
timer: 定时器接口。
linux内核中定义了如下设备类型:
enum {
SNDRV_DEVICE_TYPE_CONTROL,
SNDRV_DEVICE_TYPE_SEQUENCER,
SNDRV_DEVICE_TYPE_TIMER,
SNDRV_DEVICE_TYPE_HWDEP,
SNDRV_DEVICE_TYPE_RAWMIDI,
SNDRV_DEVICE_TYPE_PCM_PLAYBACK,
SNDRV_DEVICE_TYPE_PCM_CAPTURE,
SNDRV_DEVICE_TYPE_COMPRESS,
};
ALSA的proc接口信息的代码实现在kernel/sound/core/sound.c中。