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

    • 环境要求与权限配置
    • 集成SDK依赖
    • 运行示例应用
  • 核心架构与协议

    • RCSP协议与数据通道
    • 蓝牙连接与设备管理
    • TWS双耳功能
    • 基础功能接口与自定义命令
  • 设备功能控制

    • 设备音乐控制与ID3信息
    • 文件浏览与传输
    • FM收音与发射
    • 灯光控制
    • 闹钟与时间管理
    • 查找设备与防丢
    • ANC与噪声处理
    • 按键功能设置
    • 彩屏仓控制
    • AI翻译
  • 音效与音频处理

    • 均衡器音效调节
    • 录音与语音控制
    • Line-in、SPDIF与声卡功能
    • 音频编解码库
  • 扩展功能库

    • OTA固件升级
    • 数据加密与解密
    • 图片与动图格式转换
  • 示例应用 btsmart

    • 应用架构与界面导航
    • 设备功能适配与数据层
    • 设备配置JSON与资源文件
  • 参考与版本

    • 错误码参考
    • 版本历史与更新日志
    • 开发文档中心导航

音频编解码库

音频编解码库(AudioCodec)是 btsmart 应用中用于音频翻译链路的统一编解码抽象层,通过 AudioCodec 抽象基类与 OnAudioStreamCallback 回调契约,将 PCM、OPUS、JL AV2 等不同音频格式的解码能力封装为一致的状态机接口,供 AI 翻译等上层业务透明调用。

Purpose and Scope

本文档介绍 com.jieli.btsmart.tool.translate.codec 包下音频编解码库的完整设计:抽象基类 AudioCodec 的契约定义、回调接口 OnAudioStreamCallback 的事件模型、三个具体编解码器(PcmCodec、OpusCodec、JLAV2Codec)的实现差异、生命周期与状态流转,以及上层业务(如 AI 翻译)的接入方式。

本页范围限定在编解码器抽象层本身。以下相关主题由兄弟页面承载,不在本页展开:

  • 音频数据的采集与上传(麦克风采集、音频流上传)参见音频处理相关页面
  • 播放环节由 AudioPlayer / TranslationSessionPlayer 负责,参见播放器相关页面
  • 底层蓝牙协议与 AudioData 的传输定义位于 com.jieli.bluetooth SDK 中,本页仅引用其类型
  • 底层 OPUS 原生解码能力由独立库 com.jieli.jl_audio_decode(OpusManager)提供,本页只描述其在 OpusCodec 中的封装方式

Overview

为什么需要统一的编解码抽象

在 AI 翻译(AITranslationImpl)场景中,设备端与 App 端需要交换多种音频格式的语音数据:PCM 原始采样数据、OPUS 压缩流、JL 私有 AV2 编码流。若上层业务直接依赖每一种格式的具体实现,将导致:

  • 业务代码与格式耦合,新增格式需要改动所有调用方;
  • 解码器的启动、停止、异常处理逻辑无法复用;
  • 无法在运行时按需切换编码格式。

AudioCodec 抽象基类正是为了解决这一问题而设计:它把"解码流"建模为 启动 → 写入 → 回调输出 → 停止 的状态机,并向上层暴露 @AudioType 标注的音频类型标识。上层只需持有一个 AudioCodec 引用,即可对任意格式执行相同的操作序列,而格式差异被封装在子类内部。

核心概念与术语

术语说明
AudioCodec编解码器抽象基类,定义解码流生命周期契约
OnAudioStreamCallback解码流事件回调接口,向业务层上报 start/stop/error/stream 事件
AudioData蓝牙 SDK(com.jieli.bluetooth.bean.translation)中的音频数据载体,携带类型与字节负载
@AudioType蓝牙 SDK 注解,标注音频类型常量(如 Constants.AUDIO_TYPE_PCM、AUDIO_TYPE_OPUS)
OpusManager独立库 com.jieli.jl_audio_decode 提供的 OPUS 原生编解码管理器
OpusOptionOPUS 解码器启动参数对象

使用场景

  • AI 翻译(AITranslationImpl):接收设备端上传的音频流,按类型选择对应 AudioCodec 解码,再送入语音识别/合成链路;
  • 文件转码:OpusCodec.encodeFile() 提供 PCM 文件 → OPUS 文件的离线编码能力,用于语音数据的预处理或回放准备;
  • 格式扩展:未来新增音频格式时,仅需新增一个 AudioCodec 子类,无需改动上层业务。

Architecture

下图展示编解码库的整体架构与依赖关系:

flowchart TD
    subgraph sg_Business["业务层 (tool/translate)"]
        AITranslationImpl["AITranslationImpl"]
        CustomCallback["CustomAudioStreamCallback<br/>(implements OnAudioStreamCallback)"]
    end

    subgraph sg_CodecLib["编解码库 (tool/translate/codec)"]
        AudioCodec["AudioCodec<br/>(abstract base)"]
        PcmCodec["PcmCodec"]
        OpusCodec["OpusCodec"]
        JLAV2Codec["JLAV2Codec"]
        Callback["OnAudioStreamCallback<br/>(interface)"]
    end

    subgraph sg_Native["底层依赖"]
        OpusManager["OpusManager<br/>(jl_audio_decode)"]
        BluetoothSDK["蓝牙 SDK<br/>(AudioData / Constants)"]
    end

    subgraph sg_Player["播放侧"]
        AudioPlayer["AudioPlayer"]
        SessionPlayer["TranslationSessionPlayer"]
    end

    AITranslationImpl -->|"持有并使用"| AudioCodec
    AITranslationImpl --> CustomCallback
    CustomCallback -.->|"implements"| Callback
    AudioCodec --> Callback
    PcmCodec -->|"extends"| AudioCodec
    OpusCodec -->|"extends"| AudioCodec
    JLAV2Codec -->|"extends"| AudioCodec
    OpusCodec -->|"封装调用"| OpusManager
    OpusCodec -->|"类型常量"| BluetoothSDK
    PcmCodec -->|"类型常量"| BluetoothSDK
    AudioCodec -->|"数据载体"| BluetoothSDK
    AITranslationImpl -->|"解码输出"| AudioPlayer
    AudioPlayer --> SessionPlayer

架构解读:

  • 抽象层(契约):AudioCodec 定义了所有编解码器必须实现的生命周期方法,并持有 lastAudioData(上一包音频数据,可用于调试与重放);OnAudioStreamCallback 定义了解码事件的四类回调。
  • 实现层(策略):PcmCodec、OpusCodec、JLAV2Codec 是三种格式的具体策略。PcmCodec 是纯转发实现(PCM 无需解码,直接透传),OpusCodec 委托原生 OpusManager 完成实际解码,JLAV2Codec 面向 JL 私有格式。
  • 业务层(调用方):AITranslationImpl 通过内部类 CustomAudioStreamCallback 实现回调接口,把解码后的音频流接入翻译流水线;解码结果最终交给 AudioPlayer 播放。
  • 底层依赖:蓝牙 SDK 提供 AudioData 载体与 Constants.AUDIO_TYPE_* 类型常量,编解码库与 SDK 解耦,仅依赖其类型定义。

类层次结构

classDiagram
    class AudioCodec {
        #lastAudioData AudioData
        +getAudioType() int
        +getLastAudioData() AudioData
        +isWorking() bool
        +startDecodeStream(OnAudioStreamCallback callback)
        +startDecodeStream(Object option, OnAudioStreamCallback callback)
        +writeAudioData(AudioData audioData) bool
        +stopDecodeStream() bool
        +release()
    }

    class OnAudioStreamCallback {
        <<interface>>
        +onStart(int type)
        +onStop(int type, String result)
        +onError(int type, int code, String message)
        +onStream(int srcType, int audioType, byte[] data)
    }

    class PcmCodec {
        -mCallback OnAudioStreamCallback
        -isWorking bool
        +getAudioType() int
        +startDecodeStream(Object option, OnAudioStreamCallback callback)
        +writeAudioData(AudioData audioData) bool
        +stopDecodeStream() bool
    }

    class OpusCodec {
        -mDecoder OpusManager
        +encodeFile(String pcmFilePath, String opusFilePath, OnStateCallback callback)$
        +getAudioType() int
        +isWorking() bool
        +startDecodeStream(Object option, OnAudioStreamCallback callback)
    }

    class JLAV2Codec {
        +getAudioType() int
    }

    AudioCodec <|-- PcmCodec
    AudioCodec <|-- OpusCodec
    AudioCodec <|-- JLAV2Codec
    AudioCodec ..> OnAudioStreamCallback : "回调事件"

注:JLAV2Codec 的类声明已确认(public class JLAV2Codec extends AudioCodec),但其方法级实现细节未在本页读取范围内展开,详见源码文件。

实现详解

AudioCodec 抽象基类:编解码契约

AudioCodec 是整个编解码库的核心抽象,其设计意图是把"解码流"这一资源建模为有明确生命周期的对象。源码中的契约定义如下:

public abstract class AudioCodec {
    /** 上一包音频数据 */
    protected AudioData lastAudioData;

    /** 音频类型 */
    @AudioType
    public abstract int getAudioType();

    public AudioData getLastAudioData() {
        return lastAudioData;
    }

    public abstract boolean isWorking();

    public abstract void startDecodeStream(OnAudioStreamCallback callback);

    public abstract void startDecodeStream(Object option, OnAudioStreamCallback callback);

    public abstract boolean writeAudioData(AudioData audioData);

    public abstract boolean stopDecodeStream();

    public abstract void release();

    public interface OnAudioStreamCallback {
        void onStart(@AudioType int type);
        void onStop(@AudioType int type, String result);
        void onError(@AudioType int type, int code, String message);
        void onStream(@AudioType int srcType, @AudioType int audioType, byte[] data);
    }
}

Source: AudioCodec.java

设计要点:

  • lastAudioData 受保护字段:由子类在 writeAudioData 时记录最近一包数据,供调试、状态恢复或重放使用。getLastAudioData() 对任何调用方开放,但只有子类能写入,保证封装性。
  • 双 startDecodeStream 重载:无参版本(startDecodeStream(OnAudioStreamCallback))是便捷入口,内部委托给带 option 的版本并传入默认参数;带 option 版本允许调用方传入格式相关配置(如 OpusOption)。这是典型的"模板方法 + 参数对象"组合,让默认路径简单、高级路径可配置。
  • isWorking() 幂等查询:业务层可随时轮询解码器是否处于工作状态,避免对已启动的流重复启动。
  • release() 资源回收:语义是释放解码器占用的所有资源(原生句柄、线程、回调引用),实现上通常委托给 stopDecodeStream() 并清理内部状态。
  • OnAudioStreamCallback 事件模型:四个事件覆盖解码流完整生命周期——onStart(启动成功)、onStream(输出一帧解码后数据)、onError(异常,携带错误码与消息)、onStop(正常停止,携带结果字符串)。onStream 同时携带 srcType(源格式)与 audioType(输出格式),便于转码场景中区分输入输出。

PcmCodec:PCM 透传实现

PCM(Pulse Code Modulation)是未压缩的原始采样数据,因此 PcmCodec 的解码实质是透传——不经过任何变换,直接把收到的 AudioData 负载交给回调。其完整实现如下:

public class PcmCodec extends AudioCodec {
    private OnAudioStreamCallback mCallback;
    private boolean isWorking;

    @Override
    public int getAudioType() {
        return Constants.AUDIO_TYPE_PCM;
    }

    @Override
    public boolean isWorking() {
        return isWorking;
    }

    @Override
    public void startDecodeStream(OnAudioStreamCallback callback) {
        startDecodeStream(null, callback);
    }

    @Override
    public void startDecodeStream(Object option, OnAudioStreamCallback callback) {
        if (isWorking()) {
            if (null != callback) {
                int code = ErrorCode.SUB_ERR_OPERATION_IN_PROGRESS;
                callback.onError(getAudioType(), code, ErrorCode.code2Msg(code));
            }
            return;
        }
        mCallback = callback;
        isWorking = true;
        if (null != callback) {
            callback.onStart(getAudioType());
        }
    }

    @Override
    public boolean writeAudioData(AudioData audioData) {
        if (null == audioData || !isWorking() || audioData.getType() != getAudioType())
            return false;
        lastAudioData = audioData;
        if (null != mCallback) {
            mCallback.onStream(getAudioType(), Constants.AUDIO_TYPE_PCM, audioData.getAudioData());
        }
        return true;
    }

    @Override
    public boolean stopDecodeStream() {
        if (!isWorking()) return false;
        isWorking = false;
        final OnAudioStreamCallback callback = mCallback;
        mCallback = null;
        if (null != callback) {
            callback.onStop(getAudioType(), "Success");
        }
        return true;
    }

    @Override
    public void release() {
        stopDecodeStream();
    }
}

Source: PcmCodec.java

实现行为逐点分析:

  • 状态守卫:startDecodeStream 在 isWorking 为 true 时拒绝重复启动,并通过 ErrorCode.SUB_ERR_OPERATION_IN_PROGRESS(子错误码,表示"操作进行中")通知调用方。这保证了解码流单实例语义——同一时间只允许一个活跃解码流,避免回调被多个写入者并发触发。
  • 启动语义:startDecodeStream 是同步操作,立即将 isWorking 置位并回调 onStart。PCM 无需初始化任何原生资源,因此启动即就绪。
  • 写入语义:writeAudioData 返回 boolean,三重校验(非空、工作状态、类型匹配)任一不满足即返回 false。类型校验 audioData.getType() != getAudioType() 防止把 OPUS 数据误写入 PCM 解码器。通过后记录 lastAudioData 并回调 onStream,源类型与输出类型均为 AUDIO_TYPE_PCM。
  • 停止语义:stopDecodeStream 在未工作时返回 false(幂等);正常停止时先取走 mCallback 引用并置空,再回调 onStop("Success")——先置空再回调可防止回调内部再次触发写入导致的状态混乱。
  • release 即停止:PCM 无底层资源,释放等价于停止流。

OpusCodec:OPUS 编解码实现

OpusCodec 是三种实现中最复杂的一个,它把独立库 com.jieli.jl_audio_decode 中的原生 OpusManager 封装进 AudioCodec 契约。其启动路径源码如下:

@Override
public void startDecodeStream(OnAudioStreamCallback callback) {
    startDecodeStream(new OpusOption(), callback);
}

@Override
public void startDecodeStream(Object option, OnAudioStreamCallback callback) {
    if (!(option instanceof OpusOption)) {
        option = new OpusOption();
    }
    OpusOption opusOption = (OpusOption) option;
    if (null == mDecoder) {
        try {
            mDecoder = new OpusManager();
        } catch (OpusException e) {
            String message = AppUtil.formatString("Failed to init opus manager.\n" +
                    "message : %s", e.getMessage());
            if (null != callback) {
                callback.onError(getAudioType(), ErrorCode.ERR_NONE_INIT, message);
            }
            return;
        }
    }
    // ... 后续设置 option 并启动解码流(onStart 回调)
}

Source: OpusCodec.java

设计要点:

  • 默认参数兜底:如果调用方传入的 option 不是 OpusOption 实例,自动替换为 new OpusOption()。这种"容错归一化"保证了解码器始终以合法配置启动,避免类型转换崩溃。
  • 惰性创建 + 复用:mDecoder(OpusManager)在首次启动时创建,后续启动复用同一实例,isWorking() 委托给 mDecoder.isDecodeStream()——工作状态由底层原生管理器维护,与 PcmCodec 的本地 boolean 标志形成对比。
  • 初始化失败上报:new OpusManager() 可能抛出 OpusException(原生库初始化失败,如 SO 加载失败),此时通过 ErrorCode.ERR_NONE_INIT(未初始化错误码)回调 onError,并携带格式化后的异常消息,便于上层定位问题。

OpusCodec 还提供离线文件编码能力,用于 PCM 文件到 OPUS 文件的转码:

public static void encodeFile(String pcmFilePath, String opusFilePath, OnStateCallback callback) {
    try {
        final OpusManager encoder = new OpusManager();
        encoder.encodeFile(pcmFilePath, opusFilePath, new OnStateCallback() {
            @Override
            public void onStart() {
                if (null != callback) {
                    callback.onStart();
                }
            }

            @Override
            public void onComplete(String s) {
                encoder.release();
                if (null != callback) {
                    callback.onComplete(s);
                }
            }

            @Override
            public void onError(int i, String s) {
                encoder.release();
                if (null != callback) {
                    callback.onError(i, s);
                }
            }
        });
    } catch (OpusException e) {
        if (null != callback) {
            callback.onError(ErrorCode.ERR_NONE_INIT, ErrorCode.getErrorMsg(ErrorCode.ERR_NONE_INIT));
        }
    }
}

Source: OpusCodec.java

该静态方法的关键行为:

  • 确定性资源释放:无论编码成功(onComplete)还是失败(onError),都先调用 encoder.release() 释放原生编码器,再向调用方转发回调。这一"先释放、后上报"的顺序保证异常路径不会泄漏原生资源。
  • 初始化异常兜底:new OpusManager() 抛出的 OpusException 被捕获后,统一以 ERR_NONE_INIT 上报,错误码与解码路径保持一致。
  • 回调转发模式:内部匿名 OnStateCallback 只做空指针检查与转发,不添加业务逻辑,保持职责单一。

JLAV2Codec:JL AV2 编解码实现

JLAV2Codec 与 PcmCodec、OpusCodec 并列,同样继承 AudioCodec:

public class JLAV2Codec extends AudioCodec {
    // 具体实现细节见源码文件
}

Source: JLAV2Codec.java

JL AV2 是杰理(Jieli)私有音频编码格式。该类的存在表明编解码库采用"一格式一策略"的扩展模式:新增格式只需新增子类并实现 getAudioType() 等契约方法,上层业务无需感知差异。其方法级实现(涉及 JL 私有解码器)未在本页读取范围内展开,具体细节以源码为准。

Core Flow

解码流生命周期

下图展示上层业务通过 AudioCodec 驱动一次完整解码流的时序(以 OPUS 为例):

sequenceDiagram
    participant Biz as 业务层 (AITranslationImpl)
    participant Codec as AudioCodec 子类 (OpusCodec)
    participant Native as OpusManager (jl_audio_decode)
    participant CB as OnAudioStreamCallback (CustomAudioStreamCallback)

    Biz->>Codec: startDecodeStream(opusOption, callback)
    Codec->>Native: new OpusManager()
    Codec->>Native: 配置 OpusOption 并启动
    Codec-->>CB: onStart(AUDIO_TYPE_OPUS)
    CB-->>Biz: 更新状态 (解码中)

    loop 每包音频数据
        Biz->>Codec: writeAudioData(audioData)
        Codec->>Codec: 校验类型 / 工作状态
        Codec->>Native: 送入解码器
        Native-->>Codec: 解码后 PCM 数据
        Codec-->>CB: onStream(OPUS, PCM, byte[])
        CB-->>Biz: 送入语音识别链路
    end

    Biz->>Codec: stopDecodeStream()
    Codec->>Native: 停止并释放解码流
    Codec-->>CB: onStop(AUDIO_TYPE_OPUS, "Success")
    CB-->>Biz: 更新状态 (已停止)

流程要点:

  1. 启动:业务层调用 startDecodeStream,子类完成资源初始化(OPUS 需创建 OpusManager;PCM 仅置标志位),随后同步回调 onStart。
  2. 写入-解码循环:业务层逐包调用 writeAudioData(AudioData);子类校验数据合法性后送入底层解码,解码结果通过 onStream 回调输出。PCM 路径下该循环退化为纯透传。
  3. 停止:业务层调用 stopDecodeStream,子类停止底层流、清理回调引用并回调 onStop。
  4. 异常路径:任何环节失败(如原生初始化异常)通过 onError(type, code, message) 上报,业务层可据此终止流程或重试。

状态机视图

stateDiagram-v2
    [*] --> IDLE
    IDLE --> WORKING : startDecodeStream(callback)
    IDLE --> IDLE : startDecodeStream (重复启动) → onError(SUB_ERR_OPERATION_IN_PROGRESS)
    WORKING --> WORKING : writeAudioData(data) → onStream
    WORKING --> IDLE : stopDecodeStream() → onStop
    WORKING --> IDLE : release()
    WORKING --> IDLE : onError (异常终止)
    IDLE --> [*]
  • IDLE(空闲):解码器未启动。writeAudioData 返回 false,stopDecodeStream 返回 false(幂等)。
  • WORKING(工作中):解码流活跃。重复 startDecodeStream 被拒绝并以 SUB_ERR_OPERATION_IN_PROGRESS 上报错误;writeAudioData 正常透传/解码并触发 onStream。
  • 状态迁移全部由子类内部维护(PcmCodec 用本地 isWorking,OpusCodec 委托 OpusManager.isDecodeStream()),对上层透明。

数据模型

编解码库的数据载体是蓝牙 SDK 的 AudioData(com.jieli.bluetooth.bean.translation.AudioData)。AudioData 携带音频类型与字节负载,贯穿解码流全程:

erDiagram
    AudioCodec {
        AudioData lastAudioData "最近一包数据"
        int audioType "格式标识"
    }
    AudioData {
        int type "AUDIO_TYPE_*"
        byte audioData "负载字节"
    }
    OnAudioStreamCallback {
        int srcType "源格式"
        int audioType "输出格式"
        byte data "解码后数据"
    }
    AudioCodec ||--o{ AudioData : "持有/写入"
    AudioCodec ||--o{ OnAudioStreamCallback : "回调输出"
成员说明
AudioData.type音频类型,取值来自 Constants.AUDIO_TYPE_*(如 AUDIO_TYPE_PCM、AUDIO_TYPE_OPUS),由 @AudioType 注解约束
AudioData.getAudioData()音频负载字节数组,writeAudioData 与 onStream 均操作该负载

类型常量对照:PcmCodec.getAudioType() 返回 Constants.AUDIO_TYPE_PCM;OpusCodec.getAudioType() 返回 Constants.AUDIO_TYPE_OPUS。onStream 回调中 srcType 与 audioType 的组合(如 OPUS → PCM)体现了"源格式 → 输出格式"的转码关系——OPUS 解码后输出 PCM 数据,而 PCM 路径二者相同。

Usage Examples

示例一:业务层实现回调并驱动解码流

AITranslationImpl 通过内部静态类实现 OnAudioStreamCallback,展示业务层接入编解码库的标准姿势:

private static class CustomAudioStreamCallback implements AudioCodec.OnAudioStreamCallback {
    // onStart / onStop / onError / onStream 四类事件的业务处理
    // 其中 onStream 接收解码后的音频数据并送入后续处理链路
}

Source: AITranslationImpl.java

接入步骤(与源码结构对应):

  1. 根据音频类型选择 AudioCodec 子类(PCM → PcmCodec,OPUS → OpusCodec,AV2 → JLAV2Codec);
  2. startDecodeStream(option, callback) 启动解码流,在 onStart 中确认就绪;
  3. 持续 writeAudioData(audioData) 喂入数据,在 onStream 中消费解码结果;
  4. 结束或异常时 stopDecodeStream(),在 onStop/onError 中收尾。

示例二:PCM 解码器的最小驱动序列

PcmCodec pcmCodec = new PcmCodec();
pcmCodec.startDecodeStream(callback);                    // 触发 onStart(AUDIO_TYPE_PCM)
boolean ok = pcmCodec.writeAudioData(audioData);         // 校验通过 → onStream(PCM, PCM, data)
pcmCodec.stopDecodeStream();                             // 触发 onStop(AUDIO_TYPE_PCM, "Success")
pcmCodec.release();                                      // 等价于 stopDecodeStream()

Source: PcmCodec.java

示例三:OPUS 离线文件编码

OpusCodec.encodeFile(pcmFilePath, opusFilePath, new OnStateCallback() {
    @Override
    public void onStart() { /* 编码开始 */ }

    @Override
    public void onComplete(String s) { /* 编码完成,编码器已自动释放 */ }

    @Override
    public void onError(int i, String s) { /* 编码失败,编码器已自动释放 */ }
});

Source: OpusCodec.java

该示例演示了 encodeFile 的"回调内无需手动释放编码器"的便利性:onComplete/onError 回调触发前,内部已调用 encoder.release(),调用方只需处理业务结果。

示例四:OPUS 解码流启动(带参数)

OpusCodec opusCodec = new OpusCodec();
OpusOption option = new OpusOption();                    // 可自定义采样率等参数
opusCodec.startDecodeStream(option, callback);           // 内部创建 OpusManager 并启动
while (opusCodec.isWorking()) {
    opusCodec.writeAudioData(opusAudioData);             // 逐包喂入
}
opusCodec.stopDecodeStream();

Source: OpusCodec.java

Configuration Options

编解码库的配置通过 startDecodeStream(Object option, ...) 的 option 参数注入,目前仅有 OPUS 路径使用配置对象:

选项类型默认值说明
startDecodeStream 的 optionObject各子类默认(PCM 忽略,OPUS 为 new OpusOption())解码流启动参数;非 OpusOption 实例会被自动归一化为默认值
OpusOptionOpusOption(来自 jl_audio_decode)new OpusOption()OPUS 解码器参数(采样率、声道等),字段定义见底层库源码
startDecodeStream(callback) 便捷重载-委托带参版本使用默认参数启动,无需关心格式细节

设计意图:默认参数路径保证"零配置可用",带参路径为高级场景(如指定采样率)留出扩展空间;错误的参数类型不会导致崩溃,而是静默回退到默认配置。

API Reference

AudioCodec(抽象基类)

int getAudioType()

  • 描述:返回该编解码器处理的音频格式,标注 @AudioType,取值来自蓝牙 SDK 的 Constants.AUDIO_TYPE_*。
  • 返回:音频类型常量(AUDIO_TYPE_PCM / AUDIO_TYPE_OPUS 等)。

AudioData getLastAudioData()

  • 描述:返回最近一次成功写入的音频数据包,可用于调试或重放。
  • 返回:AudioData 或 null(从未写入)。

boolean isWorking()

  • 描述:查询解码流是否处于工作状态。PcmCodec 由本地标志维护,OpusCodec 委托 OpusManager.isDecodeStream()。
  • 返回:true 表示解码流活跃。

void startDecodeStream(OnAudioStreamCallback callback)

  • 描述:使用默认参数启动解码流。
  • 参数:callback — 事件回调,可为空(空则静默启动)。
  • 副作用:成功后回调 onStart(getAudioType());若已工作中,回调 onError(type, SUB_ERR_OPERATION_IN_PROGRESS, ...)。

void startDecodeStream(Object option, OnAudioStreamCallback callback)

  • 描述:使用指定参数启动解码流。OpusCodec 会校验 option 类型,非法值回退为 new OpusOption();OpusCodec 初始化失败时回调 onError(type, ERR_NONE_INIT, message)。
  • 参数:option — 格式相关配置对象;callback — 事件回调。
  • 返回:无(结果通过回调上报)。

boolean writeAudioData(AudioData audioData)

  • 描述:向解码流写入一包音频数据。写入成功记录 lastAudioData 并触发 onStream。
  • 参数:audioData — 音频数据包。
  • 返回:true 写入成功;false 当数据为空、未在工作状态或类型不匹配。

boolean stopDecodeStream()

  • 描述:停止解码流并触发 onStop(type, "Success");未工作时返回 false(幂等)。
  • 返回:true 本次确实停止了流。

void release()

  • 描述:释放编解码器资源。PcmCodec 等价于 stopDecodeStream();OpusCodec 额外释放底层 OpusManager(见底层库实现)。

OnAudioStreamCallback(接口)

方法触发时机关键参数
void onStart(@AudioType int type)解码流启动成功type:本解码器格式
void onStop(@AudioType int type, String result)正常停止result:结果描述(如 "Success")
void onError(@AudioType int type, int code, String message)启动/运行异常code:错误码(SUB_ERR_OPERATION_IN_PROGRESS、ERR_NONE_INIT 等);message:可读信息
void onStream(@AudioType int srcType, @AudioType int audioType, byte[] data)每输出一帧解码数据srcType:源格式;audioType:输出格式;data:解码后负载

OpusCodec(静态工具方法)

static void encodeFile(String pcmFilePath, String opusFilePath, OnStateCallback callback)

  • 描述:将 PCM 文件编码为 OPUS 文件(离线转码)。
  • 参数:pcmFilePath — PCM 源文件路径;opusFilePath — OPUS 目标文件路径;callback — 底层库的 OnStateCallback 状态回调(onStart/onComplete/onError)。
  • 资源保证:无论成功失败,内部编码器都会在回调前释放。
  • Throws:OpusException(原生初始化失败)被内部捕获并转为 onError(ERR_NONE_INIT, ...) 回调。

Failure Modes, Edge Cases & Concurrency

已确认的错误处理路径

场景处理方式证据
重复启动解码流拒绝并回调 onError(SUB_ERR_OPERATION_IN_PROGRESS)PcmCodec.java L42-L48
写入空数据 / 未启动 / 类型不匹配writeAudioData 返回 false,不触发回调PcmCodec.java L57-L59
OPUS 原生管理器初始化失败(OpusException)回调 onError(ERR_NONE_INIT, 格式化消息)OpusCodec.java L91-L99
encodeFile 初始化失败捕获 OpusException 并回调 onError(ERR_NONE_INIT)OpusCodec.java L57-L61
非法 option 参数类型静默回退为默认 OpusOption()OpusCodec.java L87-L89
未启动时停止 / 释放幂等,返回 false,无回调PcmCodec.java L68-L69

边界情况

  • 空回调:startDecodeStream(null) 允许——内部所有回调前都有空指针检查,解码流仍可启动,但业务层收不到任何事件(仅适合纯写入的丢弃场景)。
  • 类型失配写入:writeAudioData 的类型校验保证 OPUS 数据不会进入 PcmCodec,但不保证调用方传入了正确的解码器实例——选择解码器是上层职责(按 getAudioType() 匹配)。
  • 重复 release():release() 委托 stopDecodeStream(),后者幂等,重复释放安全。

并发与一致性

  • 单活跃流约束:startDecodeStream 的重复启动守卫(SUB_ERR_OPERATION_IN_PROGRESS)隐含"单解码流"设计——同一编解码器实例同一时刻只允许一个活跃流,回调不会因多写入者交错而错乱。
  • 回调引用快照:stopDecodeStream 先取走 mCallback 引用再置空,避免停止过程中回调触发新的写入导致 NPE 或状态倒挂。
  • 线程模型:源码未显示内部线程池/锁;writeAudioData 的回调在调用线程同步执行。上层若从多线程喂数据,需自行保证串行化(例如通过单一音频接收线程),本库不提供线程安全保障。

Performance & Operational Considerations

  • PCM 路径零拷贝透传:PcmCodec.writeAudioData 不进行任何数据变换,onStream 直接透传 audioData.getAudioData(),开销极低,适合高频原始采样流。
  • OPUS 解码为原生调用:OpusCodec 委托 OpusManager(jl_audio_decode 原生库),解码耗时为 JNI 调用;isWorking() 每次查询都进入底层,高频轮询有额外开销,建议以事件驱动(onStop/onError)代替轮询。
  • 解码器实例复用:OpusCodec.mDecoder 惰性创建并复用,避免每次启动都重新加载原生库;但实例与回调生命周期绑定,release() 后需重新初始化。
  • encodeFile 异步语义:OpusCodec.encodeFile 是异步文件操作,回调线程为底层库内部线程;调用方不应在回调中执行耗时 UI 操作。
  • 资源释放时机:OPUS 路径务必在业务结束或 onError 后调用 release(),确保原生解码器句柄及时回收;PcmCodec 的 release() 仅为状态清理,成本可忽略。

Extension Points

编解码库的扩展模型是"一格式一策略",新增音频格式的步骤非常清晰:

  1. 新增子类:创建 XxxCodec extends AudioCodec,置于 com.jieli.btsmart.tool.translate.codec 包内(或同包可见位置);
  2. 实现契约:实现 getAudioType()(返回新的 @AudioType 常量)、isWorking()、两个 startDecodeStream 重载、writeAudioData()、stopDecodeStream()、release();
  3. 接入格式调度:在上层(如 AITranslationImpl)按音频类型分发到新子类——若类型选择逻辑集中,可考虑引入工厂/注册表,但当前源码中未发现独立工厂类(选择逻辑内联在业务层);
  4. 可选:若新格式需要参数配置,仿照 OpusOption 定义参数对象,并在 startDecodeStream(Object option, ...) 中做类型校验与默认值回退。

扩展约定(来自现有实现的隐含约束):

  • 子类必须保证 startDecodeStream 的幂等拒绝语义(重复启动上报 SUB_ERR_OPERATION_IN_PROGRESS);
  • 子类必须在 stopDecodeStream() 中先清理回调引用再回调 onStop;
  • release() 必须可重复调用且幂等;
  • 原生资源(如 OPUS 管理器)必须在异常路径同样释放,遵循"先释放、后上报"模式。

Tests

本页读取范围内未在 tool/translate/codec 包中发现独立单元测试文件。测试保障主要来自:

  • 上层集成验证:AITranslationImpl.CustomAudioStreamCallback 对 onStream 数据的消费逻辑,实际覆盖了解码流端到端行为;
  • 契约自洽性:PcmCodec 的三重校验(空值/工作状态/类型匹配)与幂等停止逻辑,天然可被单元测试覆盖(构造非法 AudioData 断言返回 false、重复启动断言 onError 等),但当前仓库中未见对应测试用例,属于文档化差距,补充测试时建议优先覆盖:重复启动拒绝、类型失配写入拒绝、停止后写入拒绝、release 幂等。

Related Links

  • AudioCodec.java(抽象基类)
  • PcmCodec.java(PCM 实现)
  • OpusCodec.java(OPUS 实现)
  • JLAV2Codec.java(JL AV2 实现)
  • AITranslationImpl.java(业务调用方)
  • AudioPlayer.java(播放器)
  • TranslationSessionPlayer.java(翻译会话播放器)
  • 相关主题:音频播放与翻译会话流程见播放器页面;音频采集与上传见音频处理相关页面;底层 AudioData 与 @AudioType 定义见蓝牙 SDK 文档。
Prev
Line-in、SPDIF与声卡功能