[Linux Audio Driver] ACDB文件加载流程(完结篇)

高通 AudioReach 框架简介 从实现上,AudioReach 的 Linux ASoC 驱动是 AudioReach 能力的代理,或者适配器,它通过核间通信机制和 DSP 上运行的 AudioReach 通信,对于 Linux 内核,它实现 ASoC 的 PCM 和 compress 接口,向用户空间提供访问音频能力必不可少设备文件等接口。除了运行于 DSP 的 AudioReach 和它的 Linux ASoC 驱动程序,拓扑配置是整个子系统运行的另一个组成部分,Audioreach 的拓扑配置文件可以参考。 阅读详情
AI摘要

本文深入剖析了Android系统中ACDB文件的加载流程,详细介绍了acdb_loader_init_v2函数如何通过acdb_load_files加载ACDB文件,以及get_files_from_properties和get_files_from_device_tree函数在ACDB文件搜索中的作用。

0 背景

首先代码还是android7,之前两篇文章提到,拿到声卡名字以及在platform.c里面通过dlsym的方式,加载acdb_loader_init_v2函数, 今天继续分析;
代码路径:

vendor\qcom\proprietary\mm-audio\audio-acdb-util\acdb-loader\src\family-b\acdb-loader.c

int acdb_loader_init_v2(char *snd_card_name, char *cvd_version, int metaInfoKey){
…
{

1. 简单注解

int acdb_loader_init_v2(char *snd_card_name, char *cvd_version, int metaInfoKey)
{
	int				ret = 0;
	int				i;
	int				result = 0;
	AcdbVocProcGainDepVolTblSizeV2CmdType	vocvoltablesize;
	AcdbSizeResponseType		response;
	AcdbInitCmdType			acdb_init_cmd; //重点结构,后面分析
	AcdbGetMetaInfoSizeCmdType metaInfoSizeCmd;
	AcdbSizeResponseType metaInfoSize;

        pthread_mutex_lock(&loader_mutex); //上锁
        if (acdb_init_ref_cnt != 0) {
            acdb_init_ref_cnt++;
            ALOGD("ACDB -> already initialized, exit");
            goto done;
        }

	list_init(&aud_vol_idx_list.list); // 链表初始化,acdb id,app id相关,

	if (acdb_load_files(&acdb_init_cmd, snd_card_name) <= 0) { //acdb_load_files是加载
	                                                           //acdb文件的重点
		LOGE("ACDB -> Could not load .acdb files!\n");
		ret = -ENODEV;
		goto done;
	}

	LOGD("ACDB -> ACDB_CMD_INITIALIZE_V2\n");
	ret = acdb_ioctl(ACDB_CMD_INITIALIZE_V2,   //初始化acdb文件
		(const uint8_t *)&acdb_init_cmd, sizeof(acdb_init_cmd), NULL, 0);
    ......
	acdb_rtac_init(); //接收/发送来自QACT的数据,并将其发送/接收到RTAC驱动程序
	                  //(用于设备和QACT通信)
    ......
	acdb_loader_send_common_custom_topology(); //加载音频校准的API的数据并推送到DSP

	if (send_meta_info(metaInfoKey) < 0)
		LOGD("ACDB -> send_meta_info failed");

	current_feature_set = ACDB_VOCVOL_FID_DEFAULT;
	is_initialized = true; //bool变量,acdb加载一波之后置true

	parse_codec_type(snd_card_name); //声卡类型解析

	LOGD("ACDB -> init done!\n");
        acdb_init_ref_cnt++;

done:
        pthread_mutex_unlock(&loader_mutex); //释放锁
	return ret;
}

2. 重点分析

if (acdb_load_files(&acdb_init_cmd, snd_card_name) <= 0)

这句代码是加载acdb 的核心,我们先看一下这个变量acdb_init_cmd;

AcdbInitCmdType acdb_init_cmd;

//Structure to hold an individual ACDB file name and path.
LA.UM.5.6\vendor\qcom\proprietary\mm-audio\audcal\family-b\acdb\inc\acdb.h

struct _AcdbFileName{
   uint32_t fileNameLen;
   //< Full file path name length.
   char fileName[ACDB_FILENAME_MAX_CHARS];
     //< Array that holds the ACDB file path and name. The file size cannot
       //   exceed 256 characters, including the NULL-termiated character.
          //@newpagetable 
}

typedef struct _AcdbInitCmdType AcdbInitCmdType;
#include "acdb_begin_pack.h"


 //  Query command structure for the command ACDB_CMD_INITIALIZE_V2.

struct _AcdbInitCmdType {
   uint32_t nNoOfFiles;
  //< Number of ACDB files to read from the acdbFiles array. 
   AcdbFileName acdbFiles[20];
    //< Array of ACDB file names. A maximum of 20 ACDB files can be
         // provided at one time to be initialized. 
}

我们可以看到acdb_init_cmd是AcdbInitCmdType结构类型的,其存储了ACDB的文件名字以及
ACDB文件的位置;

接下来进去acdb_load_files函数,

vendor\qcom\proprietary\mm-audio\audio-acdb-util\acdb-loader\src\family-b\acdb-loader.c
static int acdb_load_files(AcdbInitCmdType *acdb_init_cmd, char * snd_card_name)
{
	int result = 0;

	result = get_files_from_properties(acdb_init_cmd);
	if (result > 0)
		goto done;

	result = get_files_from_device_tree(acdb_init_cmd, snd_card_name);
done:
	return result;
}

先分析get_files_from_properties函数,

vendor\qcom\proprietary\mm-audio\audio-acdb-util\acdb-loader\src\family-b\acdb-loader.c
static int get_files_from_properties(AcdbInitCmdType *acdb_init_cmd)
{
	int i = 0;
	int prop_len;
	char prop_name[24];

	for (i=0; i < MAX_ACDB_FILES; i++) {
		if (snprintf(prop_name, sizeof(prop_name), "persist.audio.calfile%d", i) < 0)
			goto done;

		prop_len = property_get(prop_name, acdb_init_cmd->acdbFiles[i].fileName, NULL);
		if (prop_len <= 0)
			goto done;

		acdb_init_cmd->acdbFiles[i].fileNameLen = 
		          strlen(acdb_init_cmd->acdbFiles[i].fileName);
		LOGD("ACDB -> Prop Load file: %s\n", acdb_init_cmd->acdbFiles[i].fileName);
	}
done:
	acdb_init_cmd->nNoOfFiles = i;
	return i;
}

这段代码很奇怪,首先这个persist.audio.calfile默认代码就没有配置(当然可能是作为调试用代码),
从我的理解来看,他会通过snprintf把"persist.audio.calfile0"存到字符数组prop_name里面去,

#define MAX_ACDB_FILES		20

这个常量最大20,所以字符最多为"persist.audio.calfile20",最大占用23个char 型位置,字符串最后补个\0的话,刚刚数组长度为24,那么其输出必然不被截断,返回值固定为24;

之后代码又通过property_get的方式,把刚才存到prop_name的值存到acdb_init_cmd->acdbFiles[i].fileName里面,而我们从刚才的重点的那个结构AcdbInitCmdType,

struct _AcdbInitCmdType {
   uint32_t nNoOfFiles;
  //< Number of ACDB files to read from the acdbFiles array. 
   AcdbFileName acdbFiles[20];
    //< Array of ACDB file names. A maximum of 20 ACDB files can be
         // provided at one time to be initialized. 
}

可以看出(从注释也行),其明明应该存储acdb 文件名字,所以这段代码很诡异,当时实际调试由于这个属性persist.audio.calfilem没有配置(从adb shell 进入设备,然后getprop persist.audio.calfilem没有返回值)可以看出,这段代码根本没有走到;

然后我们回到static int acdb_load_files(AcdbInitCmdType *acdb_init_cmd, char * snd_card_name)这个函数继续分析:

result = get_files_from_device_tree(acdb_init_cmd, snd_card_name);

先把这个 主要贴下来(只能删减写,不然这篇还写不完,发现优快云有篇幅限制),如下:

static int get_files_from_device_tree(AcdbInitCmdType *acdb_init_cmd, char *snd_card_name)
{
	int result = 0;
	char dir_path[300];
	char board_type[64] = DEFAULT_BOARD;
	FILE *fp = NULL;

	/* Get Board type */
	fp = fopen("/sys/devices/soc0/hw_platform","r");
	if (fp == NULL)
		fp = fopen("/sys/devices/system/soc/soc0/hw_platform","r");
	if (fp == NULL)
		LOGE("ACDB -> Error: Couldn't open hw_platform\n");
	else if (fgets(board_type, sizeof(board_type), fp) == NULL)
		LOGE("ACDB -> Error: Couldn't get board type\n");
	else if (board_type[(strlen(board_type) - 1)] == '\n')
		board_type[(strlen(board_type) - 1)] = '\0';
	if (fp != NULL)
		fclose(fp);

	/* get files from form factor & soundcard independant path */
	result = get_acdb_files_in_directory(acdb_init_cmd, ACDB_BIN_PATH);
	if (result >= 0)
		LOGD("ACDB -> found %d form factor & soundcard independant files\n",
			result);
	if (result > MAX_INDEPENDANT_ACDB_FILES)
		goto done;

	/* Try board directory with soundcard name */
	if (snd_card_name != NULL) {
		result = snprintf(dir_path, sizeof(dir_path), "%s/%s/%s", 
		ACDB_BIN_PATH, board_type, snd_card_name);
		if (result < 0) {
			LOGE("ACDB -> Error: snprintf failed for snd card %s, error: %d\n", 
			snd_card_name, result);
			result = -ENODEV;
			goto done;
		}
		result = get_acdb_files_in_directory(acdb_init_cmd, dir_path);
		if (result > 0)
			goto done;
	}

	......
}

高通代码上来就给你个注释:

/* Get Board type */
fp = fopen("/sys/devices/soc0/hw_platform","r");
if (fp == NULL)
	fp = fopen("/sys/devices/system/soc/soc0/hw_platform","r");
if (fp == NULL)
	LOGE("ACDB -> Error: Couldn't open hw_platform\n");
else if (fgets(board_type, sizeof(board_type), fp) == NULL)
	LOGE("ACDB -> Error: Couldn't get board type\n");
else if (board_type[(strlen(board_type) - 1)] == '\n')
	board_type[(strlen(board_type) - 1)] = '\0';
if (fp != NULL)
	fclose(fp);

board_type,这个代码里面默认配置的是MTP:

~/LA.UM.5.6$ grep -nr "DEFAULT_BOARD" ./vendor/ ./hardware/
./vendor/qcom/proprietary/mm-audio/audio-acdb-util/acdb-loader/Android.mk:17:
libacdbloader-def += -D DEFAULT_BOARD=\"MTP\"

./vendor/qcom/proprietary/mm-audio/audio-acdb-util/acdb-loader/src/family-b/acdb-loader.c
:616:  char board_type[64] = DEFAULT_BOARD;

然后经过fopen的读取之后,我们能够获取到他的值是QRD,这玩意是高通CPU的平台信息,芯片内部
有一段空间是存储这个平台信息的,抽空俺也可以分析一波(之前有了解,但是忘了,信息好像是从
modem那边传过来的);随便找台设备,cat 一下这个节点值,我们就知道是QRD了。
在这里插入图片描述

/* get files from form factor & soundcard independant path */
result = get_acdb_files_in_directory(acdb_init_cmd, ACDB_BIN_PATH);
if (result >= 0)
	LOGD("ACDB -> found %d form factor & soundcard independant files\n",
		result);
if (result > MAX_INDEPENDANT_ACDB_FILES)
	goto done;

后面的代码几乎都是get_acdb_files_in_directory函数在跑了,

ACDB_BIN_PATH在android 7是 设备里面的/etc/acdbdata/

result = get_acdb_files_in_directory(acdb_init_cmd, ACDB_BIN_PATH);

这个代码的意思就是,先在设备里面的etc/acdbdata/目录下面找,能不能找到那八个文件?(一般是8个),
#更新 2022.02.08
春节前去高通上海参加了培训,他们新一代的AudioReach架构,acdb文件只有一个了,即集成到一个文件中。
#更新 2022.02.08

找到了goto done;函数跑完,找不到,走下一个判断;

result = snprintf(dir_path, sizeof(dir_path), "%s/%s/%s", 
              ACDB_BIN_PATH, board_type, snd_card_name);

接下来看你能否在/etc/acdbdata/QRD/msm8953-sku3-snd-card下面找到这个acdb文件;之后就是依次查找: /etc/acdbdata/QRD/ -> /etc/acdbdata/MTP/msm8953-sku3-snd-card ->
/etc/acdbdata/MTP -> /etc

总之,按照这个流程在设备里面找这个参数,按代码顺序,谁先找到就加载那个位置的acdb文件。

补充: 这个acdb的文件由系统编到设备目录下;

3. 总结与引申

其实整个acdb 文件的加载流程如下图所示:

在这里插入图片描述
(备注,图片的标号略有问题,俺就懒得改了…)

那么我们知道这个有啥用呢,其实在实际研发过程中,会遇到不得不使用同一套软件而兼容不同硬件状态的情况,具体可参考下面这位博主的博客,我就不多聊了。

https://blog.youkuaiyun.com/crow_ch/article/details/103886156

4. 作者注

/******
@article{Linux Audio Driver,
Author = { 1byte ≠ 8bit},
Year = { 2020},
}
******/
从AGM到ADSP:图解高通AudioReach架构下的一次PCM_READ完整旅程 本文深入解析了高通8650平台AudioReach架构下PCM_READ的完整数据流,从用户空间请求到ADSP处理的每个关键环节。详细介绍了AGM、GSL和GPR等核心组件的协作机制,以及如何优化音频处理性能,为开发者提供了实用的调试和调优指南。 阅读详情

相关推荐

[Linux Audio Driver] ACDB文件加载路径解析与多硬件兼容实战

本文深入解析了Linux音频驱动中ACDB文件的加载路径与多硬件兼容实战技巧。ACDB文件作为音频校准数据库,对硬件兼容性至关重要。文章详细介绍了加载路径的优先级机制、多硬件适配的关键代码及调试技巧,并探讨了新一代AudioReach架构的变化,帮助开发者解决音频驱动兼容性问题。

weixin_30036289的博客 813

Qualcomm audio培训

高通audio培训。

编程菜汝狗 3962

Linux Audio Driver 排错:acdb id 10 导致喇叭无声的 2 种定位与修复

本文深入解析Linux音频驱动中acdb id 10导致喇叭无声的问题,提供两种有效解决方案:修正DTS硬件检测配置和强制覆盖acdb ID。通过详细的技术分析和实战案例,帮助开发者快速定位和修复音频路由问题,适用于高通平台的嵌入式Linux系统开发。

weixin_29173777的博客 237

二、Audio 架构

Audio架构图 从上图看出,共分为高中低三层 **高层: AVaudioplayer** audio 基本操作:播放 暂停 停止 循环 时间控制:任意时间位置播放、进度控制 从文件或者缓冲区播放 获取音视频关键参数AVaudioqueue 播放录制、可以拿到音频原始处理数据 1、初始化音频管理队列 2、音频队列:管理回调方法 3、实例化音频队列:audioqueueoutpu

flover5724059的博客 1236

【Audio Driver】ACDB文件加载

ADCB文件分析 文件目录 加载流程 文件信息存放在哪 文件名 解析后的数据 解析acdb文件函数 ACDB Loader acdb文件如何修改 补充 wsa状态判断 acdb文件分析路径分析 hwdep_send_cal函数处理 如何获取acdb文件 1 acdb介绍 参考文章:qcom 音频相关的dsp driver笔记(基于msm8996平台) 这个文章介绍的特别详细。 acdb,全称:au...

qq_38091632的博客 1万+

Qualcomm AudioReach音频架构

【代码】Qualcomm AudioReach音频架构。

sdrgtywretyeruet的专栏 1624

[Linux Audio Driver] ACDB文件加载流程(二)

1.概述 上篇文章,我们通过分析snd_card_name = mixer_get_name(adev->mixer);这句代码,我们拿到了声卡名字,详情见以下超链接: ACDB文件加载流程(一) 然后今天接着分析void *platform_init(struct audio_device *adev)函数,探讨acdb是如何被加载的, 其代码路径是: hardware\qcom\audi...

编程菜汝狗 3254

高通音频驱动调试:SLIMbus配置与ACDB加载实战

SLIMbus是高通平台专用的低功耗音频总线,其控制器初始化是音频子系统启动的前提;ACDB(Audio Calibration Database)则承载硬件级声学校准参数,决定codec能否正确初始化。理解SLIMbus主从通信机制、设备树地址映射规则及ACDB的HWID匹配逻辑,是打通Linux内核侧音频通路的关键。该技术广泛应用于车载信息娱乐(IVI)、智能座舱(如SA8155)、AR/VR音频模块等嵌入式场景。本文聚焦SLIMbus设备树使能、ACDB二进制加载验证与machine driver绑定

weixin_42525632的博客 209

[Linux Audio Driver] ACDB文件加载流程(一)

声卡信息是acdb加载中最重要的信息,本文追踪代码,解释声卡名如何被取出来使用的; 环境: Qualcomm平台,android7 , 内核版本:msm-3.18 1. platform_init函数 代码路径: hardware/qcom/audio/hal/msm8916/platform.c void *platform_init(struct audio_device *adev) ...

编程菜汝狗 5397

高通audio入门

介绍了高通audio入门 1.Audio软硬件系统框架 2.Audio bringup 3.ACDB校准 4.how to debug

高通平台音频应用指导_V1.0(初稿).pdf

移远 高通平台音频应用指导

[Linux Audio Driver] 高通音频相关汇总(持续更新)

0. 背景 之前写了一些音频的博客,但是比较散乱,就想着汇总下写的博客,也是对自己知识结构的梳理,这个会持续更新。 1. 代码分析&总结 I2S相关介绍 ACDB文件加载流程(一) ACDB文件加载流程(二) ACDB文件加载流程(完结篇) 高通平台内部MIC_BIAS简介 从设备地址理解 Android7 外部PA Speaker代码结构 MSM8953音频部分设备树调用关系 开启音频kernel内核debug功能 声卡与XML匹配流程 SDM660平台内部CODEC tinyalsa命令总结 S

编程菜汝狗 4962

FPGA加速SNN实现MNIST手写数字识别的实践与优化

脉冲神经网络(SNN)作为第三代神经网络模型,采用生物启发的脉冲事件驱动机制,在处理时序信号时具有天然能效优势。其核心原理是通过神经元仅在接收足够输入脉冲时激活的稀疏特性,大幅降低计算能耗。在FPGA硬件加速领域,SNN通过量化优化、硬件友好型神经元模型设计等关键技术,可在边缘计算场景实现超高能效比。本文以MNIST手写数字识别为案例,详细解析了从6bit量化策略到双缓冲流水线设计的完整优化路径,最终在Xilinx Kintex-7平台实现97.54%识别准确率的同时,功耗仅231mW,能效比达81,712

weixin_30417487的博客 529

高通Audio中ASOC的codec驱动 -----mark

高通Audio中ASOC的codec驱动(二) 原文:https://www.tinymind.net.cn/articles/8c917351355016 继上一篇文章:高通Audio中ASOC的machine驱动(一) ASOC的出现是为了让codec独立于CPU,减少和CPU之间的耦合,这样同一个codec驱动就无需修改就可以匹配任何一款平台。 在Machine中已经知道,snd...

weixin_42082222的博客 1965

移植 lwip,在 windows 内核构建一个 TCPIP 协议栈

移植 lwip,在 windows 内核构建一个 TCPIP 协议栈 亲,当你点开看这篇文章,就说明你知道我想要干什么事情,或者说,你知道在 Windows 的内核种绕开它的网络协议,再独立构建一个网络协议栈的意义。如果你要问我有 windows 系统的网络协议栈不用,移植 lwip 有什么意义,那么你可以关闭链接走人了,因为这篇文章不适合你,它不是讲 lwip 移植原理的。 经常关注我的博客的朋...

oRbIt 的专栏 3578

基于禾赛32线雷达和Xsens MTi-G-710 组合导航模块,松灵Scout mini 底盘实现 Cartographer 3D 建图过程

地下车库 Cartongrapher 3D建图

yhy_yhy13053117的博客 3936

高通8650 AudioReach架构中的GSL-Passthru-GPR推理过程

在分析audio-pkt.c(passthru)这个文件的代码时,我们推断它实现了一个名为aud_passthru_adsp的字符设备。原生tinyalsa pcm_open操作后端注册的dai link,把codec前后端连起来,还有codec上电,数据走agm。audio_prm.c 提供时钟控制接口,比如开关的功能,或者介于开关的feature调整。函数属性,实现了从应用处理器 (AP) 到后端驱动的通信。是一个包含函数指针的结构体,其中一个重要的函数指针是指向。的代码位于gpr_lx.c文件的。

常来看看~ 1829

YooAsset 2.2.12四层加密架构:跨平台资源安全实战指南

AssetBundle加密是Unity热更新安全的核心环节,其本质是通过算法、密钥管理与生命周期协同实现资源全链路防护。传统方案常陷于‘打包后加密’的误区,导致内存开销大、跨平台兼容差、逆向防护弱。YooAsset 2.2.12提出打包层、传输层、加载层、运行层四层防护架构,将加密前置到资源序列化前,并依托BundleHeader实现动态算法协商与密钥分发,显著提升iOS/Android/WebGL三端一致性。该方案兼顾AES-128等通用算法与SM4等国密支持,同时通过IMemoryDecryptor实现

weixin_32428571的博客 416

音频 (一) AudioRecord 架构简介

    去年做了关于蓝牙音频这一块,当时没有进行总结,现在再去总结都忘了差不多了,但是不管怎么样还是进行总结一下吧!总结过程中有什么不对的地方请大家不吝指教。音频基础知识声音有哪些重要属性呢?响度(Loudness)响度就是人类可以感知到的各种声音的大小,也就是音量。响度与声波的振幅有直接关系。音调(Pitch)音调与声音的频率有关系,当声音的频率越大时,人耳所感知到的音调就越高,否则就越低。音色...

Tony-jiang的博客 8470

基于非接触式微波雷达设计的跌倒检测预警系统【短信报警】

本项目选择基于非接触式微波雷达的跌倒检测预警装置作为技术核心,采用云帆瑞达的R60AFD1跌倒检测模组。这一模组能够根据人体的运动变化,如速度、距离和姿态等特征,判断是否发生跌倒,并可以持续监控老年人是否处于危险状态。此外,本项目还结合4G通信技术,通过Air724UG模块实现短信报警功能,一旦检测到跌倒事故,系统将自动通过4G网络向紧急联系人发送短信报警,及时通知亲属或医护人员进行救援。

1755

Android 9.0 SystemUI禁用长按recent键的分屏功能

​ 在9.0的系统产品开发中,系统对于多窗口模式默认会有分屏功能的,但是在某些产品中,需要禁用分屏模式,所以需要在导航栏中 禁用长按recent的分屏模式功能,接下来分析下相关分屏模式的实现 ​

安卓兼职framework和app工程师的博客 449

高通8650 AudioReach 架构中 Virtual Mic & Loopback

然而,由于平板移除了自带的扬声器,在 DP in 场景下,平板需要使用座舱的扬声器。答:上层的任何应用程序在获取录音时,最终都会通过 Android 的标准 API 调用到 AudioFlinger,从 HAL 获取录音。上面提到,DP in 被用作录音设备。在实际方案讨论中,有人建议在 HAL 中添加 IPC,如果选择在 HAL 中添加 IPC,就需要找到 pcm_read 的位置进行场景区分。由于 DP in 是一种特殊的录音设备,专供特定应用使用,不需要开放给其他第三方应用,因此这个方案是可行的。

常来看看~ 2332

Linux加密框架中的算法和算法模式(1)——算法介绍

算法和算法模式

yangguoyu8023的博客 2032

Audio框架层理解

http://blog.youkuaiyun.com/gowyz/article/details/6019314

xuwenwu1985的博客 608

【纯干货分享】常用高速接口眼图模板合集

文章汇集了常见的高速接口的信号眼图测试模板技术要求,比如,LPDDR5/4、PCIe 5.0/4.0、USB、MIPI等等,方便工程师快速进行查询和使用。

一只豌豆象的博客 3116

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Android平台实现系统内录(捕获播放的音频)并推送RTMP服务技术方案探究

低版本的Android系统,方案1应该是相对可行但局限很大的选择,方案2大多时候,非定制设备,很难满足权限要求,方案3对Android系统版本要求比较高。通过测试,方案3除了对Android版本要求比较高外,体验式最好的,感兴趣的开发者,可以尝试看看,如果是特定场景下,本身选用的设备,Android的版本就比较高,又有内录audio需求的话,无疑是非常不错的选择。

大牛直播SDK 3669

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

上一篇: [Linux Audio Driver] PDM CLOCK导致的耳机右声道POP音问题
下一篇: [C++基础] 在线C++编译器推荐
1byte ≠ 8bit
博客等级 码龄8年 7242粉丝 170原创
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值