1)文章由移远通信技术股份有限公司提供
   2)以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改/删除,18777493676(微信同号)。

一、关键字

OpenHarmony 5.0.3 Releaseusb ; adapter;解析;

二、问题描述

2.1 运行环境

  • 系统版本:OpenHarmony-5.0.3-Release(以该版本为基线)
  • 硬件:展锐7885

2.2 问题现象

2.2.1 日志

我在调试type-c耳机时,在hal层它没找到usbadapter然后报错,导致播放失败了

关键日志如下:

[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 文本 变成 一颗树,然后就可以用 cJSONAPI 获取信息
这是第三方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函数

cJSONadapter单个节点的字段填到 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耳机时它会找不到usbadapters,报如下错误

[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文件解析出来) 中只有primaryhdmi

导致和上层传递下来的usb不一致Adapter匹配不上出问题了

4.3 解决方案

通过修改alsa_adapter.json文件即可解决问题

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐