Alsa项目的官方网址:http://www.alsa-project.org/
详细插件详细解释:https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html
Alsa LIB API Reference:http://www.alsa-project.org/alsa-doc/alsa-lib/
配置文件的语法: http://www.alsa-project.org/alsa-doc/alsa-lib/conf.html
Asoundrc的官方说明文档:http://www.alsa-project.org/main/index.php/
Asoundrcarchlinux文档:https://wiki.archlinux.org/title/Advanced_Linux_Sound_Architecture/Configuration_examples
配置文件的位置
配置文件的位置是由Configure阶段的选项来决定的,不过多数时候,Alsa的配置文件位于:/usr/share/alsa目录下,主要配置文件为/usr/share/alsa/alsa.conf 其它文件是否需要,位置在哪,都是由alsa.conf来决定的。
通常会有/usr/share/alsa/card和/usr/share/alsa/pcm两个子目录,用于设置Card相关的参数,别名以及一些PCM默认设置。
此外,在alsa.conf中,通常还会引用 /etc/asound.conf和 ~/.asoundrc这两个配置文件,这两个文件通常是放置你个人需要特殊设置的相关参数。按照Alsa官方文档的说法,1.0.9版本以后,这两个文件就不再是必要的,甚至是不应该需要的。至少是不推荐使用吧。不过,对于我来说,在嵌入式系统中使用,为了简单和方便测试,恰恰是需要修改这两个文件
Alsa插件目录
/ # ls /usr/lib/alsa-lib/
libasound_module_ctl_arcam_av.so
libasound_module_ctl_oss.so
libasound_module_pcm_oss.so
libasound_module_pcm_upmix.so
libasound_module_pcm_usb_stream.so
libasound_module_pcm_vdownmix.so
libasound_module_rate_speexrate.so
libasound_module_rate_speexrate_best.so
libasound_module_rate_speexrate_medium.so
Alsa.conf
Alsa.conf中主要的一些内容包括:用hook读取了/etc/asound.conf 和 ~/.asoundrc这两个配置文件:
@hooks [
{
func load
files [
"/etc/asound.conf"
"~/.asoundrc"
]
errors false
}
]
设置了default pcm的一些默认参数,如,默认使用Card 0 ,Device 0作为音频设备等等。
defaults.ctl.card 0
defaults.pcm.card 0
defaults.pcm.device 0
defaults.pcm.subdevice -1
defaults.pcm.nonblock 1
defaults.pcm.ipc_key 5678293
设置了Alsa 内置的一些plugin的接口参数,例如file:
pcm.file {
@args [ FILE FORMAT ]
@args.FILE {
type string
}
@args.FORMAT {
type string
default raw
}
type file
slave.pcm null
file $FILE
format $FORMAT
}
File plugin的作用是将PCM数据流存储到文件中。此外,通常alsa.conf还会载入/cards/aliases.conf ,设置一些声卡的别名等,这个我是不需要了。在aliases.conf 的结尾还有以下一段:
<confdir:pcm/default.conf>
<confdir:pcm/dmix.conf>
<confdir:pcm/dsnoop.conf>
用来读入/usr/share/alsa/pcm目录下所列的那3个文件分别设置 默认PCM设备的相关参数,dmix是用来实现播放时软件混音的内建plugin,dsnoop则是用来实现录音时多路分发的内建plugin。
一些配置和使用实例
使用蓝牙设备
- 在
/etc/asound.conf中添加下列一项用来使用蓝牙的A2DP设备
#device for bluetooth
pcm.bluetooth{
type bluetooth
device 00:02:5B:00:C1:A0
}
然后调用 aplay –D bluetooth sample.wav播放。需要注意,为了使用该设备,你需要/usr/lib/alsa-lib/libasound_module_pcm_bluetooth.so 这一个蓝牙plugin的库文件。这是在Bluez相关的包里,和Alsa本身没有关系。从这里,我们也可以看出alsa的外部plugin和配置文件之间的名字关系规则:libasound_module_pcm_####.so 这里的#### 就是你再conf文件中pcm.xxxx 里所写的名字。
使用指定节点
在我的板子上,Buildin的Audio硬件在Alsa子系统中实现了两个硬件通道,一个是HIFI通道,另一个是语音通道,所以我添加了如下配置:
#device for voice channel
pcm.voice{
type plug
slave{
pcm "hw:0,1"
}
}
通过语音通道播放声音的调用的方式: aplay –D voice sample.wav这样的写法说明我通过plug这plugin对音频数据进行自动的采样率,通道等调整后,将数据送到我的第0个card的序号为1的device上。实际上,如果不写上述配置文件,用aplay -D "plug:SLAVE='hw:0,1'" sample.wav也可以得到同样的结果。
Hifi通道播放声音直接使用aplay sample.wav即可 也就是aplay –D default sample.wav
S16_LE转换S32_LE
通过arecord -D ref -c2 -r32000 -fS32_LE /data/rec.wav命令可以采集到S32_LE数据,hw:0,0只支持format S16_LE,slave指定使用S16_LE采集(必须指定否则会根据命令行参数使用S32_LE进行采集),通过plugin转成S32_LE
pcm.ref {
type plug
slave {
pcm "hw:0,0"
format S16_LE
}
}
多通道组合虚拟声卡
使用aplay -Dhw:0,0 /data/cn_32k.wav &和arecord -Dpcm.mu -f S32_LE -r 32000 -c 8 /data/rec_8ch.wav 可以将回采信号和mic array信号复合到一个声卡中,并以32bit进行采集,回采信号不支持32bit,使用refplugin转到了32bit。mic array使用32bit采集。通过arecord -Dpcm.mu -f S32_LE -r 32000 -c 8 /data/rec_8ch.wav -v可以清晰看到pipeline
pcm.mu {
type multi
slaves.a.pcm "hw:1,0"
slaves.b.pcm ref
slaves.a.channels 8
slaves.b.channels 2
bindings.0.slave a
bindings.0.channel 0
bindings.1.slave a
bindings.1.channel 1
bindings.2.slave a
bindings.2.channel 2
bindings.3.slave a
bindings.3.channel 3
bindings.4.slave a
bindings.4.channel 4
bindings.5.slave a
bindings.5.channel 5
bindings.6.slave b
bindings.6.channel 0
bindings.7.slave b
bindings.7.channel 1
}
# Sends to the two dmix interfaces
pcm.quad {
type multi
# Necessary to have both slaves be dmix; both as hw doesn't give errors, but wouldn't
slaves.a.pcm "dmixerout"
slaves.a.channels 2
slaves.b.pcm "dmixerloop"
slaves.b.channels 2
bindings {
0 { slave a; channel 0; }
1 { slave a; channel 1; }
2 { slave b; channel 0; }
3 { slave b; channel 1; }
}
}
softvol:
实现软件调节音量
pcm.softvol {
type softvol
slave.pcm "playback"
control {
name "playback_softvol"
card 2
}
min_dB -40.0
max_dB 0.0
resolution 100
}
创建了一个softvolPCM设备,其音量由名为playback_softvol的新音量控件控制音量改变的音频流将被传送到playback设备。由于该插件没有任何变化,因此新设备的音量,采样格式,采样率 和通道数与从设备的值相同。
- 首次使用新定义的设备只有使用一次新PCM设备后,才会出现
playback_softvol控件,同时也可以通过alsamixeramixer调节音量
aplay -D softvol /data/test.wav
amixer contents -c2
amixer cset -c2 numid=53,iface=MIXER,name='playback_softvol' 10
dmix插件:
dmix插件只能用于播放aplay -D real_playback sample.wav &你可以通过多次调用上述命令来测试多个音频数据的混音。也可以使用aplay -D dmix sample.wav &调用内置dmix
pcm.real_playback {
type dmix
ipc_key 5978293# must be unique for all dmix plugins!!!!
ipc_key_add_uid yes
slave {
pcm "hw:0,0"
channels 2
rate 48000
period_size 1024
buffer_size 4096
}
bindings {
0 0
1 1
}
}
- Dump音频数据:
aplay -D "plug:'file:FILE=/tmp/dump.bin'" sample.wav
定义一组输入输出设备
pcm.uac {
type asym
playback.pcm {
type plug
slave.pcm "uac_playback"
}
capture.pcm {
type plug
slave.pcm "uac_capture"
}
}
DownMix 插件
使用-v 可以看到详细的Transformation table
pcm.uac_capture_mono {
type vdownmix
slave.pcm "uac_capture"
slave.channels 2
ttable {
0.0 0.5
0.1 0.5
}
}
/ # arecord -D uac_capture_mono -c1 -r48000 -fS16_LE /data/rec.wav -v
Recording WAVE '/data/rec.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono
Route conversion PCM
Transformation table:
0 <- 0*0.5 + 1*0.5
1 <- 0*0.5
Its setup is:
stream : CAPTURE
access : RW_INTERLEAVED
format : S16_LE
subformat : STD
channels : 1
rate : 48000
Route 插件
arecord
与downmix类似将2通道音频downmix到1通道
pcm.uac_capture_mono {
type route
slave.pcm "uac_capture"
slave.channels 2
ttable {
0.0 0.5
0.1 0.5
}
}
ttable {
master.slave gain
master.slave gain
}
-v 可以查看详细转换关系:
Slave #1: Route conversion PCM
Transformation table:
0 <- 0*0.5 + 1*0.5
aplay
pcm.uac_playback_mono {
type route
slave.pcm "uac_playback"
slave.channels 2
ttable {
0.0 1
0.1 1
}
}
Slave #0: Route conversion PCM
Transformation table:
0 <- 0 * 1
1 <- 0 * 1
route plugin将2ch数据映射输出到4ch的multi插件中
# Duplicates to quad, use this to output to loopback & external
pcm.stereo2quad {
type route
slave.pcm "quad"
# ttable.A.B G
# where A - master channel
# B - slave channel
# G - volume gain (1.0 = original)
ttable.0.0 1
ttable.1.1 1
ttable.0.2 1
ttable.1.3 1
}
pcm.sub0_route {
type route
slave.pcm sub0_multi
ttable [
[1 0 1 0] # ch0 映射到 ch0/ch2
[0 1 0 1] # ch1 映射到 ch1/ch3
]
}
pcm.sub0_multi {
type multi
slaves.a.pcm main_proxy
slaves.b.pcm sub0_dst
slaves.a.channels 2
slaves.b.channels 2
bindings.0.slave a
bindings.0.channel 0
bindings.1.slave a
bindings.1.channel 1
bindings.2.slave b
bindings.2.channel 0
bindings.3.slave b
bindings.3.channel 1
}
dsnoop 插件
dsnoop插件用于录音场景,将一个捕获流拆分为多个捕获流。它的工作方式与dmix插件相反,多个客户端同时读取共享捕获缓冲区。以下参数的含义与dmix插件几乎相同。
pcm.cap_share {
type dsnoop
ipc_key 112233
slave {
pcm "hw:0,5"
channels 8
rate 32000
}
}
resample 插件
# defaults.pcm.rate_converter "speexrate_medium"
pcm.uac_playback {
type rate # 定义使用采样率转换插件
slave {
pcm "hw:2,0" # 硬件设备
rate 48000 # 表示从src_rate转成48k最终播放出去
}
}
需要编译alsa-plugins,
ls /usr/lib64/alsa-lib/libasound_module_
libasound_module_ctl_arcam_av.so
libasound_module_ctl_oss.so
libasound_module_pcm_oss.so
libasound_module_pcm_upmix.so
libasound_module_pcm_usb_stream.so
libasound_module_pcm_vdownmix.so
libasound_module_rate_speexrate.so
libasound_module_rate_speexrate_best.so
libasound_module_rate_speexrate_medium.so
另外card_no可以使用card_name替换
pcm.wireless_playback {
type rate
slave {
pcm "hw:u2wl,0"
rate 48000
}
}
pcm.wireless_capture {
type rate
slave {
pcm "hw:u2wl,0"
rate 48000
}
}
pcm.softvol {
type softvol
slave.pcm "playback"
control {
name "playback_softvol"
card rockchipak7755
}
min_dB -50.0
max_dB 0.0
resolution 100
}
获取card***_no, card_name方式***
arecord -l
aplay -l
cat /proc/asound/cards
# cat /proc/asound/cards
0 [rockchiprk3308a]: rockchip_rk3308 - rockchip,rk3308-acodec
rockchip,rk3308-acodec
1 [rockchippdmmica]: rockchip_pdm-mi - rockchip,pdm-mic-array
rockchip,pdm-mic-array
2 [u2wl ]: u2-wl - u2-wl
u2-wl
3 [rockchipak7755 ]: rockchip_ak7755 - rockchip,ak7755
rockchip,ak7755
4 [UAC1Gadget ]: UAC1_Gadget - UAC1_Gadget
UAC1_Gadget 0
7 [Loopback ]: Loopback - Loopback
Loopback 1
ALSA External PCM plugin SDK (ALSA扩展插件)
- alsa从
/usr/lib/alsa-lib/目录查找plugin,将libasound_module_pcm_fellow.socopy到/usr/lib/alsa-lib;并将/usr/lib/alsa-lib加到/etc/ld.config,否则有可能在运行时,找不到so - alsa plugin命名方式
libasound_module_pcm_XXX.so,名为XXX的插件
相关资料:
ALSA扩展插件分为2类:
External Filter plugin
- 滤波器类型插件,用于转换输入的PCM信号并输出到Slave。因此,该插件必须有Slave PCM 作为其输出。
- 该插件可以修改输入/输出PCM的
format和channel。但是 不能 修改rate,因为只是过滤型plugin - 在调用
snd_pcm_extplug_create()之前,必须在extplug记录中填写以下字段 :version、name、callback。其他字段(private_data、parms)是可选的,应初始化为零. - 必须将常量
SND_PCM_EXTPLUG_VERSION传递给 alsa-lib 中的版本检查的版本字段version。 - 必须将非 NULL ASCII 字符串传递给名称字段
name。 callback字段包含此插件的回调函数表(定义为snd_pcm_extplug_callback_t)。private_data用户私有数据,在回调中引用私有数据
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <alsa/asoundlib.h>
#include <alsa/pcm_external.h>
struct ctx_parms {
int frames;
int enable_dump;
FILE *dump_fp;
float gain;
};
typedef struct {
snd_pcm_extplug_t ext;
struct ctx_parms parms;
short *buf;
short *outbuf;
unsigned int filled;
unsigned int processed;
}snd_pcm_fellowext_t;
static inline void *area_addr(const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset)
{
unsigned int bitofs = area->first + area->step * offset;
return (char *)area->addr + bitofs / 8;
}
static void process(snd_pcm_fellowext_t *ctx)
{
int frames = ctx->parms.frames;
short *inbuf = ctx->buf;
short *outbuf= ctx->outbuf;
int channels= ctx->ext.channels;
int ch_idx = 0, frame_idx = 0;
for (frame_idx = 0; frame_idx < frames; frame_idx++)
{
for (ch_idx = 0; ch_idx < channels; ch_idx++)
{
outbuf[frame_idx * channels + ch_idx] = (short)((float)inbuf[frame_idx * channels + ch_idx] * ctx->parms.gain);
}
}
}
static snd_pcm_sframes_t fellowext_transfer(snd_pcm_extplug_t *ext, const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, snd_pcm_channel_area_t *src_areas, snd_pcm_uframes_t src_offset, snd_pcm_uframes_t size)
{
snd_pcm_fellowext_t *ctx = (snd_pcm_fellowext_t *)ext;
short *src = area_addr(src_areas, src_offset);
short *dst = area_addr(dst_areas, dst_offset);
unsigned int count = size;
int channels = ctx->ext.channels;
int bytes_per_frame = 2 * channels;
while (cout > 0) {
unsigned int chunk;
if (ctx->filled + count > ctx->parms.frames)
chunk = ctx->parms.frames - ctx->filled;
else
chunk = count;
if (ctx->processed)
memcpy(dst, ctx->outbuf + ctx->filled * channels, chunk * bytes_per_frame);
else
memset(dst, 0, chunk * bytes_per_frame);
if (ctx->parms.enable_dump)
fwrite(dst, 1, chunk * bytes_per_frame, ctx->parms.dump_fp);
dst += chunk * channels;
memcpy(ctx->buf + ctx->filled * channels, src, chunk * bytes_per_frame);
ctx->filled += chunk;
if (ctx->filled == ctx->parms.frames) {
process(ctx);
ctx->processed = 1;
}
src += chunk * channels;
count -= chunk;
}
return size;
}
static int fellowext_init(snd_pcm_extplug_t *ext)
{
snd_pcm_fellowext_t *ctx = (snd_pcm_fellowext_t *)ext;
int channels = ctx->ext.channels;
ctx->filled = 0;
ctx->processed = 0;
if (!ctx->buf) {
ctx->buf = malloc(ctx->parms.frames * 2 * channels);
if (!ctx->buf)
return -ENOMEM;
}
memset (ctx->buf, 0, ctx->parms.frames * 2 * channels);
if (!ctx->outbuf) {
ctx->outbuf = malloc(ctx->parms.frames * 2 * channels);
if (!ctx->outbuf)
return -ENOMEM;
}
memset (ctx->outbuf, 0, ctx->parms.frames * 2 * channels);
return 0;
}
static int fellowext_close(snd_pcm_extplug *ext)
{
snd_pcm_fellowext_t *ctx = (snd_pcm_fellowext_t *)ext;
if (ctx->parms.enable_dump)
fclose(ctx->parms.dump_fp);
if (ctx->buf) {
free(ctx->buf);
ctx->buf = NULL;
}
if (ctx->outbuf) {
free(ctx->outbuf);
ctx->outbuf = NULL;
}
return 0;
}
static const snd_pcm_extplug_callback_t fellowext_callback = {
.transfer = fellowext_transfer,
.init = fellowext_init,
.close = fellowext_close,
};
static int get_bool_parm(snd_config_t *n, const char *id, const char *str, int *val_ret)
{
int val;
if (strcmp(id, str))
return 0;
val = snd_config_get_bool(n);
if (val < 0) {
SNDERR("Invalid value for %s", id );
return val;
}
*val_ret = val;
return 1;
}
static int get_int_parm(snd_config_t *n, const char *id, const char *str, int *val_ret)
{
long val;
int err;
if (strcmp(id, str))
return 0;
err = snd_config_get_integer(n, &val);
if (err < 0) {
SNDERR("Invalid value for %s", id );
return err;
}
*val_ret = val;
return 1;
}
static int get_float_parm(snd_config_t *n, const char *id, const char *str, float*val_ret)
{
double val;
int err;
if (strcmp(id, str))
return 0;
err = snd_config_get_ireal(n, &val);
if (err < 0) {
SNDERR("Invalid value for %s", id );
return err;
}
*val_ret = val;
return 1;
}
SND_PCM_PLUGIN_DEINE_FUNC(fellowext)
{
snd_config_iterator_t i, next;
snd_pcm_fellowext_t *ctx;
snd_config_t *sconf = NULL;
int err;
struct ctx_parms parms = {
.frames = 512,
.enable_dump = 0,
.dump_fp = NULL,
.gain = 0.5,
};
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
if (snd_config_get_id(n, &id) < 0)
continue;
if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
continue;
if (strcmp(id, "slave") == 0) {
sconf = n;
continue;
}
err = get_int_parm(n, id, "frames", &parms.frames);
if (err)
goto ok;
err = get_bool_parm(n, id, "enable_dump", &parms.enable_dump);
if (err)
goto ok;
err = get_float_parm(n, id, "gain", &parms.gain);
if (err)
goto ok;
SNDERR("Unknown field %s", id);
return -EINVAL;
ok:
if (err < 0)
return err;
}
if (!sconf) {
SNDERR("No slave configuration for fellowext pcm");
}
if (parms.enable_dump)
parms.dump_fp = fopen("extplug.pcm", "wb");
ctx = calloc(1, sizeof(*ctx))
if (!ctx)
return -ENOMEM;
ctx->ext.version = SND_PCM_EXTPLUG_VERSION;
ctx->ext.name = "Fellow Ext Plugin";
ctx->ext.callback = &fellowext_callback;
ctx->ext.private_data = ctx;
ctx->parms = parms;
err = snd_pcm_extplug_create(&ctx->ext, name, root, conf, stream, mode);
if (err < 0) {
free(ctx);
return err;
}
snd_pcm_explug_set_param(&ctx->ext, SND_PCM_EXTPLUG_HW_CHANNELS, 2);
snd_pcm_explug_set_slave_param(&ctx->ext, SND_PCM_EXTPLUG_HW_CHANNELS, 2);
snd_pcm_explug_set_param(&ctx->ext, SND_PCM_EXTPLUG_HW_FORMAT, SND_PCM_FORMAT_S16);
snd_pcm_explug_set_slave_param(&ctx->ext, SND_PCM_EXTPLUG_HW_FORMAT, SND_PCM_FORMAT_S16);
*pcmp = ctx->ext.pcm;
return 0;
}
SND_PCM_PLUGIN_SYMBOL(fellowext);
External I/O plugin SDK
- I/O 类型插件,用作输入或输出的最终端点,即作为用户空间 PCM 程序。
通过代码设置alsa 音量
amixer -c2 cget name='playback_softvol’
#include <alsa/asoundlib.h>
#include <iostream>
int main() {
snd_ctl_t *ctl;
snd_ctl_elem_id_t *id;
snd_ctl_elem_value_t *control;
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(id, "playback_softvol");
if (snd_ctl_open(&ctl, "hw:2", 0) < 0)
{
std::cerr << "Cannot open control for card 2" << std::endl;
return -1;
}
snd_ctl_elem_value_set_id(control, id);
snd_ctl_elem_value_set_integer(control, 0, 50); // left channel
snd_ctl_elem_value_set_integer(control, 1, 50); // right channel
if (snd_ctl_elem_write(ctl, control) < 0)
{
std::cerr << "Cannot set playback_softvol volume" << std::endl;
return -1;
}
snd_ctl_close(ctl);
return 0;
}
ALSA 异步IO
// AlsaDevice.h#ifndef ALSADEVICE_H
#define ALSADEVICE_H
#include <vector>#include "alsa/pcm.h"#include "AlsaDeviceParam.h"#include <sys/poll.h>#include <string>class AlsaDevice {
private:
snd_pcm_t *capture_handle;
snd_pcm_t *playback_handle;
std::vector<struct pollfd> ufds;
int in_count;
int out_count;
public:
AlsaDeviceParam alsaDeviceParam;
AlsaDevice(const AlsaDeviceParam ¶m);
~AlsaDevice();
bool read(short *pcm, int len);
bool write(short *pcm, int len);
bool captureReady();
bool playbackReady();
void waitForPoll();
void start();
};
#endif
// AlsaDevice.cpp#include "AlsaDevice.h"#include "macrologger.h"#include <alsa/pcm.h>#include <iostream>#include <assert.h>#include <unistd.h>using namespace std;
int xrun_recovery(snd_pcm_t *handle, int err) {
if (err == -EPIPE) {/* under-run */
err = snd_pcm_prepare(handle);
if (err < 0)
LOG_ERROR("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
return 0;
} else if (err == -ESTRPIPE) {
while ((err = snd_pcm_resume(handle)) == -EAGAIN)
usleep(1000);/* wait until the suspend flag is released */if (err < 0) {
err = snd_pcm_prepare(handle);
if (err < 0)
LOG_ERROR("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
}
return 0;
}
return err;
}
AlsaDevice::AlsaDevice(const AlsaDeviceParam ¶m) {
this->alsaDeviceParam = param;
int dir;
int err;
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
snd_pcm_uframes_t buffer_size = 2048;
static snd_output_t *jcd_out;
err = snd_output_stdio_attach(&jcd_out, stderr, 0);
if ((err = snd_pcm_open(&capture_handle, alsaDeviceParam.capture_dev.c_str(), SND_PCM_STREAM_CAPTURE, 0)) < 0) {
LOG_ERROR("cannot open audio device %s (%s)",
alsaDeviceParam.capture_dev.c_str(),
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
LOG_ERROR("cannot allocate hardware parameter structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
LOG_ERROR("cannot initialize hardware parameter structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
LOG_ERROR("cannot set access type (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, alsaDeviceParam.capt_format)) < 0) {
LOG_ERROR("cannot set sample format (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &alsaDeviceParam.capt_rate, 0)) < 0) {
LOG_ERROR("cannot set sample alsaDeviceParam.capt_rate (%s)",
snd_strerror(err));
assert(0);
}
LOG_INFO("capt_rate = %d", alsaDeviceParam.capt_rate);
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, alsaDeviceParam.capt_nb_chan)) < 0) {
LOG_ERROR("cannot set capt_nb_chan count (%s)",
snd_strerror(err));
assert(0);
}
dir = 0;
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &alsaDeviceParam.capt_period_size, &dir)) < 0) {
LOG_ERROR("cannot set period size (%s)",
snd_strerror(err));
assert(0);
}
buffer_size = alsaDeviceParam.capt_period_size * alsaDeviceParam.capt_nb_periods;
dir = 0;
if ((err = snd_pcm_hw_params_set_buffer_size_near(capture_handle, hw_params, &buffer_size)) < 0) {
LOG_ERROR("cannot set buffer time (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
LOG_ERROR("cannot set capture parameters (%s)",
snd_strerror(err));
assert(0);
}
snd_pcm_dump_setup(capture_handle, jcd_out);
snd_pcm_hw_params_free(hw_params);
if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) {
LOG_ERROR("cannot allocate software parameters structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_current(capture_handle, sw_params)) < 0) {
LOG_ERROR("cannot initialize software parameters structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_set_avail_min(capture_handle, sw_params, alsaDeviceParam.capt_period_size)) < 0) {
LOG_ERROR("cannot set minimum available count (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) {
LOG_ERROR("cannot set software parameters (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_prepare(capture_handle)) < 0) {
LOG_ERROR("cannot prepare audio interface for use (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_open(&playback_handle, alsaDeviceParam.playback_dev.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
LOG_ERROR("cannot open audio device %s (%s)",
alsaDeviceParam.playback_dev.c_str(),
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
LOG_ERROR("cannot allocate hardware parameter structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_any(playback_handle, hw_params)) < 0) {
LOG_ERROR("cannot initialize hardware parameter structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_set_access(playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
LOG_ERROR("cannot set access type (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_set_format(playback_handle, hw_params, alsaDeviceParam.play_format)) < 0) {
LOG_ERROR("cannot set sample format (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_set_rate_near(playback_handle, hw_params, &alsaDeviceParam.play_rate, 0)) < 0) {
LOG_ERROR("cannot set sample alsaDeviceParam.play_rate (%s)",
snd_strerror(err));
assert(0);
}
LOG_INFO("play_rate = %d", alsaDeviceParam.play_rate);
if ((err = snd_pcm_hw_params_set_channels(playback_handle, hw_params, alsaDeviceParam.play_nb_chan)) < 0) {
LOG_ERROR("cannot set alsaDeviceParam.play_channel count (%s)",
snd_strerror(err));
assert(0);
}
dir = 0;
if ((err = snd_pcm_hw_params_set_period_size_near(playback_handle, hw_params, &alsaDeviceParam.play_period_size, &dir)) < 0) {
LOG_ERROR("cannot set alsaDeviceParam.play_period size (%s)",
snd_strerror(err));
assert(0);
}
buffer_size = alsaDeviceParam.play_period_size * alsaDeviceParam.play_nb_periods;
dir = 0;
if ((err = snd_pcm_hw_params_set_buffer_size_near(playback_handle, hw_params, &buffer_size)) < 0) {
LOG_ERROR("cannot set buffer time (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params(playback_handle, hw_params)) < 0) {
LOG_ERROR("cannot set playback parameters (%s)",
snd_strerror(err));
assert(0);
}
snd_pcm_dump_setup(playback_handle, jcd_out);
snd_pcm_hw_params_free(hw_params);
if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) {
LOG_ERROR("cannot allocate software parameters structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_current(playback_handle, sw_params)) < 0) {
LOG_ERROR("cannot initialize software parameters structure (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_set_avail_min(playback_handle, sw_params, alsaDeviceParam.play_period_size)) < 0) {
LOG_ERROR("cannot set minimum available count (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params, alsaDeviceParam.play_period_size)) < 0) {
LOG_ERROR("cannot set start mode (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params(playback_handle, sw_params)) < 0) {
LOG_ERROR("cannot set software parameters (%s)",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_prepare(playback_handle)) < 0) {
LOG_ERROR("cannot prepare audio interface for use (%s)",
snd_strerror(err));
assert(0);
}
in_count = snd_pcm_poll_descriptors_count(capture_handle);
out_count = snd_pcm_poll_descriptors_count(playback_handle);
int tot_count = in_count + out_count;
ufds.resize(tot_count);
if (snd_pcm_poll_descriptors(capture_handle, &ufds[0], in_count) != in_count) {
LOG_ERROR("snd_pcm_poll_descriptors() doesn't match for capture");
}
if (snd_pcm_poll_descriptors(playback_handle, &ufds[in_count], out_count) != out_count) {
LOG_ERROR("snd_pcm_poll_descriptors() doesn't match for playback");
}
LOG_INFO("devices are %d and %d, total is :%d", ufds[0].fd, ufds[1].fd, tot_count);
}
AlsaDevice::~AlsaDevice() {
}
bool AlsaDevice::read(short *pcm, int len) {
int err;
if ((err = snd_pcm_readi(capture_handle, pcm, len)) != len) {
if (err < 0) {
// LOG_ERROR("error %d, EPIPE = %d", err, EPIPE);if (err == -EPIPE) {
LOG_ERROR("An overrun has occured, reseting capture");
} else {
LOG_ERROR("read from audio interface failed (%s)",
snd_strerror(err));
}
if ((err = snd_pcm_prepare(capture_handle)) < 0) {
LOG_ERROR("cannot prepare audio interface for use (%s)",
snd_strerror(err));
}
if ((err = snd_pcm_start(capture_handle)) < 0) {
LOG_ERROR("cannot prepare audio interface for use (%s)",
snd_strerror(err));
}
} else {
LOG_ERROR("Couldn't read as many samples as I wanted (%d instead of %d)", err, len);
}
return true;
}
// LOG_INFO("read: %d", err);return false;
}
bool AlsaDevice::write(short *pcm, int len) {
int err;
if ((err = snd_pcm_writei(playback_handle, pcm, len)) != len) {
if (err < 0) {
if (err == -EPIPE) {
LOG_ERROR("An underrun has occured, reseting playback, len=%d", len);
if (xrun_recovery(playback_handle, err) < 0) {
LOG_ERROR("Write error: %s\n", snd_strerror(err));
}
snd_pcm_writei(playback_handle, pcm, len);
} else {
LOG_ERROR("write to audio interface failed (%s)",
snd_strerror(err));
}
} else {
LOG_ERROR("Couldn't write as many samples as I wanted (%d instead of %d)", err, len);
}
return true;
}
// LOG_INFO("wrote %d", err);return false;
}
bool AlsaDevice::captureReady() {
unsigned short revents = 0;
int err;
if ((err = snd_pcm_poll_descriptors_revents(capture_handle, &ufds[0], in_count, &revents)) < 0) {
LOG_ERROR("error in snd_pcm_poll_descriptors_revents for capture: %s", snd_strerror(err));
if (revents & POLLERR) {
return false;
}
}
return revents & POLLIN;
}
bool AlsaDevice::playbackReady() {
unsigned short revents = 0;
int err;
if ((err = snd_pcm_poll_descriptors_revents(playback_handle, &ufds[in_count], out_count, &revents)) < 0) {
LOG_ERROR("error in snd_pcm_poll_descriptors_revents for playback: %s", snd_strerror(err));
if (revents & POLLERR) {
return false;
}
}
return revents & POLLOUT;
}
void AlsaDevice::waitForPoll() {
poll(&ufds[0], ufds.size(), -1);
}
void AlsaDevice::start() {
int16_t lead_in = alsaDeviceParam.play_period_size;
int16_t channels = alsaDeviceParam.play_nb_chan;
snd_pcm_start(playback_handle);
snd_pcm_start(capture_handle);
}