杰理可视化SDK开发-音频录制功能使用教程

前言

现在为止也开发了许多杰理和中科蓝讯TWS蓝牙耳机、音响项目 SDK的案子,在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总,方便各位同行和对杰理芯片SDK感兴趣的小伙伴们学习;

本章详细讲解杰理可视化SDK开发-音频录制功能使用教程

今天遇到声学工程师要求给TWS耳机开启音频录制功能给他用于分析耳机双麦之间的差异;正好借这个机会出一个关于杰理可视化SDK开启音频录制功能的教程;

一、这个功能到底是干什么的?

用一句话概括:音频录制功能是一个"调试用数据抓取通道"------它把耳机麦克风实时采集到的原始声音数据,原封不动地导出到外部(上位机 / SD 卡 / 串口),供工程师在 PC 端做分析。

它最常见的三个用途:

用途 说明
双麦 ENC 降噪调试 把左、右两个麦克风的原始数据导出来,核对降噪算法输入是否正确
通话算法(CVP/AEC)调试 抓取通话过程中的麦克风与参考信号,回放分析回声、噪声

⚠️ 注意:这是工程调试功能 ,不是面向消费者的"录音机"功能。它默认是关闭的(TCFG_AUDIO_DATA_EXPORT_DEFINE 为 0),只有需要调试时才打开。

二、如何开启(改一个宏即可)

整个功能的开关集中在一个宏上:TCFG_AUDIO_DATA_EXPORT_DEFINE。

2.1 找到配置位置

文件:apps/earphone/include/app_config.h(第 607~616 行)

cpp 复制代码
/*Audio数据导出配置:通过蓝牙spp导出/sd写卡导出/uart写卡导出*/
#define AUDIO_DATA_EXPORT_VIA_UART	1
#define AUDIO_DATA_EXPORT_VIA_SPP 	2
#define AUDIO_DATA_EXPORT_VIA_SD	3
#define TCFG_AUDIO_DATA_EXPORT_DEFINE		AUDIO_DATA_EXPORT_VIA_SPP//DISABLE_THIS_MOUDLE

#if TCFG_DATA_EXPORT_NODE_ENABLE
#undef TCFG_AUDIO_DATA_EXPORT_DEFINE
#define TCFG_AUDIO_DATA_EXPORT_DEFINE		AUDIO_DATA_EXPORT_VIA_UART
#endif

2.2 三种取值,任选其一

把 TCFG_AUDIO_DATA_EXPORT_DEFINE 改成下面三个值中的一个,就对应一种导出方式:

取值 含义 数据去向
AUDIO_DATA_EXPORT_VIA_UART(=1) 串口导出 耳机 UART1 串口 → 串口工具
AUDIO_DATA_EXPORT_VIA_SPP(=2) 蓝牙 SPP 导出 耳机 SPP → 上位机(JL Studio/测试工具)
AUDIO_DATA_EXPORT_VIA_SD(=3) SD 卡写卡导出 耳机 → SD 卡文件

2.3 开启步骤(以 SPP 导出为例)

  1. 打开 apps/earphone/include/app_config.h;

  2. 将 TCFG_AUDIO_DATA_EXPORT_DEFINE 改为目标方式,例如:

    cpp 复制代码
    #define TCFG_AUDIO_DATA_EXPORT_DEFINE		AUDIO_DATA_EXPORT_VIA_SPP
  3. 重新编译并烧录耳机固件;

  4. 若选 SPP 方式,用上位机(JL Studio 等)通过蓝牙 SPP 连接耳机,下发"开始录制"命令即可收到麦克风数据。

提示:TCFG_DATA_EXPORT_NODE_ENABLE 一旦为真,会强制 把导出方式切回 UART(见 2.1 的第二个 #if),这一点在切换方式时需要留意。

三、三种导出方式深入对比

3.1 方式一:串口导出(UART,宏值 1)

数据流 :麦克风 → CVP/AEC 通话处理模块内部 → uartSendData() → UART1 → 串口调试工具。

入口(关键代码):

  • audio/common/audio_general.c:108-112 ------ 初始化阶段调用 uartSendInit(),打开并初始化 UART1:

    cpp 复制代码
    int audio_general_init()
    {
    #if ((defined TCFG_AUDIO_DATA_EXPORT_DEFINE) && (TCFG_AUDIO_DATA_EXPORT_DEFINE == AUDIO_DATA_EXPORT_VIA_UART))
        uartSendInit();
    #endif
    ...
    }
  • audio/CVP/audio_cvp.c:76-81 ------ 打开 AEC 串口数据导出开关:

    cpp 复制代码
    #if ((defined TCFG_AUDIO_DATA_EXPORT_DEFINE) && (TCFG_AUDIO_DATA_EXPORT_DEFINE == AUDIO_DATA_EXPORT_VIA_UART))
    /*AEC串口数据导出*/
    const u8 CONST_AEC_EXPORT = 1;
    #else
    const u8 CONST_AEC_EXPORT = 0;
    #endif
  • audio/common/uartPcmSender.c ------ 发送实现:uartSendInit() 初始化 UART1(波特率 PCM_UART1_BAUDRATE、DMA),uartSendData() 用 DMA 阻塞式发送。

特点:

  • 不关闭 TWS/BLE/A2DP,也不创建音频采集任务(audio_capture_init 不被调用)。
  • 数据直接由 AEC 模块内部导出到串口,链路最"轻"。
  • 需要接串口线,适合桌面联调,不适合无线场景。

3.2 方式二:蓝牙 SPP 导出(宏值 2,最常用)

数据流:

cpp 复制代码
麦克风 ADC 采集
   → adc_mic_output() 拆出 mic0/mic1(/mic2) 各路数据
   → 写入环形缓冲 mic0_cb / mic1_cb / mic2_cb
   → AudioCapture_Task 取出、按通道打包(4字节序号 + 640字节数据)
   → spp_data_export() → online_db 协议打包
   → SPP 蓝牙通道 → 上位机

入口(关键代码):

  • apps/earphone/mode/bt/earphone.c:472-480 ------ 开机流程里:SPP 方式先关闭蓝牙角色切换,再初始化音频采集:

    cpp 复制代码
    #if TCFG_AUDIO_DATA_EXPORT_DEFINE
    #if (TCFG_AUDIO_DATA_EXPORT_DEFINE == AUDIO_DATA_EXPORT_VIA_SPP)
            lmp_hci_set_role_switch_supported(0);   // 关闭角色切换
    #endif
    #if ((TCFG_AUDIO_DATA_EXPORT_DEFINE == AUDIO_DATA_EXPORT_VIA_SPP) \
        || (TCFG_AUDIO_DATA_EXPORT_DEFINE == AUDIO_DATA_EXPORT_VIA_SD))
            audio_capture_init();
    #endif
    #endif
  • apps/earphone/include/app_config.h:842-857 ------ SPP 方式下强制关闭 TWS/BLE/A2DP、打开 SPP 与在线调试(详见第五节)。

  • audio/common/online_debug/audio_capture.c:376-406 ------ data_export_init() 里,SPP 方式调用 aec_data_export_init(SPP_EXPORT_CH)。
  • audio/CVP/audio_cvp_online.c:693-733 ------ 注册命令解析 aec_online_parse(),处理上位机下发的:
    • AEC_RECORD_COUNT(0x200):查询导出通道数
    • AEC_RECORD_START:调用 audio_capture_start() 开始录音
    • AEC_RECORD_STOP:调用 audio_capture_stop() 停止录音
    • ONLINE_OP_QUERY_RECORD_PACKAGE_LENGTH:查询每包长度(回复 644)
  • audio/CVP/audio_cvp_online.c:758-780 ------ spp_data_export():录音状态下把数据按通道用 app_online_db_send_more() 发出去。

特点:

  • 由上位机控制启停 :耳机开机只做好采集准备,收到上位机 AEC_RECORD_START 命令后才真正启动麦克风采集。
  • 采样率 16 kHz(AC_SAMPLE_RATE),支持双麦(2 路)或三麦(3 路)导出。
  • 每包结构:4 字节序号 + 640 字节数据 = 644 字节 (SPP_EXPORT_MTU)。
  • 需要关闭 TWS/BLE/A2DP,是最常用的无线调试方式。

3.3 方式三:SD 卡写卡导出(宏值 3)

数据流:

cpp 复制代码
麦克风 ADC 采集
   → adc_mic_output() → 环形缓冲
   → AudioCapture_Task 取出 → data_export_run() 按通道写入缓冲
   → DataExport_Task → force_write_sd() 直接写 SD 卡扇区

入口(关键代码):

  • audio/common/online_debug/audio_capture.c:818-832 ------ SD 方式下开机自动开始录音 (无需上位机):

    cpp 复制代码
    static void audio_capture_board_init()
    {
        led_init();
        data_export_init();
    ...
    #if (TCFG_AUDIO_DATA_EXPORT_DEFINE == AUDIO_DATA_EXPORT_VIA_SD)
        audio_capture_start();      // SD 导出:默认自动启动声音捕获
    #endif
        dm.state = AC_STATE_INIT;
    }
  • audio/common/online_debug/audio_capture.c:376-397 ------ data_export_init() 里,SD 方式:给 SD 卡供电(RT9193,JL_PORTB 第 3 脚)→ force_open_sd() 打开 SD → 创建 DataExport_Task 写卡任务。

  • audio/common/online_debug/audio_capture.c:216-240 ------ data_export_run() 把数据写入写卡缓冲。

  • audio/common/online_debug/audio_capture.c:338-355 ------ DataExport_Task 用 force_write_sd() 按扇区写卡,起始扇区 SD_START_BLOCK = 50000,每次写 512 字节(SD_PCM_BUF_SIZE)。

特点:

  • 完全离线:不需要蓝牙连接、不需要上位机,开机即录。
  • 数据直接写 SD 卡,适合现场长时间抓数据、事后分析。
  • 不关闭 TWS/BLE(earphone.c 里 SD 分支只调用 audio_capture_init,不关角色切换)。

3.4 一张表看清三种方式

对比项 UART(1) SPP(2) SD(3)
数据去向 UART1 串口 蓝牙 SPP → 上位机 SD 卡
是否创建采集任务 否 是 是
是否关闭 TWS/BLE/A2DP 否 是 否
是否需要上位机/蓝牙 需接串口 需 SPP 连接 不需要
录音启停 随 AEC 导出 上位机命令控制 开机自动
数据格式 AEC 内部格式 4 字节序号 + 640 数据 512 字节/扇区(可加 12 字节头)
典型场景 桌面串口联调 无线在线调音/调 ENC 现场离线抓数

四、代码运行骨架(内部是怎么跑起来的)

本节以最常用的 SPP 导出 为主线,SD/UART 只标差异。所有函数与行号均在 audio/common/online_debug/audio_capture.c,除非特别说明。

4.1 开机初始化链路(从开机到"录音就绪")

cpp 复制代码
开机 power on
   │  apps/earphone/mode/bt/earphone.c:472-480
   ▼
[TCFG_AUDIO_DATA_EXPORT_DEFINE 生效]
   ├─ SPP 方式:lmp_hci_set_role_switch_supported(0)   // 关角色切换
   └─ SPP/SD 方式:audio_capture_init()                 // audio_capture.c:996
         ├─ os_sem_create(&dm.sem, 0)                   // 建信号量
         └─ task_create(AudioCapture_Task, "aud_capture")  // 建采集任务
              │  audio_capture.c:947
              ▼
         AudioCapture_Task
              └─ audio_capture_board_init()             // audio_capture.c:818
                   ├─ led_init()
                   ├─ data_export_init()                // audio_capture.c:376
                   │    ├─ SD 方式:给卡供电(RT9193/PB3) + force_open_sd()
                   │    │           + task_create(DataExport_Task, "data_export")
                   │    └─ SPP 方式:aec_data_export_init(SPP_EXPORT_CH)
                   │              → app_online_db_register_handle(DB_PKT_TYPE_EXPORT, aec_online_parse)
                   └─ SD 方式:audio_capture_start()    // 开机自动开始
              ▼
         主循环(见 4.2/4.3)

SPP 的蓝牙通道初始化是另一条并行链路(在蓝牙协议栈初始化时完成):

cpp 复制代码
APP_ONLINE_DEBUG = 1                              // app_config.h:854
   └─ BT_AI_SEL_PROTOCOL |= ONLINE_DEBUG_EN       // app_config.h:879-886
        └─ earphone.c:497 调用 multi_protocol_bt_init()
             └─ multi_protocol_main.c:378-380
                  #if (BT_AI_SEL_PROTOCOL & ONLINE_DEBUG_EN)
                      online_spp_init();           // spp_online_db.c:113
                          ├─ app_spp_hdl_alloc()          // 分配 SPP 通道句柄
                          ├─ app_spp_hdl_uuid_set()       // 设服务 UUID
                          ├─ app_spp_recieve_callback_register()  // 收数据回调
                          └─ app_spp_state_callback_register()    // 连接状态回调
                  #endif

4.2 SPP 方式:数据流 + 命令流

命令流(上位机 → 耳机),用于"开始/停止录音":

cpp 复制代码
上位机 --SPP--> online_spp_recieve_cbk()      // spp_online_db.c:100
   └─ db_api->packet_handle()  →  aec_online_parse()   // audio_cvp_online.c:693
         ├─ AEC_RECORD_COUNT(0x200)        → 回复通道数
         ├─ AEC_RECORD_START               → audio_capture_start()   // 真正启动麦克风
         ├─ AEC_RECORD_STOP                → audio_capture_stop()
         └─ QUERY_RECORD_PACKAGE_LENGTH    → 回复每包长度 644

audio_capture_start()(audio_capture.c:834)内部做三件事:

  1. 设置采样率 dm.sr = 16000,初始化 3 个环形缓冲 mic0_cb / mic1_cb / mic2_cb;
  2. 麦克风上电 + audio_mic_init(16000):打开 ADC,把回调 adc_mic_output 挂到 ADC 输出上;
  3. (定时器模式)创建 aec_export_timer 定时器,周期 SPP_SEND_INTERVAL(双麦 6ms / 三麦 4ms)。

数据流(耳机 → 上位机):

cpp 复制代码
麦克风 → ADC 中断
   └─ adc_mic_output()                        // audio_capture.c:432
         ├─ 双麦:把交织数据拆成 mic0 / mic1
         ├─ 三麦:拆成 mic0 / mic1 / mic2
         └─ cbuf_write(mic0_cb / mic1_cb / mic2_cb)
            └─ os_sem_post(&dm.sem)

定时器 aec_export_timer()                      // audio_capture.c:802,每 6ms/4ms
   └─ spp_data_export_run()                    // audio_capture.c:720
         ├─ 按 ch_idx 轮询 0/1/2 三路
         ├─ 每路:cbuf_read 640 字节 → export_buf[4..643]
         ├─ export_buf[0..3] 写入 4 字节序号(seqn)
         └─ spp_data_export(ch, export_buf, 644)   // audio_cvp_online.c:758
               └─ app_online_db_send_more(DB_PKT_TYPE_DAT_CH0/1/2, ...)
                     └─ online_db 协议打包 → app_spp_data_send() → SPP → 上位机

关于"定时器模式":audio_capture.c:110 定义 SPP_SEND_BY_TIMER = 1(默认)。

  • 为 1 时:由 aec_export_timer 定时器驱动发送(上文流程);
  • 为 0 时:改为由 ADC 中断 os_sem_post 唤醒 AudioCapture_Task,在任务里直接调 spp_data_export_run()(audio_capture.c:989)。

4.3 SD 方式:数据流

cpp 复制代码
audio_capture_board_init() → audio_capture_start()(开机自动)
   └─ 麦克风 ADC → adc_mic_output() → cbuf

AudioCapture_Task 主循环(audio_capture.c:956-968)
   └─ 当 mic0_cb、mic1_cb 都凑满 512 字节(DM_RUN_SIZE):
         ├─ 读 mic0 → data_export_run(mic0, 512, ch0)
         ├─ 读 mic1 → data_export_run(mic1, 512, ch1)
         ├─ DualMic_NoiseReduce_run() 双麦降噪
         └─ data_export_run(output, ..., ch2)

data_export_run()(audio_capture.c:216)→ 写入写卡缓冲
DataExport_Task(audio_capture.c:338)
   └─ 缓冲满 512 字节 → force_write_sd(buf, 扇区, 1) 写卡

4.4 UART 方式:数据流

cpp 复制代码
audio_general_init()(audio_general.c:108)
   └─ uartSendInit()(uartPcmSender.c:49)初始化 UART1 + DMA

CVP/AEC 模块(CONST_AEC_EXPORT = 1,audio_cvp.c:78)
   └─ uartSendData(buf, len)(uartPcmSender.c:20)
         └─ DMA 拷贝 + uart_send_blocking() 串口发送

五、开启后耳机软件发生了什么变化

"改一个宏"背后,编译器通过条件编译改变了整个固件的功能形态 。下面按方式分别说明(均对应 apps/earphone/include/app_config.h,除非标注)。

5.1 SPP 方式(变化最大)

SPP 方式会大幅收窄蓝牙功能,因为它把耳机临时变成了一台"SPP 数据采集机":

变化项 值 出处
关闭 TWS(双耳互联) TCFG_USER_TWS_ENABLE = 0 app_config.h:849
关闭 BLE TCFG_USER_BLE_ENABLE = 0 app_config.h:850
关闭 A2DP(音乐) TCFG_BT_SUPPORT_A2DP = 0 app_config.h:853
连接设备数改为 1 TCFG_BD_NUM = 1 app_config.h:851
打开经典蓝牙 SPP TCFG_BT_SUPPORT_SPP = 1 app_config.h:852
打开在线调试 APP_ONLINE_DEBUG = 1 app_config.h:854
关闭角色切换 lmp_hci_set_role_switch_supported(0) earphone.c:474
协议选择加入 ONLINE_DEBUG `BT_AI_SEL_PROTOCOL = ONLINE_DEBUG_EN`

由此带来更深层的连锁反应:

  1. 新增在线调试协议层 :APP_ONLINE_DEBUG = 1 使 online_db 协议层被编译进来,负责把数据打包成 SPP 协议包。

  2. SPP 通道注册 :online_spp_init() 分配专用 SPP 句柄并注册收发回调(spp_online_db.c:113)。

  3. 内存占用增加 :发送缓冲 DB_MEM_BUF_LEN 从默认的 768 字节增加到 768 * 15 = 11520 字节(online_db_deal.c:26-30),因为录音数据量远大于普通调试。

  4. 新增两个任务 :aud_capture(优先级 4)和 data_export(优先级 5),在 apps/earphone/app_main.c:88-91 注册:

    cpp 复制代码
    #if TCFG_AUDIO_DATA_EXPORT_DEFINE
        {"aud_capture",         4,     0,   512,   256 },
        {"data_export",         5,     0,   512,   256 },
    #endif
  5. 麦克风持续采集:收到上位机"开始录音"命令后,ADC 以 16 kHz 采样率持续采集 2~3 路麦克风并发送。

一句话总结:SPP 方式 = 关闭 TWS/BLE/A2DP + 打开 SPP/在线调试 + 常驻两个采集任务 + 增加约 11KB 发送缓冲。这也是为什么它必须"独占"------TWS 双耳互联与 SPP 调试通道会争抢蓝牙资源。

5.2 UART 方式(变化最小)

  • 不关闭 TWS/BLE/A2DP,不创建采集任务;
  • 仅打开 UART1(占用 PCM_UART1_TX_PORT/RX_PORT 引脚,波特率 PCM_UART1_BAUDRATE),并启用 DMA 发送(uartPcmSender.c:56-71);
  • 锁定系统时钟到最高频率(clock_lock("sys", clk_get_max_frequency()),uartPcmSender.c:74);
  • 打开 AEC 串口导出开关 CONST_AEC_EXPORT = 1(audio_cvp.c:78)。

5.3 SD 方式

  • 不关闭 TWS/BLE/A2DP,但会创建采集任务与写卡任务;
  • 开机即给 SD 卡供电(RT9193,PB3 引脚)并打开 SD 设备(audio_capture.c:380-383);
  • 开机自动开始录音(audio_capture.c:827-829),无需上位机;
  • 数据直接写 SD 卡扇区(起始扇区 50000)。

5.4 三种方式"软件形态"对比

变化项 UART SPP SD
关闭 TWS/BLE/A2DP ❌ ✅ ❌
打开 SPP / 在线调试 ❌ ✅ ❌
新增采集/导出任务 ❌ ✅(2 个) ✅(2 个)
麦克风持续采集 由 AEC 内部驱动 上位机命令驱动 开机自动
额外硬件/外设 UART1 + DMA 蓝牙 SPP SD 卡 + 供电
内存增长 几乎无 约 +11KB 发送缓冲 SD 写缓冲

六、关键源码文件索引

文件 作用
apps/earphone/include/app_config.h:607-616 导出方式选择宏(总开关)
apps/earphone/include/app_config.h:618-626 SPP 导出的麦克风通道配置
apps/earphone/include/app_config.h:842-886 SPP 方式下 TWS/BLE/A2DP/在线调试配置
apps/earphone/mode/bt/earphone.c:472-480 开机入口:关角色切换 + 采集初始化
apps/earphone/app_main.c:88-91 采集/导出任务注册表
audio/common/online_debug/audio_capture.c 采集任务、ADC 回调、SD 写卡、SPP 发送(核心)
audio/CVP/audio_cvp_online.c:658-803 SPP 命令解析 + 数据发送 + 导出初始化
apps/common/third_party_profile/jieli/online_db/spp_online_db.c online_db 的 SPP 通道收发
apps/common/third_party_profile/jieli/online_db/online_db_deal.c online_db 协议层(打包/缓冲/应答)
apps/common/third_party_profile/multi_protocol_main.c:378-380 online_spp_init() 调用点
audio/common/audio_general.c:108-112 UART 导出初始化入口
audio/CVP/audio_cvp.c:76-81 UART 导出:AEC 串口导出开关
audio/common/uartPcmSender.c

七、蓝牙串口SPP音频录制功能调试

1、打开工具,选择"音频录制",然后"确定"。

2、进入蓝牙连接界面,操作如下(如果有两个蓝牙标准串口,一个不行,则换另一个)

3、 进入录制界面


制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!

相关推荐
_YouziTech_1 小时前
【STM32+MAX98357】MP3转PCM存入Flash并控制音频输出
stm32·单片机·音视频·内存·pcm·i2s·wav
ysu_03141 小时前
【C++ 进阶之路】(二)从内联函数到类与对象:深挖内存对齐、this 指针与三大默认成员函数
开发语言·数据结构·c++·笔记·面向对象·this
沐欣工作室_lvyiyi1 小时前
基于STM32的老人跌倒与防走失报警系统设计与实现(论文+源码)
stm32·单片机·嵌入式硬件
叼烟扛炮1 小时前
Linux 学习笔记(三十二):线程控制——创建、等待与分离
linux·运维·服务器·笔记·学习·1024程序员节
yangmu32031 小时前
万字长文拆解:AI视频生成(SVD/Sora类)的底层技术原理与实现路径
人工智能·音视频
我叫洋洋1 小时前
Allegro 画布“垃圾显示“清理指南:把板子调成干净工作台(实战总结)
c语言·c++·单片机·嵌入式硬件·eda·嘉立创
xiangw@GZ1 小时前
SPI Flash 接口模式与 XIP 技术详解
单片机·嵌入式硬件
Nuanyt1 小时前
LeetCode Hot 100 · 双指针 3 题 Java
java·笔记·算法·leetcode·双指针
沐欣工作室_lvyiyi1 小时前
基于STM32的人体健康检测设计(论文+源码)
stm32·单片机·嵌入式硬件