深入探究 TinyALSA Plugin的应用

external/tinyalsa/pcm.c
  struct pcm *pcm_open(unsigned int card, unsigned int device,
                       unsigned int flags, struct pcm_config *config)
  {
				... ...
      pcm->snd_node = snd_utils_get_dev_node(card, device, NODE_PCM);
      pcm_type = snd_utils_get_node_type(pcm->snd_node);
      if (pcm_type == SND_NODE_TYPE_PLUGIN)
          pcm->ops = &plug_ops;
      else
          pcm->ops = &hw_ops;
  
      pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
				... ...
}

这段代码是一个函数 pcm_open 的实现,该函数用于打开音频 PCM设备。在代码中,存在两种类型的 PCM 设备:插件型(Plugin)和硬件型(Hardware)。

通过调用 snd_utils_get_dev_node 函数获取 PCM 设备的节点信息。
接着,通过 snd_utils_get_node_type 函数确定 PCM 设备的类型,如果是插件型设备,则使用插件操作(plug_ops),否则使用硬件操作(hw_ops)。

external/tinyalsa/snd_utils.c
  static int snd_utils_resolve_symbols(struct snd_node *node)
  {
      void *dl = node->dl_hdl;
      int err;
  
      SND_DLSYM(dl, node->get_card, "snd_card_def_get_card", err);
      if (err)
          goto done;
      SND_DLSYM(dl, node->put_card, "snd_card_def_put_card", err);
      if (err)
          goto done;
      SND_DLSYM(dl, node->get_node, "snd_card_def_get_node", err);
      if (err)
          goto done;
      SND_DLSYM(dl, node->get_int, "snd_card_def_get_int", err);
      if (err)
          goto done;
      SND_DLSYM(dl, node->get_str, "snd_card_def_get_str", err);
  
  done:
      return err;
  }
  
  struct snd_node *snd_utils_get_dev_node(unsigned int card,
          unsigned int device, int dev_type)
  {
      struct snd_node *node;
      int rc = 0;
  
      node = calloc(1, sizeof(*node));
      if (!node)
          return NULL;
  
      node->dl_hdl = dlopen("libsndcardparser.so", RTLD_NOW);
      if (!node->dl_hdl) {
          goto err_dl_open;
      }
  
      rc = snd_utils_resolve_symbols(node);
      if (rc < 0)
          goto err_resolve_symbols;
  
      node->card_node = node->get_card(card);
      if (!node->card_node)
          goto err_resolve_symbols;
  
      node->dev_node = node->get_node(node->card_node,
                                      device, dev_type);
				...  ...

snd_utils_get_dev_node 函数通过 dlopen 打开 “libsndcardparser.so” 动态链接库,并调用 snd_utils_resolve_symbols 函数从该库中解析各种符号(如获取卡片、释放卡片、获取节点等),这几个函数指针必须要实现。

这段代码通过动态加载自定义的动态链接库 “libsndcardparser.so”,该库自身实现了一系列与音频设备节点相关的函数。

void *snd_card_def_get_card(unsigned int card)
{
    file = fopen(CARD_DEF_FILE, "r");

    parser = XML_ParserCreate(NULL);

    card_data.card = card;
    card_data.card_name = snd_card_name;
    XML_SetUserData(parser, &card_data);
    XML_SetElementHandler(parser, snd_start_tag, snd_end_tag);
    XML_SetCharacterDataHandler(parser, snd_data_handler);

    /* parse XML */
    for (;;) {
        buf = XML_GetBuffer(parser, BUF_SIZE);
        if (buf == NULL)
            goto ret;
        bytes_read = fread(buf, 1, BUF_SIZE, file);

        if (XML_ParseBuffer(parser, bytes_read,
                            bytes_read == 0) == XML_STATUS_ERROR) {
            goto ret;
        }
    }
}

static void snd_start_tag(void *userdata, const XML_Char *tag_name,
                      const XML_Char **attr)
{
    struct xml_userdata *data = (struct xml_userdata *)userdata;
    enum snd_node_type node_type = -1;

    if (data->card_parsed)
        return;

    snd_reset_data_buf(data);

    if (!strcmp(tag_name, "card"))
        data->current_tag = TAG_CARD;

    if (!strcmp(tag_name, "pcm-device")) {
        data->current_tag = TAG_DEVICE;
        node_type = SND_NODE_TYPE_PCM;
    } else if (!strcmp(tag_name, "compress-device")) {
        data->current_tag = TAG_DEVICE;
        node_type = SND_NODE_TYPE_COMPR;
    } else if (!strcmp(tag_name, "mixer")) {
        data->current_tag = TAG_DEVICE;
        node_type = SND_NODE_TYPE_MIXER;
    } else if (strstr(tag_name, "plugin")) {
        snd_dev_set_type(data->cur_dev_def, SND_NODE_DEV_TYPE_PLUGIN);
        data->current_tag = TAG_PLUGIN;
    } else if (!strcmp(tag_name, "props")) {
        data->current_tag = TAG_DEV_PROPS;
    }

    if (data->current_tag != TAG_CARD && !data->card_found)
        return;

    snd_dev_def_init(data, tag_name, node_type);
}

snd_card_def_get_card 函数用于解析 card-defs.xml 文件,以获取声卡和插件信息。

自此,通过 snd_utils_get_dev_node 函数获取了 libsndcardparser.so 中的函数指针,然后根据解析得到的类型(type),获取了 plug_ops。值得注意的是,plug_ops 的定义位于 external/tinyalsa/pcm_plugin.c 中。

struct pcm_ops plug_ops = {
      .open = pcm_plug_open,
      .close = pcm_plug_close,
      .ioctl = pcm_plug_ioctl,
			...  ...
};

pcm_plug_open 函数中调用了 snd_utils_get_str 函数,通过前面获取到的 get_str 函数指针,调用到 libsndcardparser.so 中,其实现为通过解析的 card-defs.xml 文件中获取实现了 PCM_PLUGIN_OPEN_FN 函数的动态链接库名字。这个动态链接库中的函数实现参考了 pcm_plugin.h。这一设计允许动态加载并调用动态链接库中定义的函数,从而实现了对 PCM 插件的打开功能。

external/tinyalsa/include/tinyalsa/pcm_plugin.h
struct pcm_plugin_ops xxx_pcm_ops = {
    .close = xxx_pcm_close,
    .hw_params = xxx_pcm_hw_params,
    .sw_params = xxx_pcm_sw_params,
    .sync_ptr = xxx_pcm_sync_ptr,
    .writei_frames = xxx_pcm_writei_frames,
    .readi_frames = xxx_pcm_readi_frames,
			...  ...
    .poll = xxx_pcm_poll,
    .ioctl = xxx_pcm_ioctl,
};

PCM_PLUGIN_OPEN_FN(xxx_pcm_plugin)
{
    struct pcm_plugin *xxx_pcm_plugin;
};

/vendor/etc/card-defs.xml内容如下:

<defs>
<card>
    <id>100</id>
    <name>card name</name>

    <pcm-device>
        <id>100</id>
        <name>pcm name</name>
        <pcm_plugin>
            <so-name>libxxx_pcm_plugin.so</so-name>
        </pcm_plugin>
        <props>
            <playback>1</playback>
            <capture>0</capture>
        </props>
    </pcm-device>
    	...  ...

总结下来就是,需要首先实现一个名为 libsndcardparser.so 的动态链接库,它的作用是在 pcm_open 函数调用时获取声卡和 PCM 节点信息。在这个动态链接库中,get_card 函数指针的实现会解析 card-defs.xml 文件,以获取具体的声卡信息,其中包括了 PCM 节点文件操作函数指针实现的动态链接库名称。

/vendor/etc/audio/sku_<product name>/resourcemanager_xxx.xml
/vendor/qcom/proprietary\mm-audio-headers\acdbdata\kvh2xml.h
/vendor/qcom/opensource/pal/configs/pineapple/usecaseKvManager.xml
/vendor/qcom/opensource/audio-hal/primary-hal/configs/pineapple/microphone_characteristics.xml
/vendor/qcom/proprietary\args\gsl\api\gsl_subgraph_platform_driver_props.xml
/vendor/qcom/proprietary\mm-audio\audio-log-utils\audio_dynamic_log.xml

ftm mixer set
/vendor/qcom/proprietary/mm-audio/audio_ftm/ar/src/audio_ftm_hw_drv_ar.c
vendor/qcom/proprietary\mm-audio\audio_ftm\ar\config\kalama\ftm_test_config

AGM tools:
push to /vendor/etc/backend_conf.xml
agmplay /data/test.wav -D <card> -d <dev> -i <backend_name>

https://docs.qualcomm.com/bundle/resource/video/VD80-VN500-12C +1:39:00
https://docs.qualcomm.com/bundle/resource/video/VD80-63665-1C +44:00 GKV中实线为数据线,虚线为控制线
https://docs.qualcomm.com/bundle/resource/video/VD80-VN500-14C
https://docs.qualcomm.com/bundle/resource/video/VD80-VN500-13C +1:03:00 QXDM使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值