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使用