Files
keyboard/keyBoard/Class/AiTalk/AI技术分析.txt

522 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

服务 用途 示例格式
ASR 服务器 语音识别WebSocket wss://api.example.com/asr
LLM 服务器 AI 对话HTTP SSE https://api.example.com/chat
TTS 服务器 语音合成 https://api.example.com/tts
iOSObjective-CiOS 15+)端技术实现文档
低延迟流式语音陪伴聊天(按住说话,类似猫箱首页)
0. 范围与目标
实现首页语音陪伴对话:
按住说话:开始录音并实时流式发送到 ASR
松开结束ASR 立即 finalize返回最终文本并显示
AI 回复:边显示文字(打字机效果)边播放服务端 TTS 音频
延迟低优先:不等待完整回答/完整音频,采用“分句触发 + 流式/准流式播放”
打断Barge-inAI 正在播报时用户再次按住 → 立即停止播报/取消请求,进入新一轮录音
iOS 最低版本iOS 15
1. 总体架构(客户端模块)
KBAiMainVC
└─ ConversationOrchestrator (核心状态机 / 串联模块 / 取消与打断)
├─ AudioSessionManager (AVAudioSession 配置与中断处理)
├─ AudioCaptureManager (AVAudioEngine input tap -> 20ms PCM frames)
├─ ASRStreamClient (NSURLSessionWebSocketTask 流式识别)
├─ LLMStreamClient (SSE/WS token stream)
├─ Segmenter (句子切分:够一句就触发 TTS)
├─ TTSServiceClient (请求 TTS适配多种返回形态)
├─ TTSPlaybackPipeline (可插拔URL播放器 / AAC解码 / PCM直喂)
├─ AudioStreamPlayer (AVAudioEngine + AVAudioPlayerNode 播 PCM)
└─ SubtitleSync (按播放进度映射文字进度)
2. 音频会话AVAudioSession与权限
2.1 麦克风权限
仅在用户第一次按住说话前请求
若用户拒绝:提示到设置开启
2.2 AudioSession 配置(对话模式)
Objective-C建议参数
categoryAVAudioSessionCategoryPlayAndRecord
modeAVAudioSessionModeVoiceChat
options
AVAudioSessionCategoryOptionDefaultToSpeaker
AVAudioSessionCategoryOptionAllowBluetooth
可选AVAudioSessionCategoryOptionMixWithOthers若你希望不打断宿主音频看产品
2.3 中断与路由变化处理(必须)
监听:
AVAudioSessionInterruptionNotification
AVAudioSessionRouteChangeNotification
处理原则:
来电/中断开始:停止采集 + 停止播放 + cancel 网络会话
中断结束:回到 Idle等待用户重新按住
3. 音频采集(按住期间流式上传)
3.1 固定音频参数(锁死,便于端到端稳定)
Sample Rate16000 Hz
Channels1
FormatPCM Int16pcm_s16le
Frame Duration20ms
16kHz * 0.02s = 320 samples
每帧 bytes = 320 * 2 = 640 bytes
3.2 AudioCaptureManagerAVAudioEngine 输入 tap
使用:
AVAudioEngine
inputNode installTapOnBus:bufferSize:format:block:
关键点:
tap 回调线程不可做重活:只做拷贝 + dispatch 到 audioQueue
将 AVAudioPCMBuffer 转成 Int16 PCM NSData
确保稳定输出“20ms帧”如果 tap 回调 buffer 不刚好是 20ms需要做 帧拼接/切片ring buffer
3.3 接口定义OC
@protocol AudioCaptureManagerDelegate <NSObject>
- (void)audioCaptureManagerDidOutputPCMFrame:(NSData *)pcmFrame; // 20ms/640B
- (void)audioCaptureManagerDidUpdateRMS:(float)rms; // 可选UI波形
@end
@interface AudioCaptureManager : NSObject
@property (nonatomic, weak) id<AudioCaptureManagerDelegate> delegate;
- (BOOL)startCapture:(NSError **)error;
- (void)stopCapture;
@end
4. ASR 流式识别iOS15NSURLSessionWebSocketTask
4.1 建议协议(控制帧 JSON + 音频帧二进制)
Start文本帧
{
"type":"start",
"sessionId":"uuid",
"format":"pcm_s16le",
"sampleRate":16000,
"channels":1
}
Audio二进制帧
直接发送 640B/帧 PCM
频率50fps每秒 50 帧)
Finalize文本帧
{ "type":"finalize", "sessionId":"uuid" }
4.2 下行事件
{ "type":"partial", "text":"今天" }
{ "type":"final", "text":"今天天气怎么样" }
{ "type":"error", "code":123, "message":"..." }
4.3 ASRStreamClient 接口OC
@protocol ASRStreamClientDelegate <NSObject>
- (void)asrClientDidReceivePartialText:(NSString *)text;
- (void)asrClientDidReceiveFinalText:(NSString *)text;
- (void)asrClientDidFail:(NSError *)error;
@end
@interface ASRStreamClient : NSObject
@property (nonatomic, weak) id<ASRStreamClientDelegate> delegate;
- (void)startWithSessionId:(NSString *)sessionId;
- (void)sendAudioPCMFrame:(NSData *)pcmFrame; // 20ms frame
- (void)finalize;
- (void)cancel;
@end
5. LLM 流式生成token stream
5.1 目标
低延迟:不要等整段回答
使用 SSE 或 WS 收 token
token 进入 Segmenter够一句就触发 TTS
5.2 LLMStreamClient 接口OC
@protocol LLMStreamClientDelegate <NSObject>
- (void)llmClientDidReceiveToken:(NSString *)token;
- (void)llmClientDidComplete;
- (void)llmClientDidFail:(NSError *)error;
@end
@interface LLMStreamClient : NSObject
@property (nonatomic, weak) id<LLMStreamClientDelegate> delegate;
- (void)sendUserText:(NSString *)text conversationId:(NSString *)cid;
- (void)cancel;
@end
6. Segmenter句子切分先播第一句
6.1 切分规则(推荐)
任一满足则切分成 segment
遇到 。!?\n 之一
或累积字符数 ≥ 30可配置
6.2 Segmenter 接口OC
@interface Segmenter : NSObject
- (void)appendToken:(NSString *)token;
- (NSArray<NSString *> *)popReadySegments; // 返回立即可TTS的片段数组
- (void)reset;
@end
7. TTS返回形态未定 → 客户端做“可插拔播放管线”
由于服务端同事未定输出格式,客户端必须支持以下 四种 TTS 输出模式 的任意一种:
模式 A返回 m4a/MP3 URL最容易落地
服务端返回 URL或 base64 文件)
客户端用 AVPlayer / AVAudioPlayer 播放
字幕同步用“音频时长映射”(可拿到 duration
优点:服务端简单
缺点:首帧延迟通常更高(要等整段生成、至少等首包)
模式 B返回 AAC chunk流式
服务端 WS 推 AAC 帧
客户端需要 AAC 解码成 PCM再喂 AudioStreamPlayer
模式 C返回 Opus chunk流式
需 Opus 解码库(服务端/客户端成本更高)
解码后喂 PCM 播放
模式 D返回 PCM chunk最适合低延迟
服务端直接推 PCM16 chunk比如 100ms 一块)
客户端直接转 AVAudioPCMBuffer schedule
延迟最低、实现最稳
8. TTSServiceClient统一网络层接口
8.1 统一回调事件(抽象)
typedef NS_ENUM(NSInteger, TTSPayloadType) {
TTSPayloadTypeURL, // A
TTSPayloadTypePCMChunk, // D
TTSPayloadTypeAACChunk, // B
TTSPayloadTypeOpusChunk // C
};
@protocol TTSServiceClientDelegate <NSObject>
- (void)ttsClientDidReceiveURL:(NSURL *)url segmentId:(NSString *)segmentId;
- (void)ttsClientDidReceiveAudioChunk:(NSData *)chunk
payloadType:(TTSPayloadType)type
segmentId:(NSString *)segmentId;
- (void)ttsClientDidFinishSegment:(NSString *)segmentId;
- (void)ttsClientDidFail:(NSError *)error;
@end
@interface TTSServiceClient : NSObject
@property (nonatomic, weak) id<TTSServiceClientDelegate> delegate;
- (void)requestTTSForText:(NSString *)text segmentId:(NSString *)segmentId;
- (void)cancel;
@end
这样服务端最后选哪种输出,你只需实现对应分支即可,不需要推翻客户端架构。
9. TTSPlaybackPipeline播放管线根据 payloadType 路由)
9.1 设计目标
支持 URL 播放与流式 chunk 播放
提供统一的“开始播放/停止/进度”接口供字幕同步与打断使用
9.2 Pipeline 结构(建议)
TTSPlaybackPipeline 只做路由与队列管理
URL → TTSURLPlayerAVPlayer
PCM → AudioStreamPlayerAVAudioEngine
AAC/Opus → Decoder → PCM → AudioStreamPlayer
9.3 Pipeline 接口OC
@protocol TTSPlaybackPipelineDelegate <NSObject>
- (void)pipelineDidStartSegment:(NSString *)segmentId duration:(NSTimeInterval)duration;
- (void)pipelineDidUpdatePlaybackTime:(NSTimeInterval)time segmentId:(NSString *)segmentId;
- (void)pipelineDidFinishSegment:(NSString *)segmentId;
@end
@interface TTSPlaybackPipeline : NSObject
@property (nonatomic, weak) id<TTSPlaybackPipelineDelegate> delegate;
- (BOOL)start:(NSError **)error; // 启动音频引擎等
- (void)stop; // 立即停止(打断)
- (void)enqueueURL:(NSURL *)url segmentId:(NSString *)segmentId;
- (void)enqueueChunk:(NSData *)chunk payloadType:(TTSPayloadType)type segmentId:(NSString *)segmentId;
// 可选:用于字幕同步
- (NSTimeInterval)currentTimeForSegment:(NSString *)segmentId;
- (NSTimeInterval)durationForSegment:(NSString *)segmentId;
@end
10. AudioStreamPlayerPCM 流式播放,低延迟核心)
10.1 使用 AVAudioEngine + AVAudioPlayerNode
将 PCM chunk 转 AVAudioPCMBuffer
scheduleBuffer 播放
维护“当前 segment 的播放时间/总时长”(可估算或累加 chunk 时长)
10.2 接口OC
@interface AudioStreamPlayer : NSObject
- (BOOL)start:(NSError **)error;
- (void)stop;
- (void)enqueuePCMChunk:(NSData *)pcmData
sampleRate:(double)sampleRate
channels:(int)channels
segmentId:(NSString *)segmentId;
- (NSTimeInterval)playbackTimeForSegment:(NSString *)segmentId;
- (NSTimeInterval)durationForSegment:(NSString *)segmentId;
@end
PCM chunk 的粒度建议50ms~200ms太小 schedule 太频繁,太大延迟高)。
11. 字幕同步(延迟优先)
11.1 策略
对每个 segment 的文本 text按播放进度映射显示字符数
visibleCount = round(text.length * (t / T))
tsegment 当前播放进度pipeline 提供)
Tsegment 总时长URL 模式直接取chunk 模式可累加估算)
11.2 SubtitleSync 接口OC
@interface SubtitleSync : NSObject
- (NSString *)visibleTextForFullText:(NSString *)fullText
currentTime:(NSTimeInterval)t
duration:(NSTimeInterval)T;
@end
12. ConversationOrchestrator状态机 + 打断 + 队列)
12.1 状态
typedef NS_ENUM(NSInteger, ConversationState) {
ConversationStateIdle,
ConversationStateListening,
ConversationStateRecognizing,
ConversationStateThinking,
ConversationStateSpeaking
};
12.2 关键流程
事件用户按住userDidPressRecord
如果正在 Speaking/Thinking
[ttsService cancel]
[llmClient cancel]
[asrClient cancel](如仍在识别)
[pipeline stop](立即停播)
清空 segment 队列、字幕队列
配置/激活 AudioSession
新建 sessionId
[asrClient startWithSessionId:]
[audioCapture startCapture:]
state = Listening
事件用户松开userDidReleaseRecord
[audioCapture stopCapture]
[asrClient finalize]
state = Recognizing
回调ASR final text
UI 显示用户最终文本
state = Thinking
开始 LLM stream[llmClient sendUserText:conversationId:]
回调LLM token
segmenter appendToken
segments = [segmenter popReadySegments]
对每个 segment
生成 segmentId
记录 segmentTextMap[segmentId] = segmentText
[ttsService requestTTSForText:segmentId:]
当收到第一个可播放音频并开始播:
state = Speaking
回调TTS 音频到达
URL[pipeline enqueueURL:segmentId:]
chunk[pipeline enqueueChunk:payloadType:segmentId:]
回调pipeline 播放时间更新(每 30-60fps 或定时器)
根据当前 segmentId 取到 fullText
visible = [subtitleSync visibleTextForFullText:currentTime:duration:]
UI 更新 AI 可见文本
12.3 打断Barge-in
当用户再次按住:
立即 stop 播放
取消所有未完成网络请求
丢弃所有未播放 segments
开始新一轮录音
12.4 Orchestrator 接口OC
@interface ConversationOrchestrator : NSObject
@property (nonatomic, assign, readonly) ConversationState state;
- (void)userDidPressRecord;
- (void)userDidReleaseRecord;
@property (nonatomic, copy) void (^onUserFinalText)(NSString *text);
@property (nonatomic, copy) void (^onAssistantVisibleText)(NSString *text);
@property (nonatomic, copy) void (^onError)(NSError *error);
@end
13. 线程/队列模型(强制要求,避免竞态)
建议三条队列 + 一条 orchestrator 串行队列:
dispatch_queue_t audioQueue;采集帧处理、ring buffer
dispatch_queue_t networkQueue;WS 收发解析)
dispatch_queue_t orchestratorQueue;(状态机串行,唯一修改 state/队列的地方)
UI 更新统一回主线程
规则:
任何网络/音频回调 → dispatch_async(orchestratorQueue, ^{ ... })
Orchestrator 内部再决定是否发 UI 回调(主线程)
14. 关键参数(延迟与稳定性)
音频帧20ms
PCM16k/mono/int16
ASR 上传WS 二进制
LLMtoken stream
TTS优先 chunk若 URL 模式也要尽快开始下载与播放
chunk 播放缓冲100~200ms防抖动
15. 开发落地建议(服务端未定情况下的迭代路径)
Phase 1先跑通端到端用“URL 模式”模拟)
TTSServiceClient 先假定服务端返回 m4a URL或本地 mock URL
Pipeline 实现 URL 播放AVPlayer
打断 + 字幕同步先跑通
Phase 2服务端定了输出后再替换
若服务端给 PCM chunk直接走 AudioStreamPlayer最推荐
若给 AAC chunk补 AAC 解码模块AudioConverter 或第三方)
若给 Opus chunk集成 Opus 解码库,再喂 PCM
关键Orchestrator/Segmenter/ASR/字幕同步都不需要改,只替换 TTSPlaybackPipeline 分支。
16. 合规/体验注意
录音必须由用户动作触发(按住)
明确的“正在录音”提示与波形
避免自动偷录
播放时允许随时打断
文档结束
给“写代码的 AI”的额外要求建议你一并附上
语言Objective-C.h/.m
iOS 15+WebSocket 用 NSURLSessionWebSocketTask
音频采集用 AVAudioEngine + ring buffer 切 20ms 帧
播放管线必须支持URL 播放AVPlayer+ PCM chunk 播放AVAudioEngine
其余 AAC/Opus 分支可留 TODO / stub但接口要预留