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控件,同时也可以通过alsamixer amixer调节音量
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.so copy到/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

https://www.cnblogs.com/fellow1988/p/12377713.html

  1. 滤波器类型插件,用于转换输入的PCM信号并输出到Slave。因此,该插件必须有Slave PCM 作为其输出。
  2. 该插件可以修改输入/输出PCM的formatchannel。但是 不能 修改rate,因为只是过滤型plugin
  3. 在调用snd_pcm_extplug_create()之前,必须在extplug记录中填写以下字段 :versionnamecallback。其他字段(private_dataparms)是可选的,应初始化为零.
  4. 必须将常量SND_PCM_EXTPLUG_VERSION传递给 alsa-lib 中的版本检查的版本字段version
  5. 必须将非 NULL ASCII 字符串传递给名称字段name
  6. callback字段包含此插件的回调函数表(定义为snd_pcm_extplug_callback_t)。
  7. 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

https://www.cnblogs.com/fellow1988/p/12375206.html

  1. 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 &param);
    ~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 &param) {
    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);
}

linux & alsa info

https://magodo.github.io/alsa-pcm/