音频信号是一种连续变化的模拟信号,但计算机只能处理和记录二进制的数字信号,由自然音源得到的音频信号必须经过一定的变换,成为数字音频信号之后,才能送到计算机中作进一步的处理。
数字音频系统通过将声波的波型转换成一系列二进制数据,来实现对原始声音的重现,实现这一步骤的设备常被称为(A/D)。A/D转换器以每秒钟上万次的速率对声波进行采样,每个采样点都记录下了原始模拟声波在某一时刻的状态,通常称之为样本(sample),而每一秒钟所采样的数目则称为采样频率,通过将一串连续的样本连接起来,就可以在计算机中描述一段声音了。对于采样过程中的每一个样本来说,数字音频系统会分配一定存储位来记录声波的振幅,一般称之为采样分辩率或者采样精度,采样精度越高,声音还原时就会越细腻。
数字音频涉及到的概念非常多,对于在Linux下进行音频编程的程序员来说,最重要的是7406解声音数字化的两个关键步骤:采样和量化。
采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。采样频率的选择应该遵循奈奎斯特(Harry Nyquist)采样理论:如果对某一模拟信号进行采样,则采样后可还原的最高信号频率只有采样频率的一半,或者说只要采样频率高于输入信号最高频率的两倍,就能从采样信号系列重构原始信号。
如上图所示 用40KHz的频率去采样20KHz的信号可以正确捕捉到原始信号。用30KHz的频率去采样20KHz的信号会出现混淆信号。
一般重建音乐信号时采用的最低采样频率为44.1KHz。在许多高品质的系统中,采用的48KHz的采样频率。
系统 | 采样频率 |
---|---|
电话 | 8000Hz |
CD | 44100Hz |
专业音频 | 48000Hz |
DVD音频 | 96000Hz |
量化位数是对模拟音频信号的幅度进行数字化,它决定了模拟信号数字化以后的动态范围,常用的有8位、12位和16位。量化位越高,信号的动态范围越大,数字化后的音频信号就越可能接近原始信号,但所需要的存贮空间也越大。
音频应用中常用的数字表示方法为脉冲编码调制(Pulse-Code-Modulated,PCM)信号。在这种表示方法中,每个采样周期用一个数字电平对模拟信号的幅度进行编码。得到的数字波形是一组采样自输入模拟波形的近似值。由于所有A/D转换器的分辨率都是有限的,所以在数字音频系统中,A/D转换器带来的量化噪声是不可避免的。
ALSA全称是Advanced Linux Sound Architecture,中文音译是Linux高级声音体系。ALSA 是Linux内核2.6后续版本中支持音频系统的标准接口程序,由ALSA库、内核驱动和相关测 试开发工具组成,更好的管理Linux中音频系统。
本小节将介绍ALSA的架构。
ALSA是Linux系统中为声卡提供驱动的内核组件。它提供了专门的库函数来简化相应应用程序的编写。相较于OSS的编程接口,ALSA的函数库更加便于使用。
对应用程序而言ALSA无疑是一个更佳的选择,因为它具有更加友好的编程接口,并且完全兼容于OSS。
ALSA系统包括7个子项目:
ALSA声卡驱动与用户空间体系结构交互如下图所示:
移植ALSA主要是移植alsa-Ub和alsa-utils。
ALSA Util是纯应用层的软件,相当于ALSA设备的测试程序,ALSA-Lib则是支持应用API的中间层程序,ALSA-Util中的应用程序中会调用到ALSA-Lib中的接口来操作到我们的音频编解码芯片的寄存器,而lib中接口就是依赖于最底层驱动代码,因此移植ALSA程序的顺序就是先后移植Driver,Lib,Util。
ALSA首先需要在ALSA的官网上下载官网http://www.alsa-project.org下载alsa-lib和alsa-utils。
如上图所示我们下载的版本为:
ALSA Lib移植不需要修改源码,只需要重新编译库代码以支持自己的平台。
tar -xvf alsa-lib-1.0.27.2.tar.bz2
cd alsa-lib-1.0.27.2
CC=arm-none-linux-gnueabi-gcc
./configure --host=arm-linux --prefix=/home/m/3rd/alsa/install/
make
make install
在上述命令中./configure配置的几个重要的配置选项解释如下:
ALSA Util可以生成用于播放,录制,配置音频的应用可执行文件,测试驱动代码时用处很大,编译过程如下:
tar -xvf alsa-utils-1.0.27.2.tar.bz2
cd alsa-utils-1.0.27.2
CC=arm-none-linux-gnueabi-gcc
./configure --prefix=/home/m/3rd/alsa/install/ --host=arm-linux --with-alsa-inc-prefix=/home/m/3rd/alsa/install/include --with-alsa-prefix=/home/m/3rd/alsa/install/lib --disable-alsamixer --disable-xmlto --disable-nls
make
ALSA库和测试工具的移植就是将相应库文件和可执行文件放在目标板上,以下文件 必须被拷贝至对应位置 :
while (r > 0)
{
snd_pcm_wait(handle, 100);
do
{
res = snd_pcm_writei(handle, input_buffer, frames);
if (res == -EPIPE){
AUDIO_DEV_UNLOCK;
snd_pcm_prepare(handle);
continue;
}
}while (res < 0);
r -= err;
input_buffer += res * bits_per_frame / 8;
}
return 0;
}
## 8.9 基于ALSA音频的录制
### 8.9.1 程序设计
- 文件列表
| 序号 | 文件名 | 描述 |
| ---- | --------------- | -------------- |
| 1 | AlsaCapture.h | 音频录制头文件 |
| 2 | AlsaCapture.cpp | 音频录制程序 |
- 成员函数设计
| 序号 | 函数名 | 参数 | 参数描述 | 函数描述 |
| ---- | ------- | ---- | -------- | -------- |
| 1 | capture | 无 | | 录制音频 |
- 成员变量设计
| 序号 | 成员变量名 | 类型 | 描述 |
| ---- | -------------- | ----------------- | -------------------- |
| 1 | _VOLUMECHANGE | const float | 音量调节步进大小 |
| 2 | handle | snd_mixer_t* | Mixer handle |
| 3 | element_handle | snd_mixer_elem_t* | Mixer element handle |
| 4 | minVolume | long | 最小音量 |
| 5 | maxVolume | long | 最大音量 |
### 8.9.2 AlsaPlay类的定义
```c
#pragma once
#include "AlsaBase.h"
namespace rv1108_audio{
class AlsaCapture : public AlsaBase
{
public:
// 输出数据缓存
char *output_buffer;
// 输出缓存大小
unsigned int output_buffer_size;
// int frames_to_read;
// 用于返回已读的帧数
int frames_readed;
AlsaCapture(const std::string &dev);
~AlsaCapture();
int open_device();
int capture();
private:
int err;
};
}
AlsaCapture::AlsaCapture(const std::string &dev) : AlsaBase(dev)
{
if (!DEVICE_OPENED)
open_device();
if (!PARAMS_SETED)
set_params();
output_buffer_size = default_output_buffer_size;
output_buffer = (char *)calloc(output_buffer_size, sizeof(char));
}
int AlsaCapture::open_device()
{
if ((err = snd_pcm_open(&handle,
device,
SND_PCM_STREAM_CAPTURE,
0)) < 0)
{
DEVICE_OPENED = false;
return -1;
}
else
{
DEVICE_OPENED = true;
}
return 0;
}
int AlsaCapture::capture()
{
while (1)
{
int err;
if ((frames_readed = snd_pcm_readi(handle, output_buffer, frames)) < 0)
{
// Overrun happened
if (frames_readed == -EPIPE)
{
snd_pcm_prepare(handle);
continue;
}
return -1;
}
else
{
return frames_readed;
}
}
}
更多回帖