最近在做ALSA音频采集、编码相关的东西,也是一点点了解这方面的知识,查了挺多资料,当然很多都是大同小异,无非介绍一些ALSA的由来、相关参数以及ALSA编程采样或者回放音频,虽然直接把代码拷贝编译后,可以正常执行音频的采集与回放,但总感觉有的细节没有摸索清楚。
在完成项目的同时,通过网上的资料和自己的理解把ALSA的一些东西梳理一下,一方面给有需要的朋友提供有用的帮助,另一方面,也是自己对ALSA的总结。
ALSA(Advanced Linux Sound Architecture)高级Linux音频架构,为 Linux 系统提供了统一的音频和MIDI功能和驱动。它由许多声卡的声卡驱动程序组成,同时它也提供一个称为libasound的API库,应用程序开发者应该使用libasound而不是内核中的 ALSA接口,因为libasound提供最高级并且编程方便的编程接口。ALSA提供一系列基于命令行的工具集,比如混音器(mixer),音频文件播放器(aplay),以及控制特定卡特定属性的工具。
ALSA官网链接
ALSA提供的API库可以分解成以下几个主要的接口:
- 控制接口:提供管理声卡注册和请求可用设备的通用功能。
- PCM接口:管理数字音频回放(playback)和录音(capture)的接口。本文后续总结重点放在这个接口上,因为它是开发数字音频程序最常用到的接口。
- Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。
- 定时器(Timer)接口:为同步音频事件提供对声卡上时间处理硬件的访问。
- 时序器(Sequencer)接口
- 混音器(Mixer)接口
音频采样过程是一个将麦克风的模拟信号(电压)按一定的周期进行采样(即采样率),并将采样的数据转换为数字信号(0,1)的过程,在数字电路中称之为ADC(Analog-Digital Conversion,模数转换),具体实现是通过逐次逼近电路将模拟信号进行量化,量化时有一个精度,这个精度就是采样长度,量化后得到对应的数字量以便于在数字设备中传输;
音频回放过程是一个将声卡中的数字信号还原为模拟信号的过程,在数字电路中称之为DAC(Digital -Analog Conversion,数模转换),具体实现是通过数模转换器电路将数字信号还原为模拟信号(电压),最终将转换后的模拟信号传送到喇叭,喇叭根据电信号的特征播放出声音。
PCM(Pulse Code Modulation----脉冲编码调制)即将音频采样量化过程后变成符号化的脉冲序列,并予以记录。PCM是由数字信号(0,1)未经过任何编码和压缩处理的数字量。与模拟信号比,它不易受传送系统的杂波及失真的影响。动态范围宽,可得到音质相当好的影响效果。PCM数据是最原始的音频数据完全无损。
ALSA的API库的PCM接口中有一些参数是需要知道的,这些参数也是音频相关的基本知识:
- 采样长度(sample_length):将音频进行采样量化的精度,有
8bit/16bit
;- 采样通道(sample_channels):音频采样的通路数,比如立体声就有2个采样通路数分别进行左右声道的采样,单声道就有1个通路数进行单声道采样;
- 采样帧大小(sample_frame):以字节Byte为单位,是采样长度与采样通道的乘积,由于采样长度单位是位,所以这个乘积的单位是位,要想转换为字节,需要将这个乘积再除以8,因此采样帧字节大小公式即
sample_frame=sample_length*sample_channels/8
;举个栗子,采样长度位16bit,采样通道为立体声,那么帧大小(字节)=16*2/8=4Byte;- 采样周期(sample_period):指多久完成一次采样;
- 采样率(sample_rate):指每秒采样的帧数,注意这里是每秒采样的帧数,不是每秒采样的位数/字节数。
- 交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。
设定采样长度为16bit,采样通道为立体声,采样率为16K,采样周期为20ms,那么采样帧大小是多少?每秒有多少个采样周期?每个采样周期采样到的帧数是多少?需要多少字节的缓存区存放每个周期的数据量?
·
首先 sample_length=16 sample_channels=2 sample_rate=16000 sample_period=20ms
a. 采样帧大小:sample_frame=sample_length*sample_channels/8=4
;
b. 因为采样周期知道(sample_period=20ms),1秒的采样周期个数=1000/20=50
;
c. 每个采样周期采样到的帧数:因为每秒钟的采样帧数已知sample_rate=16000,根据b又知道每秒有50个采样周期,所以每个周期的采样帧数=16000/50=320
;(当然也可以通过sample_rate*sample_period
求得)
d. 由c知道每个采样周期的帧数为320,而由a知道sample_frame=4,所以每帧的数据量为320*4=1280Byte
,因此需要1280字节存放每个周期的数据量。
在这里目的不是为了求解,目的在于理解,因为这些东西理解清楚了,在进行音频采集和回放时采用多大的缓冲区就明白了。在利用PCM接口进行音频的采集和回放时,每次读取和写入的数据量是每个采样周期的帧数;在使用PCM接口进行读数据时,读取的数据量是每个采样周期采样到的帧数,读出的数据缓存区大小PCM底层会自动填充至每个采样周期的数据量大小。比如在上述例子中,通过PCM接口读取和写入的数据量大小应该为每个采样周期采样到的帧数即320,而读出的数据buffer会被填充为采样周期的数据量即1280B;
参考链接Alsa音频编程
可先参考上面的链接,后续会发布PCM的音视频采集和回放的源码
Waveform Audio File Format(WAVE,又或者是因为扩展名而被大众所知的WAV),是微软与IBM公司所开发在个人电脑存储音频流的编码格式,此格式属于资源交换文件格式(RIFF)的应用之一,通常会将采用PCM的音频资存储在区块中。由于此音频格式未经过压缩,所以在音质方面不会出现失真的情况,但文件的体积因而在众多音频格式中较为大。
WAV文件遵守RIFF(Resource Interchange File Format 资源交换文件格式)规则,在文件的前44(或46)字节放置标头(header),使播放器或编辑器能够简单掌握文件的基本信息。标头的前3个区块记录文件格式及长度;接着第一个子区块包含8个区块,记录声道数量、采样率等信息;接着第二个子区块才是真正的音频数据,长度则视音频长度而定。内容如下表所示。须注意的是,每个区块的端序不尽相同,而音频数据本身则是采用小端序
1。
起始地址(字节) | 区块名称 | 区块大小 | 端序 | 区块内容 |
---|---|---|---|---|
0 | 区块编号 | 4 | 大 | “RIFF” |
4 | 总区块大小 | 4 | 小 | =N+36 |
8 | 档案格式 | 4 | 小 | “WAVE” |
12 | 子区块标签 | 4 | 大 | “fmt” |
16 | 子区块大小 | 4 | 小 | 16 |
18 | 音频格式 | 2 | 小 | 1(PCM) |
20 | 声道数量 | 2 | 小 | 1(单声道) 2(立体声) |
22 | 采样频率 | 4 | 小 | 每秒的采样帧数 |
26 | 位元(组)率 | 4 | 小 | =采样频率*位元深度/8 |
30 | 区块对齐 | 2 | 小 | 4 |
32 | 位元深度 | 2 | 小 | 指采样长度 |
36 | 子区块2标签 | 4 | 大 | “data” |
40 | 子区块2大小 | 4 | 小 | N(=位元(组)率秒数声道数量) |
44 | 音频数据 | =N | 小 | PCM音频数据 |
opus是有损音频编码的一种适用于网络上的实时声音传输的编码格式。在使用中可以使用opus的开源库,在opus官网可以阅读其相关内容。
wget https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz
tar -zxvf opus-1.3.1.tar.gz
./configure
./configure --perfix=${path}
${path}替换为指定的安装目录make
make install
wget https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz
tar -zxvf opus-1.3.1.tar.gz
./configure --host=交叉工具链名称 CC=gcc CXX=g++
./configure --perfix=${path} --host=交叉工具链名称 CC=gcc CXX=g++
${path}替换为指定的安装目录make
make install
- 采样率约束:输入信号的采样率(Hz),必须是8000、12000、16000、24000、或48000。
- 帧长约束:opus为了对一个帧进行编码,必须正确地用音频数据的帧(2.5, 5, 10, 20, 40 or 60 ms)来调用opus_encode()函数。比如,在16kHz的采样率下,opus_encode()参数中的合法的frame_size(单通道的帧大小)值只有:40, 80, 160, 320, 640, 960m,即:frame_size = 采样率 * 帧时间。
- 兼容opus的容器格式:有ogg,ts,mkv。但ts无法播放,mkv只能foobar播放,ogg能用foobar,vlc播放。
opus编码
// 创建编码器
OpusEncoder *opus_encoder_create(
opus_int32 Fs, // 采样率,可以设置的大小为8000, 12000, 16000, 24000, 48000,opus_int32(int)
int channels, // 声道数,网络中实时音频一般为单声道
int application, // 应用场景 OPUS_APPLICATION_VOIP/OPUS_APPLICATION_AUDIO
int *error // 是否创建失败,返回0表示创建成功
) // 成功返回OpusEncoder首地址,失败返回NULL
// 销毁编码器
void opus_encoder_destroy(
OpusEncoder *st // 创建编码器的返回值
)
// 编码器参数设置
int opus_encoder_ctl(
OpusEncoder *st, // 创建编码器的返回值
int request, // 参数设置值
... // 其他参数
) // 成功返回0,失败返回-1
// 编码
opus_int32 opus_encode(
OpusEncoder *st, // 创建编码器的返回值
const opus_int16 *pcm, // pcm数据,传入参数,opus_int16(short)
int frame_size, // pcm每个周期的采样帧数
unsigned char *data, // 编码后的数据,传出参数
opus_int32 max_data_bytes // 最大的编码缓存区大小,opus_int32(int)
) // 成功返回编码后的数据量大小,失败返回-1
实例:
OpusEncoder *tOpusEncoder;
int OpusEncoderInit(void)
{
int opus_err;
tOpusEncoder=opus_encoder_create(16000,2,OPUS_APPLICATION_VOIP,&opus_err);
if (!m_tOpusEncoder || opus_err < 0)
{
if (!m_tOpusEncoder) {
opus_encoder_destroy(tOpusEncoder);
}
return -1;
}
opus_encoder_ctl(tOpusEncoder, OPUS_SET_VBR(0)); //关闭可变码率
opus_encoder_ctl(tOpusEncoder, OPUS_SET_BITRATE(128000));//设置固定码率128k
return 0;
}
voidOpusEncoderDeinit(void)
{
opus_encoder_destroy(tOpusEncoder);
}
opus_int32 OpusEncode(const opus_int16 pcm,int frame_size,unsigned char *opus_data,opus_int32 max_data_bytes)
{
return opus_encode(tOpusEncoder,pcm,frame_size,opus_data,opus_data_max_size);
}
opus解码
// 创建解码器
OpusDecoder *opus_decoder_create(
opus_int32 Fs, // 采样率,可以设置的大小为8000, 12000, 16000, 24000, 48000.
int channels, // 声道数,网络中实时音频一般为单声道
int *error // 是否创建失败,返回0表示创建成功
); // 成功返回OpusDecoder首地址,失败返回NULL
// 销毁解码器
void opus_decoder_destroy(
OpusDecoder *st // 创建解码器的返回值
)
// 编码器参数设置
int opus_decoder_ctl(
OpusDecoder *st, // 创建解码器的返回值
int request, // 参数设置值
... // 其他参数
) // 成功返回0,失败返回-1
// opus解码
int opus_decode(
OpusDecoder *st, // 创建解码器的返回值
const unsigned char *data, // 要解码的opus数据
opus_int32 len, // opus数据长度
opus_int16 *pcm, // 解码后的数据,注意是一个以16位长度为基本单位的数组
int frame_size, // pcm每个周期的采样帧数
int decode_fec // 是否需要fec,设置0为不需要,1为需要
)
编程实例见参考链接4
[参考链接]
大小端序,指数据的高低字节在内存中的存放方式;大端序,也称大端模式,是指数据的高字节存储到内存的低地址,数据的低字节存储到内存的高地址;小端序,也称小端模式,是指数据的高字节存储到内存的高地址,数据的低字节存储到内存的低地址。 ↩︎