音频编解码与解密
本文档介绍 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):
RECORD_STATE_START:保存RecordParam,启动录音超时计时;随后按VoiceType分支:VOICE_TYPE_OPUS:调用mOpusManager.startDecodeStream(...)注册解码回调,进入解码流程VOICE_TYPE_SPEEX:分支为空(预留,当前未实现)VOICE_TYPE_PCM:无需解码,直接回调onDecodeStart()通知上层
RECORD_STATE_WORKING:手表持续回传录音数据(OPUS 帧),数据进入OpusManager解码,解码结果经onDecodeStream(byte[])逐段转交AIRecordWrapperListener(供 IAT 等消费)。- 解码完成/停止:
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
关键设计点:
- 解码触发与录音状态绑定:只有
VoiceType == VOICE_TYPE_OPUS才启动解码器,PCM 直通、SPEEX 预留,三者互不干扰(AIRecordWrapper.java)。 - 逐段流式回调:解码不等待整段录音结束,而是随录音数据持续产出 PCM,保证语音识别低延迟。
- 断连即停:设备断开是最高优先级事件,先停解码、再停录音,避免回调风暴与资源泄漏。
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);
}
}
});
}
}
要点:回调对象内只做"转发"(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;
}
}
要点:断连是异步事件,必须显式调用 stopDecodeStream() 终止解码器内部状态,否则下次录音可能残留上一次的上下文(解码器状态污染)。
配置选项
jl_audio_decode 库本身无需配置文件(AAR 内建默认参数)。与解码行为相关的可配置项集中在录音参数 RecordParam 的语音类型选择上,由 AIRecordWrapper 在录音状态回调中读取(AIRecordWrapper.java):
| 选项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
RecordParam.VOICE_TYPE_OPUS | int | OPUS 录音时使用 | 触发 OpusManager.startDecodeStream() 解码链路 |
RecordParam.VOICE_TYPE_SPEEX | int | 未启用(分支为空) | 预留的 SPEEX 解码分支,当前未实现 |
RecordParam.VOICE_TYPE_PCM | int | PCM 直通 | 无需解码,直接回调 onDecodeStart() |
AIRecordWrapper.isNeedAsyncIat | boolean | false | 是否需要同步语音识别(断连时复位) |
AIRecordWrapper.isNeedAsyncNlp | boolean | false | 是否需要同步语义结果(断连时复位) |
AIRecordWrapper.isNeedPlayTTS | boolean | false | 是否需要播放 TTS(断连时复位) |
MSG_RECORD_TIME_OUT | int | 2 | 录音超时消息类型(Handler 消息) |
| AAR 版本号 | - | V2.1.0_20012 | 主工程统一使用 20012 构建号 |
API Reference
以下接口签名依据仓库内实际调用代码整理(AAR 为二进制,字段级细节以官方《杰理音频编码库开发说明.pdf》为准):
OpusManager
库的门面类,包路径 com.jieli.jl_audio_decode.opus。
| 方法 | 签名(依据调用点推断) | 说明 |
|---|---|---|
startDecodeStream | startDecodeStream(OnDecodeStreamCallback callback) | 启动 OPUS 解码流,注册回调;异步输出 PCM |
stopDecodeStream | stopDecodeStream() | 停止解码流,释放解码器内部状态(断连时调用) |
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 接入,新增消费方无需改动解码逻辑。 - 测试验证:开发资料包提供
OpusTestToolAPK 与测试说明.txt,可脱离完整 App 单独验证库的解码行为,是接入前验证私有格式兼容性的标准手段。
测试与验证
仓库内没有 jl_audio_decode 的单元测试源码(库为二进制 AAR),但开发资料包提供了两套验证手段(ReadMe.txt):
- OpusTestTool APK:
apk/OpusTestTool_V1.6.0_10600-debug.apk,配合apk/测试说明.txt使用,可独立测试 OPUS 编解码(含标准/杰理格式兼容),验证私有格式解密与解码正确性,不依赖完整 App 链路。 - JL_OpusDemo 示例工程:
code/JL_OpusDemo_V1.6.0_SDK_V2.1.0.zip,提供库的标准接入代码,是AIRecordWrapper集成方式之外的官方参考实现。 - 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 实测为准。