ALSA子系统 | 如何添加虚拟声卡

2023-01-05 19:45:41 浏览数 (1)

我们知道,asoc框架里面主要包含machine codec platform 这三大部分:

  1. machine:单板相关内容,表明声卡中所用的主芯片(Platform是指Soc)、编解码芯片(codec)是哪一个。主芯片里的接口(DAI(全称Digital Audio Interface)接口)接到哪里去。CPU DAI是哪一个,codec DAI是哪一个,DMA是哪个。
  2. platform:用于实现平台相关内容,如IIS(DAI)(设置接口)和DMA(传输数据)。
  3. codec:用于实现平台无关的功能,即编解码芯片驱动, DAI和控制接口(控制音量)。

但是有些场合,我们是不需要一个“真实”的 codec 做处理的,例如蓝牙通话,这时候只要一个虚拟声卡即可。这里提供一个虚拟声卡的驱动:

代码语言:javascript复制
/*
 * Driver for generic Bluetooth SCO link
 * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de>
 *
 *  This program is free software; you can redistribute  it and/or modify it
 *  under  the terms of  the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the  License, or (at your
 *  option) any later version.
 *
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/soc.h>

static const struct snd_soc_dapm_widget bt_sco_widgets[] = {
 SND_SOC_DAPM_INPUT("RX"),
 SND_SOC_DAPM_OUTPUT("TX"),
};

static const struct snd_soc_dapm_route bt_sco_routes[] = {
 { "Capture", NULL, "RX" },
 { "TX", NULL, "Playback" },
};

static int btsco_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir){
    pr_debug("%s: clk_id=%d, freq=%d.n", __func__, clk_id, freq);
    return 0;
}

static int btsco_set_pll(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out){
    pr_debug("%s: pll_id=%d, freq_in=%d, freq_out=%d.n", __func__, pll_id, freq_in, freq_out);
    return 0;
}

static int btsco_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div){
    pr_debug("%s: div_id=%d, div=%d.n", __func__, div_id, div);
    return 0;
}

static int btsco_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai){
    pr_debug("%s .n", __func__);
    return 0;
}

static int btsco_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai){
    pr_debug("%s .n", __func__);
    return 0;
}

static int btsco_set_fmt(struct snd_soc_dai *dai, unsigned int fmt){
    pr_debug("%s: fmt=%d.n", __func__, fmt);
    return 0;
}

static const struct snd_soc_dai_ops btsco_dai_ops = {
    .set_sysclk = btsco_set_sysclk,
    .set_pll = btsco_set_pll,
    .set_clkdiv = btsco_set_clkdiv,
    .hw_params = btsco_hw_params,
    .hw_free = btsco_hw_free,
    .set_fmt = btsco_set_fmt,
};

static struct snd_soc_dai_driver bt_sco_dai[] = {
 {
  .name = "bt-sco-pcm",
  .capture = {
    .stream_name = "Capture",
   .channels_min = 1,
   .channels_max = 2,
   .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT,
   .formats = SNDRV_PCM_FMTBIT_S16_LE|SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
  },
        .ops = &btsco_dai_ops,
 },
};

static struct snd_soc_codec_driver soc_codec_dev_bt_sco = {
 .component_driver = {
  .dapm_widgets  = bt_sco_widgets,
  .num_dapm_widgets = ARRAY_SIZE(bt_sco_widgets),
  .dapm_routes  = bt_sco_routes,
  .num_dapm_routes = ARRAY_SIZE(bt_sco_routes),
 },
};

static int bt_sco_probe(struct platform_device *pdev)
{
    pr_debug("bt_sco_probe.n");
 return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_bt_sco,
          &bt_sco_dai[0], 1);
}

static int bt_sco_remove(struct platform_device *pdev)
{
 snd_soc_unregister_codec(&pdev->dev);
 return 0;
}

static const struct platform_device_id bt_sco_driver_ids[] = {
 {
  .name  = "dfbmcs320",
 },
 {
  .name  = "bt-sco",
 },
 {},
};
MODULE_DEVICE_TABLE(platform, bt_sco_driver_ids);

#if defined(CONFIG_OF)
static const struct of_device_id bt_sco_codec_of_match[] = {
 { .compatible = "delta,dfbmcs320", },
 { .compatible = "linux,bt-sco", },
 {},
};
MODULE_DEVICE_TABLE(of, bt_sco_codec_of_match);
#endif

static struct platform_driver bt_sco_driver = {
 .driver = {
  .name = "bt-sco",
  .of_match_table = of_match_ptr(bt_sco_codec_of_match),
 },
 .probe = bt_sco_probe,
 .remove = bt_sco_remove,
 .id_table = bt_sco_driver_ids,
};

module_platform_driver(bt_sco_driver);

MODULE_DESCRIPTION("ASoC generic bluetooth sco link driver");
MODULE_LICENSE("GPL");

这个虚拟到声卡驱动是通用的,就一个虚拟codec,里面啥都没做,就规定了一些参数:最大两通道,采样率在8k~48k,支持16、20、24、32bit位宽。

既然codec是通用的,那么machine是否也有通用的例子的?

还真有!!!就是simple card framework

Simple card顾名思义,简单通用的machine driver,代码实现路径如下:sound/soc/generic/simple-card.c。需要在内核开启如下配置才能使用:

Device Drivers ---> <> Sound card support ---> <> Advanced Linux Sound Architecture ---> <> ALSA for SoC audio support ---> <> ASoC Simple sound card support

同时,我们还需要在设备树中进行相应到配置:

代码语言:javascript复制
sound {
        compatible = "simple-audio-card";
        simple-audio-card,name = "Dummy-Audio-Card";
        simple-audio-card,format = "dsp_a";
        simple-audio-card,bitclock-master = <&dailink0_master>;
        simple-audio-card,frame-master = <&dailink0_master>;
        simple-audio-card,mclk-fs = <512>; //codec做主,主控供给编解码芯片用的时钟 512FS

        simple-audio-card,cpu {
            sound-dai = <&i2s>;
        };

        dailink0_master: simple-audio-card,codec {
            sound-dai = <&bt_sco>;
        };
    }; 

&i2s {
    #sound-dai-cells = <0>;
    status = "okay";
};

&bt_sco {
    #sound-dai-cells = <0>;
 compatible = "linux,bt-sco";
    status = "okay";
};

这里设备数配置到时:选择蓝牙做主设备,pcm格式。

关于simple-card里的描述,差不多可以归纳为:

  • simple-audio-card,name:用户指定的音频声卡名称。
  • simple-audio-card,widgets:指定音频编解码器DAPM小部件(widgets),每个条目都是一对字符串:“template-wname”,“user-supplied-wname”。“ template-wname”是模板小部件名称,可选为:“Microphone”, “Line”, “Headphone”, “Speaker”。“user-supplied-wname”是用户指定的窗口小部件名称。
  • simple-audio-card,routing:音频组件之间的连接列表。 每个条目都是一对字符串,第一个是目的(sink),第二个是源(source)。
  • simple-audio-card,pin-switches:包含以下组件的窗口小部件名称的字符串列表必须创建哪个引脚开关。
  • simple-audio-card,dai-link:用于dai-link里指定CPU和CODEC子节点。
  • simple-audio-card,format:CPU / CODEC通用音频格式。可选格式为:“ i2s”,“ right_j”,“ left_j”,“ dsp_a”“ dsp_b”,“ ac97”,“ pdm”,“ msb”,“ lsb”。
  • frame-master:指向CPU或者CODEC,表示dai-link fram 主机。
  • bitclock-master:指向CPU或者CODEC,表示dai-link的位时钟主控。
  • bitclock-inversion:布尔属性,如果添加则表明dai-link使用位时钟反转。
  • frame-inversion:布尔属性。 如果添加则表明dai-link使用帧时钟反转。

举几个具体到例子:

代码语言:javascript复制
simple-audio-card,pin-switches = "Headphone","HpSpeaker","LINEOUT";
等价于驱动里
static const struct snd_kcontrol_new sunxi_card_controls[] = {
 SOC_DAPM_PIN_SWITCH("Headphone"),
 SOC_DAPM_PIN_SWITCH("HpSpeaker"),
 SOC_DAPM_PIN_SWITCH("LINEOUT"),
};
snd_soc_add_card_controls(card, sunxi_card_controls,
    ARRAY_SIZE(sunxi_card_controls));
代码语言:javascript复制
simple-audio-card,widgets = "Microphone", "HeadphoneMic",
      "Microphone", "Main Mic";
等价于驱动里
static const struct snd_soc_dapm_widget sunxi_card_dapm_widgets[] = {
 SND_SOC_DAPM_MIC("HeadphoneMic", NULL),
 SND_SOC_DAPM_MIC("Main Mic", NULL),
};
snd_soc_dapm_new_controls(dapm, sunxi_card_dapm_widgets,
    ARRAY_SIZE(sunxi_card_dapm_widgets));
代码语言:javascript复制
simple-audio-card,routing = "MainMic Bias", "Main Mic",
     "MIC1", "MainMic Bias",
     "MIC2", "HeadphoneMic";
等价于驱动里
static const struct snd_soc_dapm_route sunxi_card_routes[] = {
 {"MainMic Bias", NULL, "Main Mic"},
 {"MIC1", NULL, "MainMic Bias"},
 {"MIC2", NULL, "HeadphoneMic"},
};
snd_soc_dapm_add_routes(dapm, sunxi_card_routes,
    ARRAY_SIZE(sunxi_card_routes));

最后,更多实现详情可以参考内核文档:Documentation/devicetree/bindings/sound/simple-card.txt

0 人点赞