JLAudioUnitKit 示例工程
JLAudioUnitKit 是杰理科技(ZhuHai JieLi Technology)提供的 iOS 音频处理框架(以 XCFramework 二进制形式分发),本页介绍其配套示例工程 JLAudioUnitKitDemo 的结构、框架公共 API 面、播放器双模式工作机制与集成方式。
Purpose and Scope
本页覆盖以下内容:
JLAudioUnitKit框架的二进制分发形态(XCFramework 双架构布局)与公共头文件所暴露的全部模块。- 示例工程
code/JLAudioUnitKitDemo的工程结构、构建产物与 CocoaPods 集成方式。 - 核心类
JLAudioUnitPlayer的两种播放模式(文件播放 / PCM 流播放)、委托协议与完整 API。 - 播放器生命周期、核心调用流程、典型使用代码、配置项与失败模式。
以下内容不在本页范围,请参见应用架构目录下的其他页面:
- 杰理全功能示例 App
JieLi_Home_Demo的完整应用架构(本仓库另一示例工程)。 - BLE 蓝牙协议栈与设备交互细节(属于连接与传输层能力)。
概述
JLAudioUnitKit 封装了 iOS 底层音频能力(AudioToolbox / AVAudioPlayer / Audio Queue),为上层业务提供统一的音频播放与格式转换入口。它解决的问题是:在杰理设备的音频场景中(例如从蓝牙设备接收 PCM 语音、播放本地提示音文件、语音编解码),开发者无需直接面对 AudioQueueRef、AudioStreamBasicDescription、编解码器细节,而是通过一个轻量 Objective-C 对象完成播放控制与数据喂入。
框架由 EzioChan 创建(JLAudioUnitKit.h),公开模块分为三类:
- 播放器:
JLAudioUnitPlayer—— 支持文件播放(MP3/WAV/AAC)与 PCM 流式播放。 - 编解码器:
JLSpeexDecoder、JLOpusDecoder、JLOpusEncoder及格式/配置类型。 - 格式转换器:
JLPcmToWtg、JLPcmToWav、JLOpusToOgg、JLAudioConverter。
示例工程 JLAudioUnitKitDemo 位于 code/JLAudioUnitKitDemo/Code/JLAudioUnitKitDemo/,通过 CocoaPods 接入框架,以 Release-iphoneos 配置构建,展示了如何在真实 App 中链接并使用该框架。
架构
flowchart TD
subgraph sg_Demo["JLAudioUnitKitDemo 示例工程"]
App["JLAudioUnitKitDemo App"]
VC["ViewController(UI 层)"]
Pods["CocoaPods 集成<br/>Pods-JLAudioUnitKitDemo"]
end
subgraph sg_Kit["JLAudioUnitKit(XCFramework)"]
Player["JLAudioUnitPlayer"]
Codec["编解码器<br/>Opus / Speex"]
Convert["格式转换器<br/>PcmToWav / PcmToWtg / OpusToOgg / AudioConverter"]
end
subgraph sg_System["系统框架"]
AVAudioPlayer["AVAudioPlayer(文件模式)"]
AudioQueue["Audio Queue(PCM 模式)"]
end
App --> VC
VC --> Pods
Pods --> Player
VC --> Codec
VC --> Convert
Player --> AVAudioPlayer
Player --> AudioQueue
架构说明:
- 上层:示例工程以 App 形式存在,
ViewController负责 UI 与用户交互;框架通过 CocoaPods 静态/动态链接进 App。 - 中间层:
JLAudioUnitKit以xcframework二进制分发,公共 API 由 10 个头文件暴露(见 JLAudioUnitKit.h)。JLAudioUnitPlayer是播放相关能力的唯一入口。 - 底层:文件模式内部使用
AVAudioPlayer(对应枚举注释JLAudioUnitPlayerTypeFile),PCM 流模式内部使用 Audio Queue(JLAudioUnitPlayerTypePCM),见 JLAudioUnitPlayer.h。
框架结构与公共模块
XCFramework 二进制布局
框架以 Apple XCFramework 格式分发,位于 code/JieLi_Home_Demo/JLAudioUnitKit.xcframework/,包含两个平台切片:
| 切片 | 平台 | 说明 |
|---|---|---|
ios-arm64/JLAudioUnitKit.framework | 真机(iOS arm64) | 真机调试/发布链接 |
ios-x86_64-simulator/JLAudioUnitKit.framework | 模拟器(x86_64) | Intel Mac 模拟器链接 |
两个切片暴露相同的一组公共头文件,其中 JLAudioUnitKit.h 是伞头(umbrella header),集中导入全部公共模块:
#import <JLAudioUnitKit/JLSpeexDecoder.h>
#import <JLAudioUnitKit/JLOpusDecoder.h>
#import <JLAudioUnitKit/JLOpusEncoder.h>
#import <JLAudioUnitKit/JLOpusFormat.h>
#import <JLAudioUnitKit/JLOpusEncodeConfig.h>
#import <JLAudioUnitKit/JLPcmToWtg.h>
#import <JLAudioUnitKit/JLPcmToWav.h>
#import <JLAudioUnitKit/JLAudioUnitPlayer.h>
#import <JLAudioUnitKit/JLOpusToOgg.h>
#import <JLAudioUnitKit/JLAudioConverter.h>
Source: JLAudioUnitKit.h
公共模块一览
| 模块 | 类/类型 | 职责 |
|---|---|---|
| 播放器 | JLAudioUnitPlayer | 文件播放(MP3/WAV/AAC)与 PCM 流播放 |
| 解码器 | JLSpeexDecoder | Speex 语音解码 |
| 解码器 | JLOpusDecoder | Opus 语音解码 |
| 编码器 | JLOpusEncoder | Opus 语音编码 |
| 格式定义 | JLOpusFormat | Opus 格式描述类型 |
| 编码配置 | JLOpusEncodeConfig | Opus 编码参数配置 |
| 转换器 | JLPcmToWtg | PCM → WTG(杰理私有音频格式) |
| 转换器 | JLPcmToWav | PCM → WAV |
| 转换器 | JLOpusToOgg | Opus → Ogg 封装 |
| 转换器 | JLAudioConverter | 通用音频转换入口 |
设计意图:框架将「播放」与「编解码/转换」拆分为独立模块,业务侧可按需组合——例如先 JLOpusDecoder 解码蓝牙收到的 Opus 数据,再 appendPCMData: 喂给 JLAudioUnitPlayer 的 PCM 模式播放;或先用 JLPcmToWav 把 PCM 转成 WAV 文件再走文件播放。
版本信息
框架在伞头中导出版本常量(JLAudioUnitKitVersionNumber 与 JLAudioUnitKitVersionString),供运行时读取框架版本,便于排障与兼容性判断,见 JLAudioUnitKit.h。
示例工程结构
示例工程位于 code/JLAudioUnitKitDemo/Code/JLAudioUnitKitDemo/,仓库内可见其构建产物目录 build/JLAudioUnitKitDemo.build/Release-iphoneos/,从产物可确认以下集成事实:
- 构建配置:以
Release-iphoneos为目标配置,产物为JLAudioUnitKitDemo.app(含.xcent签名授权文件,说明工程启用了代码签名)。 - CocoaPods 集成:构建产物中出现
Pods-JLAudioUnitKitDemo-frameworks-Release-input-files-*.xcfilelist与checkManifestLockResult.txt,证明示例工程使用 CocoaPods 管理依赖(Pod 名为JLAudioUnitKitDemo),框架通过 Pods 的 frameworks 脚本阶段链接进 App。 - 头文件映射:
*-all-target-headers.hmap、*-project-headers.hmap等 hmap 文件表明 Xcode 使用头映射加速编译,JLAudioUnitKitDemo是唯一的 target。
说明:本次文档生成受探索预算限制,未直接读取示例工程的
ViewController/AppDelegate 源码文件;上表结论来自构建产物文件名的可验证证据。示例工程的说明文档位于code/JLAudioUnitKitDemo/Code/README.md。
JLAudioUnitPlayer 详解
JLAudioUnitPlayer 是整个框架中与「播放」相关的最核心类,定义于 JLAudioUnitPlayer.h。它通过两种初始化方式确定内部引擎:
双模式设计
typedef NS_ENUM(NSInteger, JLAudioUnitPlayerType) {
JLAudioUnitPlayerTypeFile, // 支持 MP3/WAV/AAC 等文件格式(AVAudioPlayer)
JLAudioUnitPlayerTypePCM // 支持 PCM 流式播放(Audio Queue)
};
Source: JLAudioUnitPlayer.h
- 文件模式(
initWithAudioFile:):传入本地文件路径,支持 MP3/WAV/AAC,内部由AVAudioPlayer驱动,天然支持进度查询、跳转(seekToTime:)与时长读取。 - PCM 模式(
initWithPCMFormat:):传入AudioStreamBasicDescription描述采样率、位深、声道数等格式,内部由 Audio Queue 驱动,播放数据通过appendPCMData:持续喂入,适合实时语音流(如蓝牙传输的 PCM 数据)。
设计意图:两种模式共用同一套播放控制接口(play/pause/stop)与委托回调,业务层无需关心底层引擎差异;而模式相关的差异能力(文件模式的 duration/seekToTime:,PCM 模式的 appendPCMData:/endPCMStream)通过注释明确标注适用模式,避免误用。
委托协议
@protocol JLAudioPlayerDelegate <NSObject>
@optional
// 播放进度更新(秒)
- (void)audioPlayer:(JLAudioUnitPlayer *)player didUpdateProgress:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration;
// 播放完成
- (void)audioPlayerDidFinishPlaying:(JLAudioUnitPlayer *)player;
// 播放错误
- (void)audioPlayer:(JLAudioUnitPlayer *)player didFailWithError:(NSError *)error;
@end
Source: JLAudioUnitPlayer.h
三个回调全部为 @optional,且委托属性声明为 weak(@property (nonatomic, weak) id<JLAudioPlayerDelegate> delegate;),避免循环引用——这是 iOS 委托模式的经典约定:播放器持有委托的弱引用,生命周期由外部(通常是 ViewController)管理。
公开属性
| 属性 | 类型 | 说明 |
|---|---|---|
delegate | id<JLAudioPlayerDelegate>(weak) | 播放回调委托 |
duration | NSTimeInterval(readonly) | 总时长,仅文件模式有效 |
currentTime | NSTimeInterval(readonly) | 当前播放位置(秒) |
isPlaying | BOOL(readonly) | 是否正在播放 |
播放器状态机
stateDiagram-v2
[*] --> Idle
Idle --> Playing: play()
Playing --> Paused: pause()
Paused --> Playing: play()
Playing --> Stopped: stop()
Paused --> Stopped: stop()
Stopped --> Playing: play()
Playing --> Finished: 播放完成 / endPCMStream()
Finished --> [*]
Idle:初始化完成、尚未播放;stop()后回到可重新播放的Stopped态。Playing/Paused:由play/pause切换,PCM 模式下暂停时 Audio Queue 暂停消费数据。Finished:文件模式播完或 PCM 模式调用endPCMStream结束流后,触发audioPlayerDidFinishPlaying:回调。
核心流程
文件播放流程
sequenceDiagram
participant VC as ViewController
participant P as JLAudioUnitPlayer
participant AP as AVAudioPlayer
VC->>P: initWithAudioFile: (MP3/WAV/AAC 路径)
VC->>P: play()
P->>AP: 创建并启动 AVAudioPlayer
loop 播放中
P-->>VC: audioPlayer:didUpdateProgress:duration:
end
AP-->>P: 播放结束
P-->>VC: audioPlayerDidFinishPlaying:
PCM 流播放流程
sequenceDiagram
participant VC as ViewController
participant P as JLAudioUnitPlayer
participant AQ as Audio Queue
participant SRC as 数据源(蓝牙/编解码器)
VC->>P: initWithPCMFormat: (AudioStreamBasicDescription)
VC->>P: play()
P->>AQ: 启动 Audio Queue 播放
loop 持续接收 PCM 数据
SRC->>VC: PCM 数据包
VC->>P: appendPCMData:
P->>AQ: 填充播放缓冲区
end
VC->>P: endPCMStream()
P-->>VC: audioPlayerDidFinishPlaying:
关键点:PCM 模式是「推」模型——播放器本身不产生数据,业务侧(如蓝牙接收回调、JLOpusDecoder 解码回调)必须持续调用 appendPCMData: 喂数;数据不足会出现卡顿/停顿,数据停止后必须调用 endPCMStream 让播放器知道流已结束,否则不会触发完成回调。
使用示例
以下示例均基于框架公共头文件中的真实声明整理,展示典型调用方式。
文件模式播放本地提示音
// 1. 初始化:传入本地音频文件路径(MP3/WAV/AAC)
JLAudioUnitPlayer *player = [[JLAudioUnitPlayer alloc] initWithAudioFile:audioFilePath];
// 2. 设置委托以接收进度/完成/错误回调
player.delegate = self;
// 3. 播放控制
[player play]; // 开始播放
[player pause]; // 暂停
[player seekToTime:30.0]; // 跳转到 30 秒(仅文件模式)
[player stop]; // 停止
Source: JLAudioUnitPlayer.h
PCM 流模式播放实时语音
// 1. 声明 PCM 格式(采样率/位深/声道)
AudioStreamBasicDescription format = {0};
format.mSampleRate = 16000; // 16kHz 采样率(示例值)
format.mFormatID = kAudioFormatLinearPCM;
format.mChannelsPerFrame = 1; // 单声道
// ... 按实际音频源填充 mBitsPerChannel / mBytesPerFrame 等字段
// 2. 初始化 PCM 播放器
JLAudioUnitPlayer *player = [[JLAudioUnitPlayer alloc] initWithPCMFormat:format];
player.delegate = self;
[player play];
// 3. 数据到达时持续喂数(例如蓝牙回调 / Opus 解码输出)
[player appendPCMData:pcmData];
// 4. 数据流结束时通知播放器
[player endPCMStream];
Source: JLAudioUnitPlayer.h
实现委托回调
// 进度更新(秒)——可用于更新 UI 进度条
- (void)audioPlayer:(JLAudioUnitPlayer *)player didUpdateProgress:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration {
// duration 仅文件模式有效
self.progressView.progress = (duration > 0) ? currentTime / duration : 0;
}
// 播放完成——清理资源、切换到下一首
- (void)audioPlayerDidFinishPlaying:(JLAudioUnitPlayer *)player {
[self nextTrack];
}
// 播放错误——提示用户并恢复状态
- (void)audioPlayer:(JLAudioUnitPlayer *)player didFailWithError:(NSError *)error {
NSLog(@"Audio play failed: %@", error.localizedDescription);
}
Source: JLAudioUnitPlayer.h
配置选项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
delegate | id<JLAudioPlayerDelegate>(weak) | nil | 播放回调委托,未设置则无进度/完成/错误通知 |
| PCM 格式 | AudioStreamBasicDescription | 调用方指定 | initWithPCMFormat: 的入参,决定 Audio Queue 的采样率、位深、声道数;需与实际数据严格一致 |
| 音频文件路径 | NSString | 调用方指定 | initWithAudioFile: 的入参,需指向本地存在的 MP3/WAV/AAC 文件 |
seekToTime: | NSTimeInterval | — | 跳转目标时间点,仅文件模式有效 |
配置方式说明:JLAudioUnitPlayer 采用「初始化即定模式」的设计——通过两个不同初始化方法决定播放引擎,之后不可切换;业务侧需要切换文件/流播放时必须重新创建实例。这与 CocoaPods 集成时的 Pod 配置(Pods-JLAudioUnitKitDemo)相互独立,框架本身没有全局配置文件。
API 参考
- (instancetype)initWithAudioFile:(NSString *)filePath
创建文件模式播放器(MP3/WAV/AAC)。
参数:
filePath(NSString):本地音频文件路径。
返回: 初始化后的 JLAudioUnitPlayer 实例。
- (instancetype)initWithPCMFormat:(AudioStreamBasicDescription)format
创建 PCM 流模式播放器,内部使用 Audio Queue。
参数:
format(AudioStreamBasicDescription):PCM 数据的格式描述(采样率、位深、声道等)。
返回: 初始化后的 JLAudioUnitPlayer 实例。
- (void)play
开始或恢复播放(两种模式通用)。从 Paused/Stopped 状态可再次调用。
- (void)pause
暂停播放,保留播放位置;PCM 模式下暂停 Audio Queue 的数据消费。
- (void)stop
停止播放并释放内部播放资源;文件模式下次 play 从头开始。
- (void)seekToTime:(NSTimeInterval)time
跳转到指定时间点。仅文件模式有效;PCM 模式下调用无意义。
参数:
time(NSTimeInterval):目标时间(秒)。
- (void)appendPCMData:(NSData *)pcmData
向 PCM 播放器喂入数据。仅 PCM 模式有效,需在 play 之后持续调用。
参数:
pcmData(NSData):PCM 音频数据块,格式须与初始化时声明的AudioStreamBasicDescription一致。
- (void)endPCMStream
结束 PCM 流输入,通知播放器数据已全部送达;之后播放器消费完缓冲数据即触发 audioPlayerDidFinishPlaying:。
委托方法(JLAudioPlayerDelegate,全部 @optional)
| 方法 | 触发时机 | 参数说明 |
|---|---|---|
audioPlayer:didUpdateProgress:duration: | 播放进度周期性更新 | currentTime 当前秒数;duration 总时长(仅文件模式有意义) |
audioPlayerDidFinishPlaying: | 播放完成(文件播完 / PCM 流结束) | player 播放器实例 |
audioPlayer:didFailWithError: | 播放过程出错 | error 具体错误信息 |
失败模式、边界情况与并发
播放错误
- 文件不存在/格式不支持:
initWithAudioFile:传入无效路径或非 MP3/WAV/AAC 格式时,播放启动失败,通过audioPlayer:didFailWithError:通知调用方。业务侧应在回调中提示用户并复位 UI 状态。 - PCM 格式不匹配:
initWithPCMFormat:声明的AudioStreamBasicDescription与实际喂入数据不一致(如采样率不同)会导致播放噪声或 Audio Queue 报错。这是 PCM 模式最常见的坑,必须在数据源侧保证格式一致。
模式误用边界
框架通过注释明确区分两种模式的专属能力,但 Objective-C 无编译期强制,误用只会产生无效行为而非崩溃:
- PCM 模式下调用
seekToTime:、读取duration无意义(duration仅文件模式有效)。 - 文件模式下调用
appendPCMData:/endPCMStream无效。
并发与线程
- 委托属性为
weak,若持有播放器的控制器在播放中释放,委托回调不会再触发——业务侧需在dealloc或viewWillDisappear中stop并置空 delegate。 - PCM 模式是典型的「生产-消费」模型:数据生产(蓝牙回调/解码线程)与音频消费(Audio Queue 实时线程)速率不同步。喂数过快会堆积缓冲(内存与延迟上升),过慢则导致卡顿/断音。框架未在头文件中暴露缓冲水位查询 API,业务侧需自行做流量控制(如按包间隔喂数)。
- 回调线程:头文件未声明委托回调所在线程,
didUpdateProgress:可能来自音频线程。若需更新 UI,建议在回调内dispatch_async到主队列,避免 UIKit 跨线程访问。
生命周期
stop后文件模式重新play从头播放;PCM 模式endPCMStream之后播放器进入完成态,如需再次播放应重新创建实例(或复用前先stop)。
性能与运维
- 底层引擎选择:文件模式使用
AVAudioPlayer(系统级、省电、支持硬件解码 AAC),PCM 模式使用 Audio Queue(低延迟、适合实时流)。对 CPU 敏感的语音场景(如对讲)优先走 PCM 模式并配合JLOpusDecoder/JLSpeexDecoder解码,可显著降低蓝牙传输带宽。 - 缓冲管理:PCM 模式下持续
appendPCMData:而不消费,会造成内存线性增长;结束会话必须调用endPCMStream,否则播放器无法触发完成回调且资源不释放。 - 版本管理:通过
JLAudioUnitKitVersionNumber/JLAudioUnitKitVersionString可在运行时校验框架版本,升级 XCFramework 后建议回归验证两种播放模式。 - 构建分发:示例工程以 Release-iphoneos 构建并含
.xcent签名授权,说明接入方需自行配置签名;xcframework已包含 arm64 与 x86_64-simulator 双切片,真机与模拟器可直接链接。
扩展点
- 自定义数据源:PCM 模式天然支持任意音频数据源——只要把数据以
NSData形式appendPCMData:喂入即可,可对接蓝牙串口、UDP 音频、解码器输出等。 - 编解码组合:
JLOpusDecoder、JLSpeexDecoder的解码输出(PCM)可直接接入JLAudioUnitPlayer的 PCM 模式,形成「蓝牙 → 解码 → 播放」完整链路;JLPcmToWav等转换器可将 PCM 落盘为 WAV,再走文件模式播放。 - UI 集成:通过
didUpdateProgress:duration:回调实现进度条/时间显示;通过完成/错误回调实现播放列表自动切换。
测试
本次文档生成受探索预算限制,未在仓库中发现针对 JLAudioUnitKit 或示例工程的可读测试源码(JLAudioUnitKitDemo 目录下可见的仅为构建产物)。框架以二进制形式分发,测试覆盖情况需联系杰理科技获取。建议接入方自行补充冒烟用例:分别用文件模式播放 MP3/WAV/AAC 各一个样本,PCM 模式播放已知采样率数据并验证进度回调与完成回调。
相关链接
- JLAudioUnitPlayer.h(框架播放器公共头文件)
- JLAudioUnitKit.h(框架伞头文件)
- 示例工程 README
- 示例工程构建目录(Release-iphoneos)
- 应用架构目录下的其他页面:
JieLi_Home_Demo全功能示例 App 架构(同仓库code/JieLi_Home_Demo)