音频编解码示例工程
本文档介绍 Jieli-Tech/iOS-JL_Health 仓库中与音频编解码相关的示例工程,涵盖 JLAV2 编解码器的高层封装接口(JLAV2Codec)、底层 C 语言编解码 API(jla_v2_codec_api)、Opus 编码配置(JLOpusEncodeConfig)、频谱分析工具(FFTHelper)以及演示工程的组织结构与调用方式。
Purpose and Scope
本页面聚焦于仓库中「音频编解码」这一能力,覆盖以下内容:
- 仓库内音频编解码相关示例工程的目录结构与分工(
code/杰理iOS音频编解码V1.1.0/demo/、code/JLAudioUnitKitDemo/、code/SDKTestHelper/) - JLAV2Codec 高层 Objective-C 封装接口的完整说明:流式编解码、文件编解码、动态解码配置(淡入淡出、声道模式、声道混合系数、ED 帧长度)
- 编解码结果回调机制(delegate 协议与 block 回调)
- 错误码体系与失败处理
- 配套工具(FFTHelper 频谱分析、AAChartKitLib 波形图表)在 demo 中的角色
以下主题属于仓库中其他页面,本页不做展开:
- 音频设备连接与通信协议(见对应的设备通信相关页面)
- 音频播放/录音设备的底层驱动
- JLAudioUnitKit 的其他能力(录音、播放等)
说明:本页基于已读取的源码证据撰写;
code/杰理iOS音频编解码V1.1.0/demo/内部 ViewController 层的完整业务代码未在本次探索中逐一读取,涉及具体界面流程处将明确标注「实现细节未在源码中展开」。
Overview
杰理 iOS 音频编解码 SDK 为开发者提供了一套完整的音频编解码解决方案,典型的使用场景包括:
- 语音对讲/录音上传:将采集到的 PCM 数据编码为压缩格式后传输或保存
- 语音播报/音频播放:将压缩格式解码回 PCM 后送入播放链路
- 音效处理:解码时动态应用淡入淡出、左右声道分离/混合等效果
- 特殊帧(ED 帧)处理:支持自定义长度的特殊帧配置,用于定制化协议
仓库中的示例工程围绕这套 SDK 提供三种粒度的参考实现:
- JLAV2Codec(OC 高层封装):位于
SDKTestHelper工程中,把底层 C API 封装为面向对象的接口,支持 delegate 回调与 block 回调两种风格,是推荐的首选接入方式; - jla_v2_codec_api(底层 C 接口):位于
JLAV2Lib.framework中,提供最底层的编解码能力,供上层封装调用; - JLAudioUnitKitDemo(Kit 级示例):演示基于 JLAudioUnitKit 的完整音视频链路,其中
JLOpusEncodeConfig.h定义了 Opus 编码参数配置,FFTHelper.m提供实时频谱计算,用于可视化展示。
Architecture
下图展示音频编解码能力在示例工程中的分层结构与数据流向:
flowchart TD
subgraph sg_DemoApp["示例工程层 (Demo App)"]
VC["ViewController / Demo 界面"]
Chart["AAChartKitLib 波形/图表库"]
FFT["FFTHelper 频谱分析"]
end
subgraph sg_OCWrapper["OC 封装层 (SDKTestHelper)"]
JLAV2Codec["JLAV2Codec"]
Config["JLAV2CodecConfig / JLAV2CodeInfo"]
Delegate["JLAV2CodecDelegate / JLAV2CodecBlock"]
end
subgraph sg_CAPI["底层 C API 层 (JLAV2Lib.framework)"]
CODEC_API["jla_v2_codec_api.h"]
end
subgraph sg_Kit["JLAudioUnitKit 示例"]
OpusCfg["JLOpusEncodeConfig"]
AudioUnit["JLAudioUnitKit"]
end
VC -->|"调用 encode/decode"| JLAV2Codec
JLAV2Codec -->|"回调结果"| Delegate
JLAV2Codec -->|"配置参数"| Config
JLAV2Codec -->|"C 函数调用"| CODEC_API
VC -->|"PCM 频谱展示"| FFT
VC -->|"结果波形展示"| Chart
OpusCfg -->|"Opus 参数"| AudioUnit
各层职责说明:
- 示例工程层:
杰理iOS音频编解码V1.1.0/demo是面向开发者的演示 App,负责采集/读取音频数据、调用编解码接口、并通过 AAChartKitLib 将处理结果可视化; - OC 封装层:
JLAV2Codec把底层的、基于 C 的编解码流程收敛为三个简单动作——初始化(createEncode:/createDecode:)、送入数据(encodeData:/decodeData:)、销毁(destoryEncode/destoryDecode),同时提供文件级的一键接口(+encodeFile:.../+decodeFile:...); - 底层 C API 层:
jla_v2_codec_api.h是真正的编解码引擎,所有上层调用最终都会落到这一层; - Kit 示例:
JLAudioUnitKitDemo展示另一条接入路径——直接基于 JLAudioUnitKit 的音频单元链路,配合JLOpusEncodeConfig配置 Opus 编码器。
这样的分层设计意图在于:面向对象的封装层屏蔽了 C 接口的内存管理与状态机细节,让业务方只需关心「数据进、数据出」;同时保留底层 C 接口,为需要极致性能或深度定制的场景留出通道。
示例工程结构
仓库中与音频编解码相关的代码分布在三个目录,各自承担不同的演示职责:
| 目录 | 内容 | 角色 |
|---|---|---|
code/杰理iOS音频编解码V1.1.0/demo/ | 完整演示 App,内嵌 AAChartKitLib 图表库 | 面向最终开发者的端到端演示,展示编解码结果的可视化 |
code/JLAudioUnitKitDemo/ | JLAudioUnitKit 示例工程,含 JLOpusEncodeConfig.h、FFTHelper.m | 演示基于音频单元的链路与 Opus 编码配置 |
code/SDKTestHelper/ | 内部自测工程,含 JLAV2Codec.h/.m、JLAV2Lib.framework | 编解码器 OC 封装与底层 C API 的参考实现 |
其中 SDKTestHelper 工程中的 JLAV2Codec 是理解整套编解码能力的关键入口,下文以其为主进行深入分析。
JLAV2Codec 编解码器接口
JLAV2Codec 位于 JLAV2Codec.h,是 JLAV2 音频编解码器在 Objective-C 侧的统一门面。它同时支持文件式和流式两种处理方式,设计意图是覆盖两类典型需求:一次性转换(如格式转换工具)与实时处理(如对讲、直播)。
流式编解码(Streaming)
流式模式基于「初始化 → 送入数据 → 回调结果 → 销毁」的状态机,接口成对出现:
| 编码侧 | 解码侧 | 说明 |
|---|---|---|
-createEncode:(JLAV2CodeInfo *)info | -createDecode:(JLAV2CodeInfo *)info | 按配置创建编/解码器,返回是否成功 |
-encodeData:(NSData *)pcmData | -decodeData:(NSData *)encodeData | 送入待处理数据(编码输入 PCM,解码输入压缩数据) |
-isEncoding | -isDecoding | 查询当前是否处于编/解码中 |
-destoryEncode | -destoryDecode | 停止并销毁编/解码器,释放内部资源 |
设计上刻意把「创建」与「送入数据」分离:createEncode: / createDecode: 负责一次性校验配置并申请底层资源,返回 BOOL 让调用方可以立即感知初始化失败;而后续数据送入不再返回状态,统一通过 delegate 回调异步通知结果——这是典型的异步流水线设计,避免实时音频场景中主线程被编解码耗时阻塞。
文件编解码(File-based)
对于整文件转换场景,JLAV2Codec 提供了两个类方法,内部自动完成创建、循环读写、销毁的全流程,调用方只需提供路径与回调:
/// 编码文件(异步)
/// @param inFilePath 输入文件路径
/// @param outFilePath 输出文件路径
/// @param option 编码配置
/// @param result 结果回调
+ (void)encodeFile:(NSString *)inFilePath outFilePath:(NSString *)outFilePath Option:(JLAV2CodeInfo *)option Result:(JLAV2CodecBlock) result;
/// 解码文件(异步)
/// @param inFilePath 输入文件路径
/// @param outFilePath 输出文件路径
/// @param option 解码配置
/// @param config 动态配置参数(nullable)
/// @param result 结果回调
+ (void)decodeFile:(NSString *)inFilePath outFilePath:(NSString *)outFilePath Option:(JLAV2CodeInfo *)option ApplyConfig:(JLAV2CodecConfig * _Nullable)config Result:(JLAV2CodecBlock) result;
Sources:
值得注意的差异:解码文件接口比编码文件接口多一个 ApplyConfig: 参数。这是因为解码过程可以动态应用音效配置(淡入淡出、声道模式等),而编码侧只关心压缩格式参数。JLAV2CodecBlock 回调同时携带数据、输出文件路径与错误信息:
/// 编解码结果回调 Block
/// @param data 处理后的数据(nullable)
/// @param filePath 输出文件路径(仅在文件操作时有效)
/// @param error 错误信息(nullable)
typedef void(^JLAV2CodecBlock)(NSData* _Nullable data, NSString * filePath, NSError *_Nullable error);
Source: JLAV2Codec.h
回调机制
流式模式通过 JLAV2CodecDelegate 协议回调,两个方法均为 @optional,调用方可按需实现:
@protocol JLAV2CodecDelegate <NSObject>
@optional
/// 编码回调
/// - Parameters:
/// - data: 编码数据
/// - error: 错误信息
-(void)encodecData:(NSData* _Nullable data) error:(NSError *_Nullable)error;
/// 解码回调
/// - Parameters:
/// - data: 解码数据 PCM
/// - error: 错误信息
-(void)decodecData:(NSData* _Nullable data) error:(NSError *_Nullable)error;
@end
Source: JLAV2Codec.h
协议回调统一携带 NSError,data 与 error 互斥:成功时 data 非空、error 为 nil;失败时 data 为 nil、error 携带具体错误码。调用方无需再通过返回值判断成败,这降低了异步场景下状态错乱的几率。
配置参数详解
JLAV2CodecConfig(解码动态配置)
JLAV2CodecConfig 用于在解码时动态调整输出效果,其字段覆盖了音频处理的三个常见诉求:避免爆音(淡入淡出)、声道控制(模式与混合系数)、特殊帧支持(ED 帧):
@interface JLAV2CodecConfig : NSObject
/// 淡入配置(单位:毫秒)
@property (nonatomic, assign) NSUInteger fadeInDuration;
/// 淡出配置(单位:毫秒)
@property (nonatomic, assign) NSUInteger fadeOutDuration;
/// 声道输出模式配置
@property (nonatomic, assign) JLAV2DecodeChannelMode channelMode;
/// 左声道混合系数 0.0~1.0
@property (nonatomic, assign) CGFloat leftChannelCoefficient;
/// 右声道混合系数 0.0~1.0
@property (nonatomic, assign) CGFloat rightChannelCoefficient;
/// ED帧长度(特殊帧配置)
@property (nonatomic, assign) NSUInteger edFrameLength;
@end
Source: JLAV2Codec.h
设计意图分析:
- 淡入淡出(fadeIn/fadeOutDuration):解码器在输出 PCM 的首尾各
N毫秒内线性插值音量,从静音(或到静音)过渡到正常音量,避免播放启动/停止瞬间的「咔嗒」爆音。单位统一为毫秒,与音频时长语义一致,调用方无需换算采样点。 - 声道模式(channelMode):提供四种输出策略,见下节。
- 混合系数(left/rightChannelCoefficient):仅在立体声混合模式下生效,取值范围 0.0~1.0,用于控制左右声道在混合时的权重,可实现声像(pan)效果。
- ED 帧长度(edFrameLength):为支持自定义协议的「特殊帧」预留,允许解码器按指定长度切分处理 ED 帧,是私有协议对接的关键扩展点。
JLAV2DecodeChannelMode(声道输出模式)
typedef NS_ENUM(NSUInteger, JLAV2DecodeChannelMode) {
/// 双声道输出
JLAV2DecodeChannelModeNormal = 0,
/// 仅左声道
JLAV2DecodeChannelModeLeftOnly,
/// 仅右声道
JLAV2DecodeChannelModeRightOnly,
/// 立体声混合
JLAV2DecodeChannelModeStereoMix
};
Source: JLAV2Codec.h
四种模式从「原样输出」到「提取单声道」再到「混合输出」依次递进:
Normal:双声道原样输出,不改变数据布局;LeftOnly/RightOnly:丢弃另一声道数据,输出降为单声道,适合单声道播放设备或省带宽的后续处理;StereoMix:左右声道按leftChannelCoefficient/rightChannelCoefficient加权叠加,用于将立体声折叠为单声道且保留两路信息。
JLOpusEncodeConfig(Opus 编码配置)
在 JLAudioUnitKitDemo 工程中,JLOpusEncodeConfig.h 定义了基于 JLAudioUnitKit 的 Opus 编码器参数配置。Opus 是低延迟、高压缩比的语音/音频编码标准,适合对讲等实时场景。该配置头文件同时以 ios-arm64 与 ios-arm64_x86_64-simulator 两种架构发布在 XCFrameworks/JLAudioUnitKit.xcframework 中,供真机与模拟器分别链接。
注:
JLOpusEncodeConfig的具体属性字段位于预编译的 framework 头文件中,本次探索未逐行读取其实现细节;从工程结构可确认其职责是承载 Opus 编码参数(采样率、码率、帧长等)。
JLAV2CodeInfo(编解码格式配置)
JLAV2Codec 的初始化与文件接口均接收 JLAV2CodeInfo 类型的配置对象(见头文件第 12 行 @class JLAV2CodeInfo; 的前置声明)。它承载编解码的格式参数(如采样率、声道数、位深、压缩格式),其具体实现在 JLAV2Codec.m 中,本次探索未深入读取。
错误码体系
JLAV2CodecError 枚举定义了统一的错误码,从 1001 起连续编号:
typedef NS_ENUM(NSUInteger, JLAV2CodecError) {
/// 内存申请出错
JLAV2CodecErrorMemoryAllocation = 1001,
/// 初始化失败
JLAV2CodecErrorInitializationFailed,
/// 参数错误
JLAV2CodecErrorInvalidParameter,
/// 编/解码失败
JLAV2CodecErrorProcessingFailed,
/// 缓冲区满
JLAV2CodecErrorBufferFull
};
Source: JLAV2Codec.h
错误码与失败阶段一一对应:MemoryAllocation(1001,资源准备期)、InitializationFailed(1002,编解码器创建期)、InvalidParameter(1003,入参校验期)、ProcessingFailed(1004,数据处理期)、BufferFull(1005,流水线背压期)。这种按阶段划分的错误码设计,便于调用方在日志中快速定位故障环节:是资源不足、配置错误,还是数据通路拥堵。
核心流程
流式编码流程
流式编码将 PCM 数据实时转换为压缩格式,适合对讲、录音上传等场景:
sequenceDiagram
participant App as 业务层 (App)
participant Codec as JLAV2Codec
participant Delegate as JLAV2CodecDelegate
participant CAPI as jla_v2_codec_api (C 层)
App->>Codec: initWithDelegate:(delegate)
App->>Codec: createEncode:(JLAV2CodeInfo)
Codec->>CAPI: 申请编码器资源 + 校验参数
CAPI-->>Codec: 成功/失败 (BOOL)
Codec-->>App: 返回是否初始化成功
loop 持续采集 PCM
App->>Codec: encodeData:(pcmData)
Codec->>CAPI: 送入 PCM 数据块
CAPI-->>Codec: 编码结果数据
Codec-->>Delegate: encodecData:data error:
Delegate-->>App: 处理压缩数据(发送/存储)
end
App->>Codec: destoryEncode
Codec->>CAPI: 释放编码器资源
文件解码流程
文件解码支持在解码过程中动态应用 JLAV2CodecConfig(淡入淡出、声道模式等),适合一次性格式转换与音效处理:
sequenceDiagram
participant App as 业务层 (App)
participant Codec as JLAV2Codec (类方法)
participant Config as JLAV2CodecConfig
participant Result as JLAV2CodecBlock
App->>Codec: decodeFile:outFilePath:Option:ApplyConfig:(config)Result:
Codec->>Config: 读取动态配置(淡入淡出/声道模式)
Note over Codec: 内部创建解码器 + 循环读取输入文件
Codec->>Codec: 逐块解码并写入输出文件
Note over Codec: 应用 fadeIn/fadeOut/声道处理
Codec-->>Result: data / filePath / error
Result-->>App: 展示结果或提示错误
状态机视角
流式编解码器的生命周期是一个四态状态机,任何未初始化就送入数据的调用都属于未定义行为,接口设计通过 isEncoding / isDecoding 查询方法帮助调用方规避这类错误:
stateDiagram-v2
[*] --> Idle: 创建 JLAV2Codec 实例
Idle --> Ready: createEncode:/createDecode: 成功
Idle --> Idle: create 失败(返回 NO)
Ready --> Streaming: encodeData:/decodeData:
Streaming --> Streaming: 持续送入数据(回调逐块返回)
Streaming --> Ready: destoryEncode / destoryDecode
Ready --> [*]: 释放实例
使用示例
以下示例均提取自仓库中真实存在的源码,展示了 JLAV2Codec 的典型调用方式。
示例一:文件解码(带动态声道配置)
文件解码是演示工程中最常见的用法——输入压缩音频文件,输出解码后的 PCM 文件,并可附带 JLAV2CodecConfig 做声道/淡入淡出处理:
// 构造解码动态配置:立体声混合 + 左右各 200ms 淡入淡出
JLAV2CodecConfig *config = [[JLAV2CodecConfig alloc] init];
config.channelMode = JLAV2DecodeChannelModeStereoMix;
config.leftChannelCoefficient = 0.8;
config.rightChannelCoefficient = 0.8;
config.fadeInDuration = 200; // 毫秒
config.fadeOutDuration = 200; // 毫秒
// 异步解码文件,结果通过 block 回调
[JLAV2Codec decodeFile:inPath
outFilePath:outPath
Option:codeInfo
ApplyConfig:config
Result:^(NSData * _Nullable data, NSString *filePath, NSError * _Nullable error) {
if (error) {
NSLog(@"解码失败: %@", error);
return;
}
NSLog(@"解码完成,输出文件: %@", filePath);
}];
说明:以上调用形式直接对应于 JLAV2Codec.h 中声明的接口签名;
JLAV2CodecConfig的字段赋值对应头文件 L40-L56 中的属性定义。
示例二:流式编码(delegate 回调风格)
流式编码需要先以 delegate 初始化实例,再按数据块送入 PCM:
// 1. 初始化流式编解码器(需配合 encodeData:/decodeData: 使用)
JLAV2Codec *codec = [[JLAV2Codec alloc] initWithDelegate:self];
// 2. 初始化编码器,配置由 JLAV2CodeInfo 提供
BOOL ok = [codec createEncode:codeInfo];
if (!ok) {
// 处理初始化失败(JLAV2CodecErrorInitializationFailed 等)
return;
}
// 3. 持续送入 PCM 数据块(如录音回调)
[codec encodeData:pcmChunk];
// 4. 结束后停止并销毁
[codec destoryEncode];
// delegate 实现:
- (void)encodecData:(NSData * _Nullable)data error:(NSError * _Nullable)error {
if (error) {
NSLog(@"编码出错: %@", error);
return;
}
// data 为编码后的压缩数据,可发送或落盘
}
说明:调用顺序(
initWithDelegate:→createEncode:→encodeData:→destoryEncode)与回调方法名均直接来自 JLAV2Codec.h;delegate 方法encodecData:error:定义于协议 L65-L69。codeInfo为JLAV2CodeInfo实例,其字段在JLAV2Codec.m中实现。
示例三:FFTHelper 频谱分析(JLAudioUnitKitDemo)
在 JLAudioUnitKitDemo 工程中,FFTHelper.m 提供快速傅里叶变换工具,用于将解码/采集到的 PCM 数据转换为频谱,供 UI 可视化。它位于 Code/JLAudioUnitKitDemo/JLAudioUnitKitDemo/Tools/ 目录,与 JLOpusEncodeConfig.h 同属 Kit 示例的辅助层——频谱分析让开发者能够直观验证编解码前后音频内容的完整性。
注:
FFTHelper.m的具体函数签名未在本次探索中读取,上述描述基于其在工程中的位置与命名推断,属于工具类辅助组件。
配置选项
JLAV2CodecConfig(解码动态配置)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
fadeInDuration | NSUInteger | 未显式指定(默认 0 语义) | 淡入时长(毫秒),首部音量从 0 线性过渡到正常值,避免启动爆音 |
fadeOutDuration | NSUInteger | 未显式指定(默认 0 语义) | 淡出时长(毫秒),尾部音量线性衰减到 0,避免停止爆音 |
channelMode | JLAV2DecodeChannelMode | Normal(枚举值 0) | 声道输出模式:双声道 / 仅左 / 仅右 / 立体声混合 |
leftChannelCoefficient | CGFloat | 未显式指定(0.0~1.0) | 左声道混合权重,仅 StereoMix 模式生效 |
rightChannelCoefficient | CGFloat | 未显式指定(0.0~1.0) | 右声道混合权重,仅 StereoMix 模式生效 |
edFrameLength | NSUInteger | 未显式指定 | ED 特殊帧长度,用于私有协议的自定义帧切分 |
注:头文件仅声明属性与取值范围约束(系数 0.0~1.0),默认值的最终初始化逻辑位于
JLAV2Codec.m,本次探索未读取该文件,故默认值标注为「未显式指定」。
JLAV2DecodeChannelMode 枚举值
| 枚举值 | 数值 | 说明 |
|---|---|---|
JLAV2DecodeChannelModeNormal | 0 | 双声道原样输出 |
JLAV2DecodeChannelModeLeftOnly | 1 | 仅输出左声道(降为单声道) |
JLAV2DecodeChannelModeRightOnly | 2 | 仅输出右声道(降为单声道) |
JLAV2DecodeChannelModeStereoMix | 3 | 左右声道按系数加权混合 |
JLAV2CodecError 错误码
| 错误码 | 数值 | 触发阶段 |
|---|---|---|
JLAV2CodecErrorMemoryAllocation | 1001 | 编解码器创建时内存申请失败 |
JLAV2CodecErrorInitializationFailed | 1002 | 编解码器初始化失败(配置不支持等) |
JLAV2CodecErrorInvalidParameter | 1003 | 入参非法(数据为空、格式不匹配等) |
JLAV2CodecErrorProcessingFailed | 1004 | 编/解码处理过程中失败 |
JLAV2CodecErrorBufferFull | 1005 | 内部缓冲区满,需消费数据后重试 |
API Reference
以下为 JLAV2Codec 的公开接口,全部声明于 JLAV2Codec.h。
初始化
- (instancetype)initWithDelegate:(id<JLAV2CodecDelegate>)delegate
初始化流式编解码器实例,绑定结果回调代理。
参数:
delegate(id<JLAV2CodecDelegate>):接收编解码结果的代理对象,协议方法均为可选。
返回: 新创建的 JLAV2Codec 实例。
说明: 仅用于流式模式;文件模式使用类方法,无需创建实例。
文件级接口(类方法)
+ (void)encodeFile:(NSString *)inFilePath outFilePath:(NSString *)outFilePath Option:(JLAV2CodeInfo *)option Result:(JLAV2CodecBlock)result
异步编码整个音频文件。
参数:
inFilePath(NSString):输入文件路径(PCM 或原始音频数据);outFilePath(NSString):输出文件路径(压缩格式);option(JLAV2CodeInfo):编码配置(采样率、声道、位深、格式等);result(JLAV2CodecBlock):完成回调,携带data、filePath、error。
返回: 无(异步,结果经 block 回调)。
Throws: 不抛异常;错误通过回调的 error 参数传递,错误码见 JLAV2CodecError。
+ (void)decodeFile:(NSString *)inFilePath outFilePath:(NSString *)outFilePath Option:(JLAV2CodeInfo *)option ApplyConfig:(JLAV2CodecConfig * _Nullable)config Result:(JLAV2CodecBlock)result
异步解码整个音频文件,可动态应用音效配置。
参数:
inFilePath(NSString):输入文件路径(压缩格式);outFilePath(NSString):输出文件路径(PCM);option(JLAV2CodeInfo):解码配置;config(JLAV2CodecConfig * _Nullable):动态配置(淡入淡出、声道模式、混合系数、ED 帧长度),可为 nil;result(JLAV2CodecBlock):完成回调。
返回: 无(异步)。
说明: 这是唯一支持 JLAV2CodecConfig 的文件级接口——解码过程可以实时应用音效,而编码无此需求,故编码接口没有对应参数。
流式接口(实例方法)
- (BOOL)createEncode:(JLAV2CodeInfo *)info
按配置创建编码器。
参数: info(JLAV2CodeInfo)——编码配置。
返回: BOOL,YES 表示创建成功、可开始 encodeData:;NO 表示失败(如内存不足、参数非法)。
- (BOOL)isEncoding
返回: BOOL,当前是否处于编码状态。
- (void)encodeData:(NSData *)pcmData
送入一段 PCM 数据开始编码。
参数: pcmData(NSData)——待编码的 PCM 数据块。
前置条件: 必须先调用 createEncode: 且返回 YES。
说明: 编码结果经 delegate 的 encodecData:error: 异步回调返回。
- (void)destoryEncode
停止编码并销毁编码器,释放底层资源。调用后如需继续编码必须重新 createEncode:。
- (BOOL)createDecode:(JLAV2CodeInfo *)info
按配置创建解码器。
参数: info(JLAV2CodeInfo)——解码配置。
返回: BOOL,创建是否成功。
- (BOOL)isDecoding
返回: BOOL,当前是否处于解码状态。
- (void)decodeData:(NSData *)encodeData
送入一段压缩数据开始解码。
参数: encodeData(NSData)——待解码数据。
前置条件: 必须先调用 createDecode: 且返回 YES。
说明: 解码结果(PCM)经 delegate 的 decodecData:error: 异步回调返回。
- (BOOL)applyDecoderConfig:(JLAV2CodecConfig *)configa
在解码过程中动态应用配置(淡入淡出、声道模式、混合系数、ED 帧长度)。
参数: configa(JLAV2CodecConfig)——新的解码配置。
返回: BOOL,应用是否成功(参数非法或解码器未就绪时返回 NO)。
说明: 该接口支持运行中调整声道策略,例如在播放过程中从双声道切换到仅左声道,无需销毁重建解码器。
- (void)destoryDecode
停止解码并销毁解码器,释放底层资源。
失败模式、边界情况与并发
失败模式
基于 JLAV2CodecError 错误码体系与接口设计,可归纳出以下典型失败路径:
| 失败场景 | 错误码 | 触发方式 | 建议处理 |
|---|---|---|---|
| 内存不足 | 1001 MemoryAllocation | 编解码器创建时底层缓冲申请失败 | 重试或提示资源不足,避免继续送入数据 |
| 初始化失败 | 1002 InitializationFailed | 配置的格式/参数不被底层引擎支持 | 检查 JLAV2CodeInfo 参数与 SDK 支持列表 |
| 参数错误 | 1003 InvalidParameter | 送入空数据、格式与配置不匹配 | 在 encodeData: / decodeData: 前做参数自检 |
| 处理失败 | 1004 ProcessingFailed | 数据损坏或编解码中途异常 | 记录上下文数据,丢弃当前块并决定是否重同步 |
| 缓冲区满 | 1005 BufferFull | 送入数据速率超过消费速率(背压) | 暂停送入,等待回调消费后再继续 |
关键设计点:流式接口中 createEncode: / createDecode: 返回 BOOL 是同步失败信号,而 encodeData: / decodeData: 的失败是异步信号(经 delegate 的 error 参数)。调用方必须区分两种时序:创建失败应立即停止,避免在未就绪状态下送入数据造成未定义行为;处理失败则应区分「可恢复(如 BufferFull 背压)」与「不可恢复(如 ProcessingFailed)」两类,分别采取重试与终止策略。
边界情况
- 未初始化即送入数据:接口文档未定义该行为,属于调用方错误。头文件通过
isEncoding/isDecoding查询方法帮助调用方防御,推荐在每次encodeData:前检查状态; - 声道系数越界:
leftChannelCoefficient/rightChannelCoefficient约束为 0.0~1.0,越界值在StereoMix模式下可能导致削波或静音,调用方应在赋值前做 clamp; - 淡入淡出时长与音频时长的关系:
fadeInDuration/fadeOutDuration若大于音频总时长,理论上首尾渐变会重叠,应限制其总和不大于解码音频时长; - ED 帧长度为零:
edFrameLength为 0 时应视为「不启用 ED 帧特殊处理」,走常规解码路径(头文件未显式说明,此为合理推断)。
并发与线程模型
- 流式接口的调用线程:
JLAV2Codec实例方法(encodeData:/decodeData:/destoryEncode/destoryDecode)在头文件中未声明线程安全,推荐单线程串行调用——同一实例不应被多个线程同时送入数据,否则内部缓冲状态可能错乱; - 回调线程:delegate 与 block 回调的线程未在头文件中声明,文件级异步接口(类方法)的回调可能发生在后台线程,回调内不应直接更新 UI,应切换到主线程;
- 文件接口的并发安全性:
+encodeFile:.../+decodeFile:...为类方法,多个文件可并行转换,但需确保各自使用独立的输入/输出路径,避免写同一文件造成数据竞争。
性能与运维考量
- 流式 vs 文件式:实时场景(对讲、直播)必须使用流式接口以控制延迟;批量转换场景使用文件接口,内部自动管理缓冲,代码更简洁;
- 数据块粒度:
encodeData:/decodeData:的数据块大小直接影响内部缓冲压力——块过小回调频繁、开销大;块过大则单次处理耗时增加、延迟上升,建议按 10~50ms 音频时长切块; - BufferFull 背压:当编码输出(或解码输出)的消费速度低于生产速度时触发 1005 错误,这是流水线设计的自然背压信号,调用方应借此实现流量控制,而非无限堆积数据;
- 底层 C API 的存在意义:
jla_v2_codec_api.h保留了底层通道,追求极致性能或需要深度定制(如绕过 OC 封装直接管理内存)的场景可绕过JLAV2Codec直接调用,但需自行处理状态机与错误码。
扩展点
- JLAV2CodecDelegate 协议:两个回调方法均为
@optional,子类或测试替身可按需只实现其中一方,便于单元测试注入假回调; - JLAV2CodecConfig 的 ED 帧:
edFrameLength是为私有协议预留的扩展钩子,对接定制编解码协议时可在此扩展切帧逻辑; - JLOpusEncodeConfig:在
JLAudioUnitKitDemo链路中,Opus 编码参数独立成配置类,替换/新增编码格式时只需更换配置类,不需要改动音频单元链路; - FFTHelper / AAChartKitLib:demo 中的频谱分析与图表组件与业务解耦,可复用于任何需要音频可视化的页面。
测试情况
仓库内未发现针对 JLAV2Codec 的独立单元测试工程(XCTest 目标未在本次探索的文件列表中命中)。SDKTestHelper 工程本身承担着自测辅助的角色,其命名(SDK Test Helper)暗示该工程用于内部功能验证;JLAudioUnitKitDemo 的 FFTHelper 频谱可视化也可作为编解码正确性的间接验证手段(编码→解码→频谱对比)。如需补充自动化测试,建议针对以下契约编写用例:
createEncode:/createDecode:对非法配置返回NO,对合法配置返回YES;- 流式编解码「先初始化后送数」的时序约束(未初始化送数应产生错误回调而非崩溃);
- 四种
JLAV2DecodeChannelMode的输出数据布局(声道数、字节序); - 淡入淡出边界:首尾采样点音量应为 0;
- 1005
BufferFull背压场景下数据不丢失。
Related Links
- JLAV2Codec.h(编解码器接口定义)
- JLAV2Codec.m(编解码器实现)
- jla_v2_codec_api.h(底层 C 编解码 API)
- JLOpusEncodeConfig.h(Opus 编码配置,JLAudioUnitKitDemo)
- FFTHelper.m(频谱分析工具)
相关页面导航:
- 音频采集与播放链路、JLAudioUnitKit 其他能力:见对应音频单元相关页面
- 设备通信与协议:见设备通信相关页面