杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • 仓库概览
    • 运行环境与 SDK 集成
    • 工程结构与目录导航
  • 核心 SDK 架构

    • SDK 库体系与模块划分
    • 蓝牙连接与 RCSP 协议
    • 广播包解析与设备认证
    • 日志助手与调试支持
  • 设备功能模块

    • OTA 固件升级
    • 表盘管理与自定义表盘
    • 图像转换工具
    • 资源打包
    • 音频编解码
    • 健康与运动数据同步
    • 消息通知与实用设备功能
  • 宜动健康示例应用

    • 应用架构与页面导航
    • 健康界面与数据可视化
    • 设备连接与数据同步
    • 登录注册与用户中心
    • AI 云服务与语音交互
    • 本地数据库与持久化
    • 多语言国际化
  • 测试与调试

    • SDKTestHelper 功能测试工具
    • 音频编解码示例工程
    • 调试技巧与问题排查
  • 文档与资源

    • 在线文档与版本历史
    • 第三方框架与依赖管理

资源打包

本文档介绍 iOS-JL_Health 项目中语音资源打包能力的完整实现,涵盖 JLPackageResKit 框架中资源打包(JLPackageSourceMgr)、提示音打包与解析(JLVoicePackageManager)、PCM 转 WTS 编码(JLPcmToWts)以及设备端提示音替换集成(JLTipsSoundReplaceMgr)等模块。

Purpose and Scope

本页面以「资源打包」为边界,端到端说明 JLPackageResKit 工具库的打包机制:

  • 资源合并打包:将多个二进制文件(.bin/.res)通过 JLPackageSourceMgr makePks: 合并为单一资源包;
  • 提示音打包与解析:将多个 .wts 语音文件打包为 tone.cfg,并可反向解析;同时定义与设备交互的 JLVoiceReplaceInfo 数据结构;
  • PCM 转 WTS 编码:JLPcmToWts 单例提供 PCM→WTS 转换,支持码率、采样率、VAD 阈值与编码策略配置;
  • 设备端提示音替换流程:通过 JLTipsSoundReplaceMgr 查询设备支持能力、获取已有提示音信息、下发新提示音。

以下内容不属于本页面范围,请参见对应目录页:设备 BLE 通信层(JL_ManagerM、JL_BLEMultiple 的连接与命令机制)、具体业务页面(如闹钟、来电提示音 UI)、AI 云服务(语音识别/合成)。

概述

在 JL 系列健康/音频硬件生态中,设备固件内预置的提示音(如开机音、连接音、闹钟音)由语音资源文件组成。开发者常需在 App 侧完成两类工作:

  1. 资源归一化:把多个独立的二进制资源文件合并成一个整体包,便于一次性传输、校验和写入;
  2. 提示音定制:将用户自定义的 .wts 语音文件打包为设备可解析的 tone.cfg 配置,并通过 BLE 下发,实现提示音替换。

JLPackageResKit 正是为此设计的 Objective-C 静态工具框架,以 xcframework 形式随仓库发布(见 libs/JLPackageResKit.xcframework 下的 ios-arm64 与 ios-arm64_x86_64-simulator 两个切片),通过统一头文件 JLPackageResKit.h 暴露全部公共组件。其核心设计思想是:以"打包/解包"对称的 API 形态封装二进制容器格式,让上层业务只需关心"文件 → 数据 → 设备"的语义,而无需关心字节布局。

关键概念

概念说明
资源包(.res / package)JLPackageSourceMgr makePks: 的输出,将多个文件条目合并后的二进制数据,可直接写盘或传输
tone.cfg提示音配置文件,由多个 .wts 语音文件打包而成,设备端据此解析出语音列表
WTS设备支持的语音编码格式,由 JLPcmToWts 从 PCM 转换而来
提示音替换通过 BLE 将自定义语音写入设备,替换固件默认提示音

架构

JLPackageResKit 的模块划分遵循"入口头文件 → 功能管理器 → 数据模型"三层结构:

flowchart TD
    subgraph sg_Entry["入口层 (JLPackageResKit.h)"]
        H["JLPackageResKit.h<br/>统一导入点"]
    end

    subgraph sg_Manager["管理器层 (功能封装)"]
        SrcMgr["JLPackageSourceMgr<br/>资源打包管理器"]
        VoiceMgr["JLVoicePackageManager<br/>提示音打包/解析管理器"]
        PcmWts["JLPcmToWts<br/>PCM→WTS 编码器 (单例)"]
    end

    subgraph sg_Model["数据模型层 (打包/解析载体)"]
        BaseInfo["JLPackageBaseInfo<br/>contentData / fileName"]
        ReplaceInfo["JLVoiceReplaceInfo<br/>version / maxNum / infoArray"]
        TipsVoice["JLTipsVoiceInfo<br/>index / offset / length / fileName / nickName"]
        ToneCfg["JLToneCfgModel<br/>fileName / data"]
    end

    subgraph sg_Device["设备集成层 (JL_BLEKit)"]
        BleMgr["JL_BLEMultiple / JL_ManagerM"]
        TipsReplace["JLTipsSoundReplaceMgr<br/>支持查询 / 信息获取 / 语音下发"]
    end

    H --> SrcMgr
    H --> VoiceMgr
    H --> PcmWts
    SrcMgr --> BaseInfo
    VoiceMgr --> ReplaceInfo
    VoiceMgr --> ToneCfg
    ReplaceInfo --> TipsVoice
    VoiceMgr -->|"tone.cfg 数据"| TipsReplace
    TipsReplace --> BleMgr
    PcmWts -->|"生成 .wts 文件"| VoiceMgr

设计意图说明:

  • 入口层负责聚合导出,业务方只需 #import <JLPackageResKit/JLPackageResKit.h> 即可使用全部能力;
  • 管理器层采用类方法/单例的轻量封装,JLPackageSourceMgr 与 JLVoicePackageManager 均为无状态静态方法(+ 方法),适合离线批量打包工具场景;JLPcmToWts 为单例,内部持有编码器生命周期;
  • 数据模型层是打包格式的抽象:JLPackageBaseInfo 描述通用资源文件条目,JLVoiceReplaceInfo/JLTipsVoiceInfo 描述设备提示音结构,JLToneCfgModel 描述解析后的提示音条目;
  • 设备集成层依赖宿主工程已有的 JL_BLEKit(JL_BLEMultiple 获取当前 JL_ManagerM),实现"打包 → 下发 → 查询"闭环。

框架最低支持 iOS 11.0+、Xcode 12.0+,集成方式为将 JLPackageResKit.framework 添加到 Target → General → Frameworks, Libraries, and Embedded Content 并设置为 Embed & Sign。

资源打包模块(JLPackageSourceMgr)

资源打包模块用于将多个 .bin 等二进制文件合并为一个资源包,输出可直接写入 .res 或任意扩展名文件的数据。该模块由两个类组成:

JLPackageBaseInfo — 文件条目模型

JLPackageBaseInfo 是描述单个待打包文件的基本信息类,定义于头文件 JLPackageSourceMgr.h:

/// 信息
@interface JLPackageBaseInfo : NSObject
@property (nonatomic, strong) NSData *contentData; // 文件内容的二进制数据
@property (nonatomic, copy) NSString *fileName;    // 原始文件名(含扩展名)
@end

Source: JLPackageSourceMgr.h

设计意图:fileName 保留原始文件名(含扩展名)而非仅存数据,使打包后的资源包具备自描述能力——解包方(无论是本库的解析逻辑还是设备端固件)可以依据文件名识别条目用途(如 texture.res、config.res),避免依赖调用方维护外部索引表。

JLPackageSourceMgr — 打包管理器

/// 资源打包类
@interface JLPackageSourceMgr : NSObject

/// 将多个文件信息打包为单个资源包
///   - infos: 文件信息
+(NSData *)makePks:(NSArray<JLPackageBaseInfo *>*)infos;
@end

Source: JLPackageSourceMgr.h

行为说明:

  • 入参 infos 为 JLPackageBaseInfo 数组,每个元素代表一个要打包的文件;
  • 返回值为合并后的 NSData,可直接 writeToFile:atomically: 落盘或经 BLE/网络传输;
  • 方法为类方法,无内部状态,天然线程安全,可并发处理多组打包任务。

为什么是类方法而非实例方法? 打包操作是纯函数式转换(输入文件数组 → 输出二进制数据),不依赖实例状态。采用类方法消除了实例管理与生命周期问题,也便于在脚本化批量处理(如 CI 资源预处理)中直接调用。

典型打包控制流

flowchart LR
    A["读取文件<br/>NSData dataWithContentsOfFile:"] --> B["构造 JLPackageBaseInfo<br/>设置 fileName / contentData"]
    B --> C["批量构造条目数组<br/>infos"]
    C --> D["JLPackageSourceMgr makePks:infos"]
    D --> E["输出合并后 NSData"]
    E --> F["写入 .res 文件<br/>或传输给设备"]

提示音打包与解析模块(JLVoicePackageManager)

提示音模块是资源打包能力的核心业务形态:将多个 .wts 语音文件打包为设备可解析的 tone.cfg,同时提供反向解析能力,用于读取设备中已有提示音配置。

JLToneCfgModel — 解析结果模型

JLToneCfgModel 描述单个提示音配置项,含两个字段:fileName(提示音文件名)与 data(文件数据)。它是 parsePks: 方法的返回元素类型,供上层遍历获取每个提示音的元信息与数据。

JLVoiceReplaceInfo — 设备提示音替换结构

JLVoiceReplaceInfo 定义提示音替换相关的容器结构,包含:

字段类型说明
version整型结构版本号,用于设备端兼容判断
blockSize整型保留字段(文档标注为保留,打包时通常置 0)
maxNum整型最大语音数量,即本包内提示音条数
fileNameNSString配置文件名(如 tone.cfg)
infoArray数组提示音详细列表,元素为 JLTipsVoiceInfo

JLTipsVoiceInfo 描述单个提示音的定位信息:

字段类型说明
index整型语音索引,标识设备上的槽位
offset整型偏移地址(数据在包内的偏移)
length整型文件长度
fileNameNSString原始文件名
nickNameNSString昵称(展示用名称)

设计意图:JLVoiceReplaceInfo 采用"容器头 + 条目列表"结构,offset/length 构成对语音数据的定位索引,设备端无需一次性加载全部语音即可按需读取指定 index 的语音——这对内存受限的嵌入式设备至关重要。version 字段则为未来格式演进预留了兼容通道。

JLVoicePackageManager — 打包/解析入口

@interface JLVoicePackageManager : NSObject

// 将多个 .wts 文件打包成 tone.cfg
+ (NSData *)makePks:(NSArray *)paths FileNames:(NSArray *)names Info:(JLVoiceReplaceInfo *)info;

// 解包 tone.cfg 获取语音列表
+ (NSArray<JLToneCfgModel *> *)parsePks:(NSData *)data;
@end

Source: JLVoicePackageManager.h

打包流程(makePks:FileNames:Info:):

  1. 调用方提供 .wts 文件路径数组(paths)、对应文件名数组(names)以及承载版本/数量等元信息的 JLVoiceReplaceInfo;
  2. 管理器读取各文件数据,按 JLVoiceReplaceInfo 的结构填充 infoArray(计算每个条目的 index、offset、length);
  3. 序列化为 tone.cfg 二进制数据返回。

解析流程(parsePks:):

  1. 输入 tone.cfg 的 NSData;
  2. 按同样的二进制布局反向拆解,还原出 NSArray<JLToneCfgModel *>;
  3. 每个 JLToneCfgModel 携带 fileName 与 data,上层可直接用于展示或再次下发。

打包与解析共用同一套格式定义,保证"打包产物可被本库解析、可被设备固件解析"的一致性。

PCM 转 WTS 模块(JLPcmToWts)

JLPcmToWts 提供 PCM 音频到设备专用 WTS 格式的编码能力,是提示音打包链路的"上游"——用户提供的音频需先转为 .wts,才能进入 JLVoicePackageManager 打包为 tone.cfg。

接口形态

@interface JLPcmToWts : NSObject
+ (instancetype)share;                 // 获取单例对象
- (void)pcmToWts:...;                  // 将 PCM 文件编码为 WTS 文件
@end

Source: JLPackageResKit.h(JLPcmToWts.h 由统一入口导入)

编码参数

参数说明
speechInFileName输入 PCM 文件路径
bitOutFileName输出 WTS 文件路径
targetRate目标码率
sr_in输入采样率
vadthrVAD(语音活动检测)阈值
usesavemodef编码策略:0 = 位流优先,1 = 质量优先

设计意图:编码参数全部显式暴露而非硬编码,是因为不同设备型号的存储容量与解码能力差异较大——低端设备适合低码率位流优先(usesavemodef = 0)以缩小文件体积,高端设备可用质量优先(usesavemodef = 1)换取更佳听感。vadthr 则用于静音段检测,剔除提示音前后的静音噪声,进一步压缩包体。

音频预处理链路

flowchart TD
    A["原始音频 (PCM)"] --> B["JLPcmToWts share 单例"]
    B --> C{"pcmToWts 编码"}
    C -->|"usesavemodef=0 位流优先"| D["小体积 WTS"]
    C -->|"usesavemodef=1 质量优先"| E["高质量 WTS"]
    D --> F["JLVoicePackageManager makePks"]
    E --> F
    F --> G["tone.cfg 资源包"]
    G --> H["JLTipsSoundReplaceMgr 下发设备"]

设备端提示音替换集成(JLTipsSoundReplaceMgr)

打包后的 tone.cfg 最终要下发到设备。该能力由 JL_BLEKit 侧的管理器 JLTipsSoundReplaceMgr(单例 share)承担,与 JLPackageResKit 的数据结构无缝衔接:

  1. 能力查询:isSupportTipsVoiceReplace:result: 异步回调 BOOL,判断当前连接设备是否支持提示音替换;
  2. 信息获取:voicesReplaceGetVoiceInfo:Result: 从设备读取已有提示音配置,返回 NSData 后可交给 [JLVoiceReplaceInfo parsePks:data] 解析出 infoArray,逐条读取 index/fileName/nickName;
  3. 语音下发:将本地打包好的提示音数据经 BLE 命令写入设备,替换对应 index 槽位。

设备管理器实例通过 [[JL_BLEMultiple share] getCurrentManager] 获取,与仓库中 BLE 通信层(JL_ManagerM)解耦——资源打包层只产出数据,不关心传输细节。

核心流程

端到端提示音定制时序

sequenceDiagram
    participant App as App 业务层
    participant Pcm as JLPcmToWts
    participant Vpkg as JLVoicePackageManager
    participant Rep as JLTipsSoundReplaceMgr
    participant Ble as JL_BLEMultiple / JL_ManagerM
    participant Dev as 设备固件

    App->>Pcm: pcmToWts (码率/采样率/VAD/策略)
    Pcm-->>App: 生成 .wts 文件
    App->>Vpkg: makePks:FileNames:Info: (paths, names, JLVoiceReplaceInfo)
    Vpkg-->>App: tone.cfg NSData
    App->>Ble: getCurrentManager 获取设备管理器
    App->>Rep: isSupportTipsVoiceReplace 能力查询
    Rep-->>App: support = YES
    App->>Rep: voicesReplaceGetVoiceInfo 读取已有配置
    Rep->>Ble: BLE 读取命令
    Ble-->>Rep: 设备返回 NSData
    Rep-->>App: data
    App->>App: [JLVoiceReplaceInfo parsePks:data] 解析
    App->>Rep: 下发打包后的提示音数据
    Rep->>Ble: BLE 写入命令
    Ble->>Dev: 写入 tone.cfg / 语音数据
    Dev-->>App: 替换完成

流程要点:

  • 打包与解析严格对称:makePks: 产生的 tone.cfg 既可由本库 parsePks: 解析(用于调试/预览),也可由设备固件解析(用于实际替换);
  • 所有设备交互均为异步回调风格(result: / Result: block),符合 BLE 命令的串行应答模型;
  • index 是设备端槽位标识,App 侧需先查询已有配置(voicesReplaceGetVoiceInfo)再决定覆盖哪个槽位,避免误替换。

使用示例

以下示例均提取自仓库文档 docs/JLPackageResKit.md。

示例 1:通用资源打包(.bin → .res)

// 准备要打包的文件
JLPackageBaseInfo *file1 = [JLPackageBaseInfo new];
file1.fileName = @"texture.res";
file1.contentData = [NSData dataWithContentsOfFile:path1];

JLPackageBaseInfo *file2 = [JLPackageBaseInfo new];
file2.fileName = @"config.res";
file2.contentData = [NSData dataWithContentsOfFile:path2];

// 执行打包
NSData *packageData = [JLPackageSourceMgr makePks:@[file1, file2]];

// 写入文件
[packageData writeToFile:@"/path/to/package" atomically:YES];

Source: JLPackageResKit.md

说明:先逐文件读取为 NSData 并设置 fileName,再一次性打包。该模式适合资源管理器(如图片/配置/字体)的批量归并。

示例 2:提示音打包为 tone.cfg

//1. 打包多个 .wts 文件为 tone.cfg
NSArray *paths = @[
    @"/Users/xxx/voice1.wts",
    @"/Users/xxx/voice2.wts"
];
NSArray *names = @[
    @"boot.wts",
    @"connect.wts"
];

JLVoiceReplaceInfo *info = [[JLVoiceReplaceInfo alloc] init];
info.version = 1;
info.fileName = @"tone.cfg";
info.maxNum = 2;
info.blockSize = 0;

NSData *cfgData = [JLVoicePackageManager makePks:paths FileNames:names Info:info];

// 将生成的 tone.cfg 写入本地
[cfgData writeToFile:@"/Users/xxx/tone.cfg" atomically:YES];

Source: JLPackageResKit.md

说明:paths 与 names 数组需一一对应;info.maxNum 应等于语音条数(此处为 2),version 用于设备端格式兼容判断。

示例 3:解析 tone.cfg

//2. 解析 tone.cfg 文件
NSData *data = [NSData dataWithContentsOfFile:@"/Users/xxx/tone.cfg"];
NSArray<JLToneCfgModel *> *tones = [JLVoicePackageManager parsePks:data];

for (JLToneCfgModel *model in tones) {
    NSLog(@"Tone File: %@, Data Length: %lu", model.fileName, (unsigned long)model.data.length);
}

Source: JLPackageResKit.md

示例 4:查询设备支持并获取已有提示音

//3. 查询设备是否支持提示音替换
//这里假设是通过某个初始化的类获取到了 manager 类,具体实现方式需要用户根据实际调整
JL_ManagerM *manager = [[JL_BLEMultiple share] getCurrentManager];

[[JLTipsSoundReplaceMgr share] isSupportTipsVoiceReplace:manager result:^(BOOL support) {
    if (support) {
        NSLog(@"支持提示音替换");
    } else {
        NSLog(@"不支持提示音替换");
    }
}];

//4. 获取设备中已有的提示音信息
[[JLTipsSoundReplaceMgr share] voicesReplaceGetVoiceInfo:manager Result:^(JL_CMDStatus status, NSData * _Nullable data) {
    if (status == JL_CMDStatusSuccess && data) {
        JLVoiceReplaceInfo *info = [JLVoiceReplaceInfo parsePks:data];
        for (JLTipsVoiceInfo *item in info.infoArray) {
            NSLog(@"Index: %d, Name: %@ (%@)", item.index, item.fileName, item.nickName);
        }
    } else {
        NSLog(@"获取失败");
    }
}];

Source: JLPackageResKit.md

说明:设备返回的原始 NSData 复用 JLVoiceReplaceInfo parsePks: 解析,说明打包格式同时服务于"文件 ↔ 数据"与"设备 ↔ App"两条链路。

API 参考

JLPackageSourceMgr

方法签名说明
makePks:+(NSData *)makePks:(NSArray<JLPackageBaseInfo *> *)infos将多个文件条目打包为单个资源包。参数 infos 为待打包文件信息数组;返回打包后的 NSData,可直接写入 .res 文件

JLPackageBaseInfo

属性类型说明
contentDataNSData *文件内容的二进制数据表示
fileNameNSString *原始文件名(包含扩展名)

JLVoicePackageManager

方法签名说明
makePks:FileNames:Info:+(NSData *)makePks:(NSArray *)paths FileNames:(NSArray *)names Info:(JLVoiceReplaceInfo *)info将多个 .wts 文件打包成 tone.cfg。paths 为文件路径数组,names 为对应文件名数组,info 承载版本/数量等元信息
parsePks:+(NSArray<JLToneCfgModel *> *)parsePks:(NSData *)data解包 tone.cfg 数据,返回语音条目列表

JLPcmToWts

方法签名说明
share+(instancetype)share获取单例对象
pcmToWts:...-(void)pcmToWts:...将 PCM 文件编码为 WTS 文件;参数覆盖输入/输出路径、目标码率、采样率、VAD 阈值、编码策略(0 位流优先 / 1 质量优先)

JLVoiceReplaceInfo

方法/属性说明
parsePks:从设备返回或本地文件解析出 JLVoiceReplaceInfo
version / blockSize / maxNum / fileName / infoArray容器头元信息与条目列表(元素为 JLTipsVoiceInfo)

设备侧(JL_BLEKit 集成)

方法说明
isSupportTipsVoiceReplace:result:查询设备是否支持提示音替换,异步回调 BOOL
voicesReplaceGetVoiceInfo:Result:读取设备已有提示音配置,回调 JL_CMDStatus 与原始 NSData

故障模式与边界情况

  • 文件读取失败:NSData dataWithContentsOfFile: 返回 nil 时,条目数据为空,打包结果可能不完整。建议打包前校验每个文件的读取结果与 fileName 非空。
  • 路径与名称数组不一致:makePks:FileNames:Info: 要求 paths 与 names 一一对应;若数组长度不等,可能导致条目错位或越界,调用方需保证长度一致。
  • maxNum 与实际条数不符:JLVoiceReplaceInfo.maxNum 应等于实际语音数量;设备端可能按 maxNum 分配槽位,超出部分可能被忽略。
  • 设备不支持提示音替换:isSupportTipsVoiceReplace:result: 返回 NO 时,后续获取/下发命令应被跳过,避免无效 BLE 命令。
  • BLE 命令失败:voicesReplaceGetVoiceInfo:Result: 回调中 status != JL_CMDStatusSuccess 或 data == nil 时,应按"获取失败"处理(示例中即如此分支)。
  • 并发打包:JLPackageSourceMgr/JLVoicePackageManager 为无状态类方法,可安全并发;但 JLPcmToWts 为单例,编码过程若被多线程并发调用,需外部加锁或串行化,避免编码器内部状态竞争。

性能与运维考虑

  • 编码是耗时操作:pcmToWts 涉及 PCM 读取、VAD 检测与位流编码,建议在后台队列执行,避免阻塞主线程;批量转码时逐文件串行或受限并发,防止内存峰值。
  • 打包数据量:makePks: 会将全部文件数据载入内存合并,超大资源集合建议分批打包或控制单包体积,防止内存暴涨。
  • 调试技巧:利用打包/解析对称性——对 makePks: 产物立即执行 parsePks:,可离线验证格式正确性,无需真实设备即可定位问题。
  • 版本管理:JLVoiceReplaceInfo.version 与 JLTipsVoiceInfo 的布局一旦发布即成为设备端契约,后续变更需增加版本分支,保证旧设备兼容。

扩展点

  • 自定义资源包格式:JLPackageBaseInfo + JLPackageSourceMgr 是通用容器,可扩展用于任意二进制资源(图片、字体、配置文件)的归并传输;
  • 新编码策略:pcmToWts 的 usesavemodef 位流/质量策略为编码器开关,未来可扩展更多档位而不改变调用方接口;
  • 多设备下发:JLTipsSoundReplaceMgr 与 JL_ManagerM 解耦,接入不同设备型号时仅需调整 BLE 命令适配层,打包层无需改动。

相关链接

  • JLPackageResKit.md(框架使用文档)
  • JLPackageSourceMgr.h(资源打包管理器头文件)
  • JLVoicePackageManager.h(提示音打包/解析管理器头文件)
  • JLPackageResKit.h(框架统一入口头文件)
Prev
图像转换工具
Next
音频编解码