OpenHarmony5.0.3之alsa_adapter.json文件的解析
摘要:本文分析了OpenHarmony 5.0.3 Release系统中USB音频适配器识别失败的问题。在展锐7885硬件平台上调试Type-C耳机时,系统因找不到USB adapter导致播放失败,关键报错为"unknow card type error"。文章详细解析了alsa_soundcard.c中CfgGetAdapterCardType()和SndMatchSelA
1)文章由移远通信技术股份有限公司提供
2)以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改/删除,18777493676(微信同号)。
一、关键字
OpenHarmony 5.0.3 Release;usb ; adapter;解析;
二、问题描述
2.1 运行环境
- 系统版本:
OpenHarmony-5.0.3-Release(以该版本为基线) - 硬件:展锐
7885
2.2 问题现象
2.2.1 日志
我在调试type-c耳机时,在hal层它没找到usb的adapter然后报错,导致播放失败了
关键日志如下:
[SndMatchSelAdapter][line:520]: unknow card type error.
2.2.2 打印报错的关键代码
//alsa_soundcard.c
116 static enum SndCardType CfgGetAdapterCardType(const char* adapterName)
117 {
118 if (adapterName == NULL) {
119 return SND_CARD_UNKNOWN;
120 }
121
122 struct AlsaAdapterCfgInfo *info;
123 for (enum SndCardType type = SND_CARD_PRIMARY; type < SND_CARD_MAX; ++type) {
124 for (int32_t i = 0; i < g_alsaAdapterList[type].num; ++i) {
125 info = &g_alsaAdapterList[type].list[i];
126 if (strncmp(adapterName, info->adapterName, strlen(info->adapterName)) == 0) {
127 return type;
128 }
129 }
130 }
131 return SND_CARD_UNKNOWN;
132 }
508 int32_t SndMatchSelAdapter(struct AlsaSoundCard *cardIns, const char *adapterName)
509 {
510 int32_t ret;
511 enum SndCardType cardType;
512 struct AlsaAdapterCfgInfo *info = NULL;
513 struct AlsaDevInfo *devInfo = NULL;
514 CHECK_NULL_PTR_RETURN_DEFAULT(cardIns);
515 CHECK_NULL_PTR_RETURN_DEFAULT(adapterName);
516
517 AUDIO_FUNC_LOGI("adapterName: %{public}s", adapterName);
518 cardType = CfgGetAdapterCardType(adapterName);
519 if (cardType == SND_CARD_UNKNOWN) {
520 AUDIO_FUNC_LOGE("unknow card type error.");
521 return HDF_FAILURE;
522 }
2.3 预期效果
解决报错:[SndMatchSelAdapter][line:520]: unknow card type error
三、逻辑分析
3.1 相关宏定义
//hdf_base.h
#define HDF_CONFIG_DIR "/vendor/etc/hdfconfig"
//alsa_soundcard.c
#define ALSA_CARD_CONFIG_FILE HDF_CONFIG_DIR "/alsa_adapter.json"
/* Define structure description alsa_adapter.json information */
struct AlsaAdapterCfgInfo {
char adapterName[MAX_CARD_NAME_LEN];
int32_t cardId;
char cardName[MAX_CARD_NAME_LEN];
};
struct AlsaAdapterList {
int32_t num;
struct AlsaAdapterCfgInfo list[AUDIO_MAX_CARD_NUM];
};
static struct AlsaAdapterList g_alsaAdapterList[SND_CARD_MAX];
3.2 代码逻辑细节
3.2.1 CfgSaveAdapterFromFile函数
读取vendor/etc/hdfconfig/alsa_adapter.json文件信息到全局数组g_alsaAdapterList中
//alsa_soundcard.c
// ===================== CfgSaveAdapterFromFile =====================
// 功能:顶层入口:读文件→解析 JSON→写入全局表
// 返回值:HDF_SUCCESS / HDF_FAILURE
int32_t CfgSaveAdapterFromFile(void)
{
int32_t ret;
cJSON *adapterObj = NULL;
char *configBuf = NULL;
// 1. 把/vendor/etc/hdfconfig/alsa_adapter.json文件的内容读取到configBuf字符指针指向的内存
configBuf = CfgReadAdapterFile(ALSA_CARD_CONFIG_FILE);
if (configBuf == NULL) {
AUDIO_FUNC_LOGE("CfgReadAdapterFile failed!");
return HDF_FAILURE;
}
/* 2. 把configBuf字符指针指向的内存里的 JSON 文本 变成 一颗树,然后就可以用 cJSON 的 API 获取信息*/
adapterObj = cJSON_Parse(configBuf);
if (adapterObj == NULL) {
AUDIO_FUNC_LOGE("Parse json file failed!");
AudioMemFree((void **)&configBuf);
return HDF_FAILURE;
}
AudioMemFree((void **)&configBuf); // 解析完即可释放
/* 3. 遍历 JSON 中的 adapters 数组,逐条解析后写入全局表g_alsaAdapterList中 */
ret = CfgParseAdapterItems(adapterObj);
if (ret != HDF_SUCCESS) {
cJSON_Delete(adapterObj);
AUDIO_FUNC_LOGE("Parse adapter items failed!");
return HDF_FAILURE;
}
cJSON_Delete(adapterObj); // 释放 cJSON 树
return HDF_SUCCESS;
}
3.2.2 CfgReadAdapterFile函数
把/vendor/etc/hdfconfig/alsa_adapter.json文件的内容读取到configBuf字符指针指向的内存
//alsa_soundcard.c
// ===================== CfgReadAdapterFile =====================
// 功能:把 /vendor/etc/hdfconfig/alsa_adapter.json 完整读入堆内存,返回 JSON 字符串指针
// 入参:fpath――文件路径(硬编码为 ALSA_CARD_CONFIG_FILE)
// 返回值:成功返回堆指针,失败返回 NULL(内部已打印 log)
static char *CfgReadAdapterFile(const char *fpath)
{
int32_t jsonStrSize;
FILE *fp = NULL;
char *pJsonStr = NULL;
char pathBuf[PATH_MAX] = {0};
if (fpath == NULL) {
AUDIO_FUNC_LOGE("Parameter is null!!!");
return NULL;
}
/* 1. 把可能包含相对路径、软链的 fpath 解析成绝对路径 */
if (realpath(fpath, pathBuf) == NULL) {
AUDIO_FUNC_LOGE("File path invalid!");
return NULL;
}
/* 2. 以只读方式打开 */
fp = fopen(pathBuf, "r");
if (fp == NULL) {
AUDIO_FUNC_LOGE("Can not open config file [ %{public}s ].", fpath);
return NULL;
}
/* 3. 跳到文件尾,用 ftell 取得文件长度 */
if (fseek(fp, 0, SEEK_END) != 0) {
AUDIO_FUNC_LOGE("fseek configuration file error!");
(void)fclose(fp);
return NULL;
}
jsonStrSize = ftell(fp);
if (jsonStrSize <= 0) {
AUDIO_FUNC_LOGE("The configuration file size <= 0!");
(void)fclose(fp);
return NULL;
}
rewind(fp); // 回到文件头
/* 4. 长度上限保护,防止超大文件耗尽内存 */
if (jsonStrSize > ALSA_CONFIG_FILE_MAX) {
AUDIO_FUNC_LOGE("The configuration file is too large to load!");
(void)fclose(fp);
return NULL;
}
/* 5. 申请堆内存并多读 1 字节,保证后面能手动补 '\0' */
pJsonStr = (char *)OsalMemCalloc((uint32_t)jsonStrSize + 1);
if (pJsonStr == NULL) {
AUDIO_FUNC_LOGE("OsalMemCalloc pJsonStr failed!");
(void)fclose(fp);
return NULL;
}
/* 6. 一次性读完整文件 */
if (fread(pJsonStr, jsonStrSize, 1, fp) != 1) {
AUDIO_FUNC_LOGE("Read to config file failed!!!");
(void)fclose(fp);
AudioMemFree((void **)&pJsonStr);
return NULL;
}
(void)fclose(fp);
return pJsonStr; // 调用者负责释放
}
3.2.3 cJSON_Parse函数
把configBuf字符指针指向的内存里的 JSON 文本 变成 一颗树,然后就可以用 cJSON 的 API 获取信息
这是第三方cJSON库中的标准接口函数,这里就不多介绍了
- 代码位置
quectel@252576a88a7c:/home/openharmony$ ls -l third_party/cJSON/
total 284
-rw-rw-r-- 1 quectel quectel 2284 Nov 27 22:47 appveyor.yml
-rw-rw-r-- 1 quectel quectel 1909 Nov 27 22:47 BUILD.gn
-rw-rw-r-- 1 quectel quectel 960 Nov 27 22:47 bundle.json
-rw-rw-r-- 1 quectel quectel 25080 Nov 27 22:47 CHANGELOG.md
-rw-rw-r-- 1 quectel quectel 87305 Nov 27 22:47 cJSON.c
-rw-rw-r-- 1 quectel quectel 17141 Nov 27 22:47 cJSON.h
-rw-rw-r-- 1 quectel quectel 41230 Nov 27 22:47 cJSON_Utils.c
-rw-rw-r-- 1 quectel quectel 3938 Nov 27 22:47 cJSON_Utils.h
-rw-rw-r-- 1 quectel quectel 10610 Nov 27 22:47 CMakeLists.txt
-rw-rw-r-- 1 quectel quectel 3903 Nov 27 22:47 CONTRIBUTORS.md
drwxrwxr-x 3 quectel quectel 4096 Nov 27 22:47 fuzzing
drwxrwxr-x 2 quectel quectel 4096 Nov 27 22:47 library_config
-rw-rw-r-- 1 quectel quectel 1084 Nov 27 22:47 LICENSE
-rw-rw-r-- 1 quectel quectel 4650 Nov 27 22:47 Makefile
lrwxrwxrwx 1 quectel quectel 7 Nov 27 22:47 NOTICE -> LICENSE
-rw-rw-r-- 1 quectel quectel 4024 Nov 27 22:47 OAT.xml
-rw-rw-r-- 1 quectel quectel 27747 Nov 27 22:47 README.md
-rw-rw-r-- 1 quectel quectel 296 Nov 27 22:47 README.OpenSource
-rw-rw-r-- 1 quectel quectel 7711 Nov 27 22:47 test.c
drwxrwxr-x 5 quectel quectel 4096 Nov 27 22:47 tests
-rw-rw-r-- 1 quectel quectel 61 Nov 27 22:47 valgrind.supp
quectel@252576a88a7c:/home/openharmony$
- 官方介绍
Given some JSON in a zero terminated string, you can parse it with cJSON_Parse.
cJSON *json = cJSON_Parse(string);
3.2.4 CfgParseAdapterItems函数
遍历 JSON 中的 adapters 数组,逐条解析后写入全局表g_alsaAdapterList中
//alsa_soundcard.c
// ===================== CfgParseAdapterItems =====================
// 功能:遍历 JSON 中的 adapters 数组,逐条解析后写入全局表
// 入参:adapterObj――根 JSON 对象
// 返回值:HDF_SUCCESS / HDF_FAILURE
static int32_t CfgParseAdapterItems(cJSON *adapterObj)
{
int32_t ret, adapterNum;
cJSON *adapterItems = NULL;
/* 1. 取 adapters 数组 */
adapterItems = cJSON_GetObjectItem(adapterObj, "adapters");
if (adapterItems == NULL) {
AUDIO_FUNC_LOGE("Get adapterItems from json failed!\n");
return HDF_FAILURE;
}
/* 2. 数组长度检查 */
adapterNum = cJSON_GetArraySize(adapterItems);
if (adapterNum <= 0) {
AUDIO_FUNC_LOGE("Get adapter number failed!");
return HDF_FAILURE;
} else if (adapterNum > MAX_CARD_NUM) {
AUDIO_FUNC_LOGE("Read adapters number is %{public}d over max num %{public}d!", adapterNum, MAX_CARD_NUM);
return HDF_FAILURE;
}
/* 3. 逐条解析 */
for (int32_t i = 0; i < adapterNum; ++i) {
cJSON *adapter;
struct AlsaAdapterCfgInfo info;
adapter = cJSON_GetArrayItem(adapterItems, i);
if (adapter == NULL) {
AUDIO_FUNC_LOGE("Get adapter item from array failed!");
}
ret = CfgSaveAdapterStruct(adapter, &info);
if (ret != HDF_SUCCESS) {
AUDIO_FUNC_LOGE("CfgSaveAdapterStruct failed!");
return HDF_FAILURE;
}
ret = CfgDumpAdapterInfo(&info);
if (ret != HDF_SUCCESS) {
AUDIO_FUNC_LOGE("CfgDumpAdapterInfo failed!");
return HDF_FAILURE;
}
}
return HDF_SUCCESS;
}
3.2.5 cJSON_GetObjectItem函数
将adapters数组的节点取出来,这里我们只定义了两个
这也是第三方cJSON库中的标准接口函数,这里就不多介绍了
3.2.6 cJSON_GetArrayItem函数
通过adapterItems逐个获取adapters数组
这也是第三方cJSON库中的标准接口函数,这里就不多介绍了
3.2.7 CfgSaveAdapterStruct函数
把 cJSON 里adapter单个节点的字段填到 C 结构体 struct AlsaAdapterCfgInfo
//alsa_soundcard.c
// ===================== CfgSaveAdapterStruct =====================
// 功能:把 cJSON 里单个 adapter 节点的字段填到 C 结构体 struct AlsaAdapterCfgInfo
// 入参:adapter――cJSON 对象,info――输出结构体
// 返回值:HDF_SUCCESS / HDF_FAILURE
static int32_t CfgSaveAdapterStruct(cJSON *adapter, struct AlsaAdapterCfgInfo *info)
{
/*
Define structure description alsa_adapter.json information
struct AlsaAdapterCfgInfo {
char adapterName[MAX_CARD_NAME_LEN];
int32_t cardId;
char cardName[MAX_CARD_NAME_LEN];
};
*/
int32_t ret;
cJSON *item;
CHECK_NULL_PTR_RETURN_DEFAULT(adapter);
CHECK_NULL_PTR_RETURN_DEFAULT(info);
/* 1. 解析 name 字符串 */
item = cJSON_GetObjectItem(adapter, "name");
if (item == NULL || item->valuestring == NULL) {
AUDIO_FUNC_LOGE("adapter name is null!");
return HDF_FAILURE;
}
ret = memcpy_s(info->adapterName, MAX_CARD_NAME_LEN - 1, item->valuestring, MAX_CARD_NAME_LEN - 1);
if (ret != EOK) {
AUDIO_FUNC_LOGE("memcpy_s adapterName fail!");
return HDF_FAILURE;
}
/* 2. 解析 cardId(JSON 里可能是浮点,统一用 valuedouble) */
item = cJSON_GetObjectItem(adapter, "cardId");
if (item == NULL) {
AUDIO_FUNC_LOGE("cardId not set!");
return HDF_FAILURE;
}
info->cardId = item->valuedouble;
/* 3. 解析 cardName 字符串 */
item = cJSON_GetObjectItem(adapter, "cardName");
if (item == NULL || item->valuestring == NULL) {
AUDIO_FUNC_LOGE("cardName is null!");
return HDF_FAILURE;
}
ret = memcpy_s(info->cardName, MAX_CARD_NAME_LEN - 1, item->valuestring, MAX_CARD_NAME_LEN - 1);
if (ret != EOK) {
AUDIO_FUNC_LOGE("memcpy_s cardName fail!");
return HDF_FAILURE;
}
return HDF_SUCCESS;
}
3.2.8 CfgDumpAdapterInfo函数
把解析好的struct AlsaAdapterCfgInfo信息写入全局数组 g_alsaAdapterList[cardType]
//alsa_soundcard.c
// ===================== CfgDumpAdapterInfo =====================
// 功能:把解析好的单条 adapter 信息写入全局数组 g_alsaAdapterList[cardType]
// 入参:info――单条配置
// 返回值:HDF_SUCCESS / HDF_FAILURE
static int32_t CfgDumpAdapterInfo(struct AlsaAdapterCfgInfo *info)
{
int32_t ret, idx;
enum SndCardType cardType = SND_CARD_UNKNOWN;
CHECK_NULL_PTR_RETURN_DEFAULT(info);
/* 1. 根据 adapterName 字符串映射到枚举值 */
if (strcmp(info->adapterName, PRIMARY) == 0) {
cardType = SND_CARD_PRIMARY;
} else if (strcmp(info->adapterName, HDMI) == 0) {
cardType = SND_CARD_HDMI;
} else if (strcmp(info->adapterName, USB) == 0) {
cardType = SND_CARD_USB;
} else if (strcmp(info->adapterName, A2DP) == 0) {
cardType = SND_CARD_BT;
}
if (cardType == SND_CARD_UNKNOWN) {
AUDIO_FUNC_LOGE("Error: %{public}s is unspupported adapter name", info->adapterName);
}
idx = g_alsaAdapterList[cardType].num; // 取当前已存数量当下标
/* 2. 拷贝结构体到全局表 */
ret = memcpy_s((void*)&g_alsaAdapterList[cardType].list[idx], sizeof(struct AlsaAdapterCfgInfo),
(void*)info, sizeof(struct AlsaAdapterCfgInfo));
if (ret != EOK) {
AUDIO_FUNC_LOGE("memcpy_s g_alsaAdapterList fail!");
return HDF_FAILURE;
}
g_alsaAdapterList[cardType].num++; // 数量 +1
/* 3. 打印一条日志,方便调试 */
AUDIO_FUNC_LOGI("cardId:%{public}d: adapterName:%{public}s, cardName:%{public}s",
g_alsaAdapterList[cardType].list[idx].cardId,
g_alsaAdapterList[cardType].list[idx].adapterName,
g_alsaAdapterList[cardType].list[idx].cardName);
/*
log可以看到如下:
[CfgDumpAdapterInfo][line:191]: cardId:0: adapterName:primary, cardName:sprdphonesc2730
[CfgDumpAdapterInfo][line:191]: cardId:1: adapterName:hdmi, cardName:rockchiphdmi
*/
return HDF_SUCCESS;
}
3.3 代码逻辑梳理汇总
//alsa_soundcard.c
SndSaveCardListInfo
//读取vendor/etc/hdfconfig/alsa_adapter.json文件信息到全局数组g_alsaAdapterList中
1 CfgSaveAdapterFromFile
//把/vendor/etc/hdfconfig/alsa_adapter.json文件的内容读取到configBuf字符指针指向的内存
1.1 CfgReadAdapterFile
//把configBuf字符指针指向的内存里的 JSON 文本 变成 一颗树,然后就可以用 cJSON 的 API 获取信息
1.2 cJSON_Parse
//遍历 JSON 中的 adapters 数组,逐条解析后写入全局表g_alsaAdapterList中
1.3 CfgParseAdapterItems
//将adapters数组的节点取出来,这里我们只定义了两个
1.3.1 adapterItems = cJSON_GetObjectItem(adapterObj, "adapters");
for循环
//通过adapterItems逐个获取adapters数组的节点
1.3.2 cJSON_GetArrayItem
//把 cJSON 里adapter单个节点的字段填到 C 结构体 struct AlsaAdapterCfgInfo
1.3.3 CfgSaveAdapterStruct
//把解析好的struct AlsaAdapterCfgInfo信息写入全局数组 g_alsaAdapterList[cardType]
1.3.4 CfgDumpAdapterInfo
//调用 ALSA 层扫描实际硬件,填充 g_alsaCardsDevList
DevSaveDriverInfo
DevGetInfoByPcmInfoId

3.4 json配置文件
quectel@adbbd1e0c495:/home/openharmony$ cat xx/xx/alsa_adapter.json
{
"adapters": [
{
"name": "primary",
"cardId": 0,
"cardName": "sprdphonesc2730"
},
{
"name": "hdmi",
"cardId": 1,
"cardName": "rockchiphdmi"
}
]
}
quectel@adbbd1e0c495:/home/openharmony$
四、知道了alsa_adapter.json文件有什么用
4.1 原来的alsa_adapter.json文件
{
"adapters": [
{
"name": "primary",
"cardId": 0,
"cardName": "sprdphonesc2730"
},
{
"name": "hdmi",
"cardId": 1,
"cardName": "rockchiphdmi"
}
]
}
4.2 打印报错前获取到的adapter
当我在调试type-c耳机时它会找不到usb的adapters,报如下错误
[SndMatchSelAdapter][line:520]: unknow card type error.
添加打印日志如下
HDF_AUDIO_HAL_SND: [CfgGetAdapterCardType][line:128]: >>> table[0][0]: adapterName="primary" (len=7)
HDF_AUDIO_HAL_SND: [CfgGetAdapterCardType][line:131]: >>> user input: "usb" (len=3)
HDF_AUDIO_HAL_SND: [CfgGetAdapterCardType][line:128]: >>> table[1][0]: adapterName="hdmi" (len=4)
HDF_AUDIO_HAL_SND: [CfgGetAdapterCardType][line:131]: >>> user input: "usb" (len=3)
HDF_AUDIO_HAL_SND: [SndMatchSelAdapter][line:520]: unknow card type error.
发现g_alsaAdapterList (alsa_adapter.json文件解析出来) 中只有primary和hdmi
导致和上层传递下来的usb不一致Adapter匹配不上出问题了
4.3 解决方案
通过修改alsa_adapter.json文件即可解决问题
更多推荐



所有评论(0)