杰理 SDK 文档中心
首页
首页
  • 项目概述

    • 项目简介与核心能力
    • 运行环境与SDK版本
  • 快速开始

    • 工程导入与依赖配置
    • 权限配置与示例运行
  • 平台架构

    • SDK分层架构与RCSP协议
    • 蓝牙连接库
    • 健康SDK核心库 JL_Watch
    • 健康服务器与云端服务
  • 健康与运动数据

    • 健康数据同步
    • 运动数据同步
    • 本地数据持久化
  • 设备管理功能

    • 表盘管理
    • 闹钟与健康提醒
    • 消息与联系人同步
    • 天气同步
    • 设备查找
    • 支付宝集成
  • 传输与媒体处理

    • 文件传输与文件管理
    • 音乐传输与播放控制
    • 图像转换库
    • 音频编解码与解密
  • OTA 升级

    • 固件空中升级流程
    • 4G模块与差分升级
  • AI 能力

    • AI表盘与云服务
    • AI语音助手
  • 示例应用

    • HealthAide 健康助手应用
    • WatchTestTool 测试工具
  • 开发者指南

    • 自定义命令扩展
    • 调试技巧与问题排查
    • 版本历史与兼容性

音频编解码与解密

本文档介绍 Android-JL_Health 项目中音频编解码与解密能力:由杰理官方 jl_audio_decode 库(AAR)提供的 OPUS 音频解码、杰理私有 OPUS 格式与标准 OPUS 格式的兼容转换,以及其在 AI 语音链路(手表录音 → RCSP 回传 → 解码 PCM → 语音识别)中的真实集成方式。

Purpose and Scope

本页覆盖:

  • jl_audio_decode 音频编解码库的构成(AAR、开发资料包、测试工具)与版本演进
  • 核心 API OpusManager 及其回调接口 OnDecodeStreamCallback 的用法
  • 手表端录音数据经 RCSP 协议回传后,应用侧解码为 PCM 流的完整控制流
  • 杰理私有 OPUS 与标准 OPUS 格式的兼容/解密处理背景

本页不涉及:RCSP 协议本身、录音指令的底层封装(见录音相关页面)、语音识别/NLP/TTS 业务(属于 AI 语音能力页面)。库内部实现以 AAR 二进制形式提供,源码不在本仓库内,相关部分会明确标注"实现细节未在仓库源码中体现"。

Overview

在 AI 语音交互场景中,手表(Watch)采集语音后以 OPUS 编码的音频数据通过 RCSP 协议回传到手机端。手机端 HealthAide 应用需要把 OPUS 数据解码为 PCM 裸流,才能交给语音识别(IAT)、语义理解(NLP)与 TTS 播放链路使用。

jl_audio_decode 是杰理科技提供的音频编解码库,以 jl_audio_decode_V2.1.0_20012-release.aar 形式分发。根据开发资料 ReadMe.txt 的更新说明,V2.1.0 增加了标准 OPUS 格式与杰理 OPUS 格式的兼容——这是"解密"语义的来源:杰理手表回传的 OPUS 数据带有厂商私有封装/私有头,库负责识别并兼容两种格式,对应用层透明地输出统一格式的音频流。

库的关键使用入口为 com.jieli.jl_audio_decode.opus.OpusManager:

  • startDecodeStream(OnDecodeStreamCallback) 启动解码流,解码结果通过回调逐段返回
  • 回调包含 onStart / onDecodeStream(byte[]) / onComplete(String) / onError(int, String) 四类事件
  • 异常类型统一为 com.jieli.jl_audio_decode.exceptions.OpusException

仓库中的 AIRecordWrapper(AI 录音实现)是库在真实产品链路中的唯一调用方,也是本文档的主要代码依据。

Architecture

下图展示了音频解码能力在整条 AI 语音链路中的位置与数据流向(基于 AIRecordWrapper 与 RCSP 录音回调的真实接线):

flowchart TD
    subgraph sg_Watch["手表端"]
        Watch["Watch 采集语音"]
        Encoder["OPUS 编码器(杰理私有格式)"]
    end

    subgraph sg_Phone["手机端 HealthAide"]
        subgraph sg_RCSP["RCSP 协议层"]
            RecordOp["RecordOpImpl<br/>录音控制"]
            StateCB["OnRecordStateCallback<br/>录音状态回调"]
        end

        subgraph sg_Codec["音频编解码(本页主题)"]
            OpusMgr["OpusManager<br/>jl_audio_decode 库"]
            DecCB["OnDecodeStreamCallback<br/>解码回调"]
        end

        subgraph sg_AI["AI 语音链路"]
            IAT["语音识别 IAT"]
            NLP["语义理解 NLP"]
            TTS["TTS 播放"]
            Listeners["AIRecordWrapperListener"]
        end
    end

    Watch -->|"RCSP 录音指令"| RecordOp
    RecordOp -->|"VOICE_TYPE_OPUS"| StateCB
    StateCB -->|"RECORD_STATE_START"| OpusMgr
    StateCB -->|"RECORD_STATE_WORKING 回传 OPUS 数据"| OpusMgr
    OpusMgr -->|"解码为 PCM 流"| DecCB
    DecCB -->|"onDecodeStream(byte[])"| Listeners
    Listeners --> IAT
    Listeners --> NLP
    Listeners --> TTS
    Watch --> Encoder
    Encoder -->|"OPUS 私有格式流"| StateCB

各节点职责说明:

  • RecordOpImpl:RCSP 录音功能实现,负责向手表下发开始/停止录音指令,见 AIRecordWrapper 中 mRecordOp 字段(AIRecordWrapper.java)。
  • OnRecordStateCallback:监听录音状态机(RECORD_STATE_START / RECORD_STATE_WORKING 等),是解码链路的触发源。
  • OpusManager:编解码库门面,startDecodeStream 消费 OPUS 数据并产出 PCM。
  • OnDecodeStreamCallback:解码结果回调,把 PCM 字节流转发给 AIRecordWrapperListener,再进入 AI 识别链路。

设计意图:把编解码这一"脏活"隔离在 RCSP 业务之外,AIRecordWrapper 只关心"开始解码/收到 PCM/解码完成/解码出错"四个事件,语音识别等上层无需感知 OPUS 私有格式与解密细节。

库的构成与版本

分发形态

jl_audio_decode 以预编译 AAR 二进制分发,仓库中存在于多处(同一产物多副本):

路径说明
libs/JL/jl_audio_decode_V2.1.0_20012-release.aar根目录公共库目录
code/app/HealthAide_V1.1.0_SDK_V1.14.0/app/libs/jl_audio_decode_V2.1.0_20012-release.aar正式 App 工程
code/tool/WatchTestTool_V0.9.0_SDK_V1.14.0/app/libs/jl_audio_decode_V2.1.0_20012-release.aar手表测试工具工程
code/杰理音频编解码库开发资料_V2.1.0_Android/libs/jl_audio_decode_V2.1.0_20010-release.aar开发资料包(旧版本 20010)
code/杰理音频编解码库开发资料_V2.1.0_Android/libs/jl_audio_decode_V2.1.0_20012-release.aar开发资料包(当前版本 20012)

开发资料包目录结构(ReadMe.txt):

├ apk        --- 测试APK(OpusTestTool_V1.6.0_10600-debug.apk + 测试说明.txt)
├ code       --- 测试项目代码(JL_OpusDemo_V1.6.0_SDK_V2.1.0.zip)
├ doc        --- 开发资料(杰理音频编码库开发说明.pdf)
├ libs       --- 核心库(jl_audio_decode AAR)
└ ReadMe.txt --- 开发前必读

版本演进与"解密"语义

开发资料包更新说明(ReadMe.txt)明确指出 V2.1.0 的核心更新:

增加标准OPUS格式和杰理OPUS格式的兼容

这意味着:杰理手表设备输出的 OPUS 流带有厂商私有封装(私有头/私有格式),与标准 OPUS(RFC 6716 流)不直接互通。库在解码层面对两种格式做兼容识别与归一化,对上层调用方表现为统一的解码接口——这正是本页"编解码与解密"中"解密"的实际含义:去除/识别私有格式封装,还原出可被标准解码器或上层业务消费的音频数据。

版本号说明:AAR 命名中 V2.1.0 为库版本,20012 / 20010 为构建序号(Build Number),仓库中主工程统一使用 20012。

核心 API:OpusManager

库的公开 API 从仓库内真实调用点(AIRecordWrapper)可确认如下(AAR 为二进制,完整接口以官方开发说明 PDF 为准,本文只记录源码中已证实的部分):

OpusManager.startDecodeStream(OnDecodeStreamCallback callback)

  • 作用:启动 OPUS 解码流,将后续回传的 OPUS 数据实时解码为 PCM
  • 回调对象在录音开始时注册(RECORD_STATE_START 分支)
  • 返回:void(异步回调模式)
  • 抛出:解码异常统一封装为 OpusException(com.jieli.jl_audio_decode.exceptions.OpusException)

OnDecodeStreamCallback 回调接口

方法触发时机说明
onStart()解码流启动上层据此通知 UI/业务"解码开始"
onDecodeStream(byte[] bytes)每收到一段解码结果bytes 为解码后的音频数据(PCM),逐段回调
onComplete(String s)解码完成s 为输出信息(如输出文件路径),上层据此做收尾
onError(int i, String s)解码异常i 为错误码,s 为错误描述

应用侧集成:AIRecordWrapper 控制流

AIRecordWrapper(AI 录音实现)是解码库在仓库中的唯一消费方(AIRecordWrapper.java)。其关键字段:

  • mRecordOp(RecordOpImpl):RCSP 录音实现,控制手表录音起止
  • mOpusManager(OpusManager):音频解码器
  • mRecordParam(RecordParam):录音参数,含 VoiceType(VOICE_TYPE_OPUS / VOICE_TYPE_SPEEX / VOICE_TYPE_PCM)
  • mListeners(ArrayList<AIRecordWrapperListener>):解码结果的上层分发对象

录音状态驱动的解码生命周期

录音状态通过 OnRecordStateCallback 回调驱动(AIRecordWrapper.java):

  1. RECORD_STATE_START:保存 RecordParam,启动录音超时计时;随后按 VoiceType 分支:
    • VOICE_TYPE_OPUS:调用 mOpusManager.startDecodeStream(...) 注册解码回调,进入解码流程
    • VOICE_TYPE_SPEEX:分支为空(预留,当前未实现)
    • VOICE_TYPE_PCM:无需解码,直接回调 onDecodeStart() 通知上层
  2. RECORD_STATE_WORKING:手表持续回传录音数据(OPUS 帧),数据进入 OpusManager 解码,解码结果经 onDecodeStream(byte[]) 逐段转交 AIRecordWrapperListener(供 IAT 等消费)。
  3. 解码完成/停止:onComplete(String) 上报完成原因与输出;异常路径走 onError(int, String)。

断连兜底

OnWatchCallback.onConnectStateChange 中处理了 CONNECTION_DISCONNECT / CONNECTION_FAILED:一旦目标设备断连,立即复位 AI 相关标志位(isNeedPlayTTS、isNeedAsyncIat、isNeedAsyncNlp),调用 stopDecodeStream() 停止解码,并通知 mRecordOp.stopRecord(...) 终止录音(AIRecordWrapper.java)。这保证了解码状态机在设备异常断开时不会悬挂。

异步与线程模型

  • 解码回调在库内部线程异步触发,AIRecordWrapper 通过 Handler(Looper) 处理超时等主线程任务,避免阻塞解码。
  • onDecodeStream 高频逐段回调,源码注释 "解码数据长度: %d" 表明每段为独立字节数组,上层需自行做流式缓冲(如拼接给 IAT 分片)。

Core Flow:一次 AI 录音的端到端解码流程

sequenceDiagram
    participant App as HealthAide
    participant W as Watch(手表)
    participant RO as RecordOpImpl
    participant CB as OnRecordStateCallback
    participant OM as OpusManager
    participant DC as OnDecodeStreamCallback
    participant AI as 语音识别链路

    App->>RO: 下发开始录音指令
    RO->>W: RCSP 录音指令
    W-->>CB: RECORD_STATE_START(含 VoiceType=OPUS)
    CB->>OM: startDecodeStream(callback)
    OM-->>DC: onStart()
    DC->>AI: onDecodeStart()
    loop 录音数据回传
        W-->>CB: RECORD_STATE_WORKING + OPUS 数据帧
        CB->>OM: 回传 OPUS 数据
        OM->>OM: 私有格式识别/兼容 + 解码
        OM-->>DC: onDecodeStream(PCM byte[])
        DC->>AI: 分发 PCM 流(IAT/NLP/TTS)
    end
    App->>RO: 停止录音
    W-->>CB: RECORD_STATE_STOP/COMPLETE
    OM-->>DC: onComplete(输出信息)
    DC->>AI: onDecodeComplete(reason, output)
    alt 解码异常
        OM-->>DC: onError(code, message)
        DC->>AI: onDecodeError(code, message)
    else 设备断连
        W-->>CB: CONNECTION_DISCONNECT
        CB->>OM: stopDecodeStream()
        CB->>RO: stopRecord()
    end

关键设计点:

  1. 解码触发与录音状态绑定:只有 VoiceType == VOICE_TYPE_OPUS 才启动解码器,PCM 直通、SPEEX 预留,三者互不干扰(AIRecordWrapper.java)。
  2. 逐段流式回调:解码不等待整段录音结束,而是随录音数据持续产出 PCM,保证语音识别低延迟。
  3. 断连即停:设备断开是最高优先级事件,先停解码、再停录音,避免回调风暴与资源泄漏。

Usage Examples

示例 1:启动 OPUS 解码流并注册回调

以下代码摘自 AIRecordWrapper 在录音开始状态(RECORD_STATE_START)下的解码启动逻辑,展示了 OpusManager 的标准使用方式:

if (getVoiceType() == RecordParam.VOICE_TYPE_OPUS) {//opus
    if (mOpusManager != null) {
        mOpusManager.startDecodeStream(new OnDecodeStreamCallback() {
            @Override
            public void onDecodeStream(byte[] bytes) {
                JL_Log.d(TAG, "onDecodeStream", CalendarUtil.formatString("解码数据长度: %d", bytes.length));
                for (AIRecordWrapperListener listener : mListeners) {
                    listener.onDecodeStream(bytes);
                }
            }

            @Override
            public void onStart() {
                JL_Log.d(TAG, "onStart", "开始编码--------->>>");
                for (AIRecordWrapperListener listener : mListeners) {
                    listener.onDecodeStart();
                }
            }

            @Override
            public void onComplete(String s) {
                JL_Log.d(TAG, "onComplete", "编码完成, output : " + s);
                if (mRecordOp != null) {
                    for (AIRecordWrapperListener listener : mListeners) {
                        listener.onDecodeComplete(mRecordOp.getRecordState().getReason(), s);
                    }
                }
            }

            @Override
            public void onError(int i, String s) {
                JL_Log.d(TAG, "onError", "编码异常: " + i + ", " + s);
                for (AIRecordWrapperListener listener : mListeners) {
                    listener.onDecodeError(i, s);
                }
            }
        });
    }
}

源码:AIRecordWrapper.java

要点:回调对象内只做"转发"(for (listener : mListeners)),把库事件广播给上层监听者;日志中 "解码数据长度: %d" 说明每段回调携带独立字节数组,上层需自行缓冲拼接。

示例 2:设备断连时停止解码与录音

@Override
public void onConnectStateChange(BluetoothDevice device, int status) {
    super.onConnectStateChange(device, status);
    switch (status) {
        case StateCode.CONNECTION_DISCONNECT:
        case StateCode.CONNECTION_FAILED:
            if (RcspUtil.deviceEquals(device, WatchManager.getInstance().getTargetDevice())) {
                isNeedPlayTTS = false;
                isNeedAsyncIat = false;
                isNeedAsyncNlp = false;
                stopDecodeStream();
                if (mRecordOp != null) {
                    mRecordOp.stopRecord(device, 1, false, false, false, null);
                }
            }
            break;
    }
}

源码:AIRecordWrapper.java

要点:断连是异步事件,必须显式调用 stopDecodeStream() 终止解码器内部状态,否则下次录音可能残留上一次的上下文(解码器状态污染)。

配置选项

jl_audio_decode 库本身无需配置文件(AAR 内建默认参数)。与解码行为相关的可配置项集中在录音参数 RecordParam 的语音类型选择上,由 AIRecordWrapper 在录音状态回调中读取(AIRecordWrapper.java):

选项类型默认/取值说明
RecordParam.VOICE_TYPE_OPUSintOPUS 录音时使用触发 OpusManager.startDecodeStream() 解码链路
RecordParam.VOICE_TYPE_SPEEXint未启用(分支为空)预留的 SPEEX 解码分支,当前未实现
RecordParam.VOICE_TYPE_PCMintPCM 直通无需解码,直接回调 onDecodeStart()
AIRecordWrapper.isNeedAsyncIatbooleanfalse是否需要同步语音识别(断连时复位)
AIRecordWrapper.isNeedAsyncNlpbooleanfalse是否需要同步语义结果(断连时复位)
AIRecordWrapper.isNeedPlayTTSbooleanfalse是否需要播放 TTS(断连时复位)
MSG_RECORD_TIME_OUTint2录音超时消息类型(Handler 消息)
AAR 版本号-V2.1.0_20012主工程统一使用 20012 构建号

API Reference

以下接口签名依据仓库内实际调用代码整理(AAR 为二进制,字段级细节以官方《杰理音频编码库开发说明.pdf》为准):

OpusManager

库的门面类,包路径 com.jieli.jl_audio_decode.opus。

方法签名(依据调用点推断)说明
startDecodeStreamstartDecodeStream(OnDecodeStreamCallback callback)启动 OPUS 解码流,注册回调;异步输出 PCM
stopDecodeStreamstopDecodeStream()停止解码流,释放解码器内部状态(断连时调用)

OnDecodeStreamCallback

回调接口,包路径 com.jieli.jl_audio_decode.callback。

  • void onStart():解码流启动。
  • void onDecodeStream(byte[] bytes):每段解码结果(PCM),bytes.length 为段长度。
  • void onComplete(String s):解码完成,s 为输出描述(如文件路径)。
  • void onError(int i, String s):解码异常,i 为错误码,s 为错误描述。

OpusException

异常类型,包路径 com.jieli.jl_audio_decode.exceptions。解码失败时由库抛出/上报,AIRecordWrapper 已将其导入(AIRecordWrapper.java),错误码与描述通过 onError(int, String) 透传给上层。

Professional Notes

失败模式与边界情况

  • 设备断连/连接失败:CONNECTION_DISCONNECT 与 CONNECTION_FAILED 均会触发解码停止与录音停止;断连回调只对目标设备(RcspUtil.deviceEquals(device, targetDevice))生效,避免多设备干扰。
  • 解码错误:onError(int i, String s) 携带错误码与描述,AIRecordWrapper 只转发不吞掉,上层 AIRecordWrapperListener.onDecodeError 负责用户提示。
  • 非 OPUS 语音类型:VOICE_TYPE_SPEEX 分支为空实现——若设备配置为 SPEEX 编码,当前版本不会启动解码器,属于已知未完成路径(源码 TODO 注释也表明解码链路"要优化成可以停止处理录音数据")。
  • 解码状态残留:断连路径必须 stopDecodeStream() 后再 stopRecord(),顺序颠倒可能导致解码器在无数据源时空转。

并发与一致性

  • 解码回调为库内部线程异步触发,mListeners 的遍历发生在回调线程;上层消费 PCM 时应自行保证线程安全或切主线程更新 UI。
  • Handler + Looper 用于录音超时等主线程任务(MSG_RECORD_TIME_OUT = 2),与解码回调线程解耦。

性能与运维

  • 流式低延迟:onDecodeStream 逐段回调,适合语音识别实时分片,无需等整段录音结束。
  • 二进制分发:库以 AAR 预编译产物提供,无法从仓库源码审计内部算法;如需深入(私有 OPUS 头格式、加密方案),需参考开发资料包中的 PDF 文档与 JL_OpusDemo 示例工程。
  • 版本一致性:主工程、测试工具与开发资料包应保持同一构建号(20012),避免新旧库行为差异(如 20010 → 20012 的格式兼容增强)。

扩展点

  • 新增语音编码类型:在 OnRecordStateCallback 的 RECORD_STATE_START 分支仿照 OPUS/PCM 增加新分支,并在 AIRecordWrapperListener 增加对应回调即可接入(SPEEX 分支即为此预留)。
  • 解码结果消费方:AIRecordWrapperListener(onDecodeStart/onDecodeStream/onDecodeComplete/onDecodeError)是统一的解码结果出口,IAT/NLP/TTS 均通过注册 listener 接入,新增消费方无需改动解码逻辑。
  • 测试验证:开发资料包提供 OpusTestTool APK 与 测试说明.txt,可脱离完整 App 单独验证库的解码行为,是接入前验证私有格式兼容性的标准手段。

测试与验证

仓库内没有 jl_audio_decode 的单元测试源码(库为二进制 AAR),但开发资料包提供了两套验证手段(ReadMe.txt):

  1. OpusTestTool APK:apk/OpusTestTool_V1.6.0_10600-debug.apk,配合 apk/测试说明.txt 使用,可独立测试 OPUS 编解码(含标准/杰理格式兼容),验证私有格式解密与解码正确性,不依赖完整 App 链路。
  2. JL_OpusDemo 示例工程:code/JL_OpusDemo_V1.6.0_SDK_V2.1.0.zip,提供库的标准接入代码,是 AIRecordWrapper 集成方式之外的官方参考实现。
  3. WatchTestTool:code/tool/WatchTestTool_V0.9.0_SDK_V1.14.0 工程同样打包了该 AAR,可在手表调试工具中验证录音回传 → 解码的完整链路。

应用侧集成行为的验证点:AIRecordWrapper 在 RECORD_STATE_START 时按 VoiceType 分支、断连时 stopDecodeStream() + stopRecord() 的顺序、以及 onError 透传——这些是回归测试应覆盖的核心路径。

Related Links

  • AIRecordWrapper.java(解码集成主文件) —— 本页主要代码依据
  • 开发资料 ReadMe.txt —— 库结构说明与更新记录
  • jl_audio_decode AAR(主工程副本) —— 二进制产物
  • RCSP 录音协议与 RecordOpImpl:见录音相关页面(本页不做展开)
  • AI 语音识别 / NLP / TTS 业务:见 AI 语音能力页面(本页只覆盖解码环节)

说明:本页涉及 AAR 内部实现的细节(私有 OPUS 头结构、解密算法)在仓库源码中不可见,请以官方《杰理音频编码库开发说明.pdf》与 OpusTestTool 实测为准。

Prev
图像转换库