Linux音频采集和在国产化平台中遇到的坑(一)
Linux音频采集和在国产化平台中遇到的坑(一)
最近在做一个国产化平台的软件项目的开发,是基于国产芯片的银河麒麟系统。其中有一个重要模块,是采集和播放音频数据,播放不用多说了,采集的话,包括采集麦克风和采集桌面系统声音。很多人都觉得银河麒麟不就是linux么,那不直接用ALSA就好了,我原本也是这么想的,但是实际开发下来才发现,还是有各种坑需要自己去趟的。这里我简单记录一下。
虽然都是linux,芯片也是基于同样的架构,同样的指令集,但是考虑到芯片的实现毕竟是不同的,于是所有涉及到硬件交互的软件部分,也会有所差异,最终会导致了有些应用层面的接口,不能按照普通linux的通常用法去使用。
linux ALSA音频采集
首先,银河麒麟既然是linux系统,那首先考虑到的是通过ALSA(Advanced Linux Sound Architecture)来进行采集,ALSA是linux的默认声卡驱动,同时在用户层还有一个ALSA Lib来供应用程序调用,它的整体上的结构图是这个样子的:
应用程序通常都是通过alsa-lib来使用,如果系统没有的话,可以通过命令安装开发库,就可以使用了。例如
sudo apt-get install libasound2-dev
另外需要注意一点的是,如果是android系统,那么系统里通常是不存在alsa的,而是它的简化版tiny-alsa,接口名称也不一样,但是大致调用流程是相同的。
alsa音频采集,有几个关键函数
#include <sys/asoundlib.h>
创建alsa pcm handle去连接设备
@param handle: 返回创建的PCM handle
@param name: 设备名称,ASCII编码
@param stream: 标明采集或者播放(SND_PCM_STREAM_CAPTURE, SND_PCM_STREAM_PLAYBACK)
@param mode: 打开模式(see SND_PCM_NONBLOCK, SND_PCM_ASYNC)
@return: 0表示成功,小于0表示错误
int snd_pcm_open( snd_pcm_t **handle, const char* name, int stream, int mode );
读取音频帧
@param handle: PCM handle
@param buffer: frames containing buffer
@param size: frames to be read
@return: 实际读取的音频帧个数,小于0表示错误
ssize_t snd_pcm_readi( snd_pcm_t *handle, void *buffer, size_t size );
@param handle: PCM handle
@return: 实际读取的音频帧个数,小于0表示错误
int snd_pcm_close( snd_pcm_t *handle );
准备使用PCM
@param handle: PCM handle
@return: 实际读取的音频帧个数,小于0表示错误
int snd_pcm_prepare( snd_pcm_t *handle );
接口简单,参数也少,所以使用起来很方便,基本上是linux下采集和播放的第一选择,下面写个简单的例子演示下如何调用
- 打开音频设备并设置参数
SIMPLE_LOG("try open %s\n", device_name_.c_str());
int ret = snd_pcm_open(&alsa_pcm_, device_name_.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
if (!alsa_pcm_ || ret < 0)
SIMPLE_LOG("open %s failed, ret: %d\n", device_name_.c_str(), ret);
return false;
snd_pcm_hw_params_t* params;
snd_pcm_hw_params_alloca (¶ms);
snd_pcm_hw_params_any (alsa_pcm_, params);
snd_pcm_hw_params_set_access (alsa_pcm_, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_format_t format;
switch (bits_per_sam_) {
case 8:
format = SND_PCM_FORMAT_S8;
break;
case 16:
format = SND_PCM_FORMAT_S16_LE;
break;
case 24:
format = SND_PCM_FORMAT_S24_LE;
break;
case 32:
format = SND_PCM_FORMAT_S32_LE;
break;
default:
format = SND_PCM_FORMAT_S16_LE;
break;
snd_pcm_hw_params_set_format (alsa_pcm_, params, format);
snd_pcm_hw_params_set_channels (alsa_pcm_, params, channel_count_);
unsigned int rate = sample_rate_;
snd_pcm_hw_params_set_rate_near (alsa_pcm_, params, &rate, NULL);
sample_size_ = channel_count_ * (bits_per_sam_/8);
/* Activate the parameters */
ret = snd_pcm_hw_params (alsa_pcm_, params);
if (ret < 0)
SIMPLE_LOG("set param failed, ret: %d\n", ret);
snd_pcm_close (alsa_pcm_);
alsa_pcm_ = NULL;
return false;
}
- 读取音频数据
bool AlsaCapture::ReadData()
int read_size = 0;
snd_pcm_uframes_t need_frames = real_sample_count_;
for (;;)
if (read_size >= pcm_buf_.size())
break;
int ret = 0;
while (true)
char* read_buf = &pcm_buf_[0] + read_size;
ret = snd_pcm_readi(alsa_pcm_, read_buf, need_frames);
if (ret >= 0)
break;
if (ret == -EAGAIN)
SIMPLE_LOG("snd_pcm_readi EAGAIN\n");
return false;
if (AlsaXRunRecover(alsa_pcm_, ret) < 0)
SIMPLE_LOG("ALSA read error: %s\n", snd_strerror(ret));
return false;
read_size += ret * sample_size_;
need_frames -= ret;