01 / 使用场景:先明确会遇到什么问题
下表汇总典型使用场景、公开资料描述的常见机制和通用链路分析。公开机制用于解释问题,不代表这些场景都已在某个产品上复现。
| 场景 case | 具体问题 | 依据与处理章节 |
|---|---|---|
| 持续记录声音,同时等待唤醒;唤醒后开始对话 | 重复开关麦克风;一个功能结束导致其它功能失去输入。 | 场景归纳 共享输入与独立读取 |
| 唤醒词后紧接着说问题,语音服务尚未连上 | 开头没有保存;历史与实时拼接时漏音或重复。 | 场景归纳 开头保留与顺序接续 |
| 蓝牙声音成批到达,但识别接口要求固定长度声音 | 直接把到达事件当成处理节拍,导致输入断续或片段尺寸不对。 | 通用链路分析 到达缓冲与格式重组 |
| 录音保存或识别突然变慢 | 等待队列不断增长,或共享声音一直无法释放。 | 通用链路分析 慢消费者与空间上限 |
| 应用仍在运行,却突然只收到静音 | 其它应用或系统优先级改变了实际采集权限;应用内部共享解决不了系统限制。 | Android 官方输入共享规则 使用资格与中断 |
| 锁屏或负载变高时,回复出现短暂停顿 | 收音、写文件或连接操作阻塞播放回调,设备来不及取到声音。 | 场景归纳 + Android 回调说明 输出缓冲与独立执行 |
| 语音服务一阵一阵返回回复,或无线包晚到、丢失 | 到达节奏与播放节奏不同,出现断供或越来越大的延迟。 | WebRTC 接收缓冲机制 预填、缺音与过期处理 |
| 短时间正常,连续通话后缓冲逐渐变深或变空 | 发送与播放设备时钟有细小差异,长期累积。 | WebRTC 时钟差异处理 区分时钟漂移与拥塞 |
| 对话、翻译、提示音、音乐相互交叠 | 声音盖住主语音,或多个功能交错控制同一输出。 | 场景归纳 + 通用产品场景 排队、混音与抢占 |
| 边播放边收音,麦克风又收到扬声器的声音 | 远端听到回声,或应用把自己的回复当成用户说话。 | WebRTC 语音增强机制 回声处理与音频职责 |
| 拔下耳机、接入新设备、来电打断 | 播放位置变化;音频格式、缓冲时长也可能变化,不能沿用旧配置。 | Apple 路由变化说明 重建格式与恢复条件 |
| 蓝牙断开重连,旧播放的结束消息迟到 | 旧回复补播,或旧消息关闭了新的播放。 | 场景归纳 传输清理与播放身份 |
| 其它应用播放音乐或播客,本应用要播一条回复 | 内部输出安排无法直接控制其它应用;音乐与人声节目需要不同让位策略。 | 系统播放协调规则 跨应用共存与中断 |
| 音乐经蓝牙出声,回复从专用语音通道进入同一设备 | 手机与设备可能重复压低同一份音乐,或回复停了音乐仍未恢复。 | 近期讨论归纳 实际通道与衰减归属 |
02 / 整体设计:完整链路与模块职责
声音经过“设备输入端 → 输入池 → 功能处理 → 输出池 → 设备输出端”。应用调度器安排内部任务;系统音频协调把平台的允许、让位与中断结果交回调度器,两者共同决定链路何时能运行。
输入和输出可以独立工作,录音不必有播放,提示音也不必有收音。其它应用的音乐不进入本应用的池,它通过系统音频和实际播放设备与本应用声音共存。内部请求获准后,还需满足该路线所需的系统与设备条件。
完整架构:声音链路、系统准入与设备反馈
声音链路:设备输入 → 输入池 → 各功能处理 → 输出池 → 设备播放。
控制关系:应用调度器把播放用途和路线交给系统音频协调,后者向手机系统申请所需资格、配置共存方式,并接收结果、中断与路线事件。
执行与反馈:调度器据此暂停、恢复或结束相关任务。外接设备可用空间、断供和通话状态另行反馈。其它应用音乐不进入本应用池,自定义通道也须服从实际资源与中断约束。
各模块负责什么,怎样相互配合?
| 模块 | 主要职责 | 交接与执行时机 |
|---|---|---|
| 应用调度器 | 管理任务编号、使用资格、优先级、播放路线与缓冲目标。 | 启动时提出所需系统策略;收到系统结果或设备状态变化后,统一发布允许、暂停、恢复或结束规则。 |
| 系统音频协调 | 对接平台的播放资格、共存配置、中断与路线变化;统一管理本应用持有的相关系统资源。 | 在所需资格取得后才通知调度器可用;中断与失去资格时通知让位;最后一个相关任务结束后释放。可复用播放器已有管理能力。 |
| 设备输入端 | 接入麦克风或外接音源,完成必要重组、解码与位置标记。 | 收音回调或数据到达时写入输入池;实际采集资格或设备状态改变时通知调度器,不等待功能处理。 |
| 输入池 | 保存短时共享声音、独立读取位置与专用保留范围。 | 输入端写、各功能分别读;中断后是否继续保存需看输入资格与容量,不能无限等待输出恢复。 |
| 功能执行器 | 独立完成唤醒、录音、对话或翻译,调用所需存储与语音服务。 | 按已发布的资格处理输入;回复异步入队,慢工作不阻塞实时采播。 |
| 输出池与输出执行器 | 安排各回复的顺序或混音,按目的地保存待播序列,执行取数、转换与发送。 | 所需准入满足后按播放需求交付;失去资格先停止继续喂声,再按场景保留或清理积压。 |
| 设备输出端 | 通过系统播放器或专用外接通道出声;按自身时钟消耗缓冲。 | 可用时反馈深度、断供与通话状态。停止能力、独立混音及清空边界由系统/设备合同决定。 |
一次播放怎样从请求走到真正出声?
- 功能提出请求:说明要播什么、目的地在哪里,以及需要与音乐共存、压低还是互斥。
- 应用调度器检查内部冲突:确认当前任务和路线,给请求分配有效身份,再交给系统音频协调检查该路线所需条件。
- 系统音频协调返回结果:可以开始、需要等待,或无法取得资格。应用不能只因为输出池已有声音就直接开播;等待仍需容量和时间上限。
- 输出执行器按规则播放:手机和外接路线分别按第 04 章的预填与交接条件运行,系统或设备负责其支持的混音与压低。
- 运行中接收变化:系统中断、路线变化或设备通话状态交回调度器,再暂停喂声、处理积压或结束;恢复须重查用户意图与任务有效性。
- 结束时释放影响:清理属于本次请求的声音;相关系统资源和设备压低状态仅在最后一个依赖它们的任务结束后撤销。
这里的“系统条件”按路线判断:普通系统播放与自定义外接语音的数据路径不同,但后者也不能无视采集权限、系统中断或设备通话。平台机制和音乐共存场景在第 06 章展开;音频缓冲与设备输出的细节仍在第 03、04 章。
03 / 输入缓冲:声音存在哪里,多个功能怎样各自读取?
要解决的问题:同一段声音可能同时用于唤醒、录音和对话,但它们需要的起点、读取长度与处理速度不同;对话建连还会耽搁一段时间。既要允许各自读取,又要限制内存,不能让最慢的功能拖住收音或无限留住旧声音。
本方案选择:每个音源有一块共享短实时区,只保存最近一小段声音;每个功能有自己的读取位置。需要完整保留的问题开头和录音内容,及时进入各自有容量限制的保留区或存储队列。先用有界复制把关系讲清楚,测到复制成本明显后,再考虑更复杂的共享引用。
输入池由哪些部分组成?
短实时区是什么?就是“只保存最近 N 秒”的滚动窗口
例如暂定保留最近 2 秒:新声音不断写入,窗口也向后移动;超过窗口的旧声音会退出。可以用一块固定容量的循环内存实现,写到末尾后从头复用空间。物理内存位置可以循环,声音的编号仍连续递增,这样能判断某个读取者要的声音是否还在。
“短实时”说的是保留范围短,供当前运行的功能及时使用,不代表声音只允许读一次,也不代表每个功能要先等满 N 秒。2 秒只是解释用的示例;实际长度要覆盖唤醒所需回看与正常读取延误,并满足内存预算。它也不等同于接收端为平滑无线抖动设置的播放缓冲。
| 池中记录 | 表示什么 | 为什么需要 |
|---|---|---|
| 音源与连续段编号 | 声音来自哪个麦克风;是否属于同一次连续采集。 | 设备重启或换音源后,不把新声音接到旧位置上。 |
| 最早保留位置 | 再早的声音已经不在短实时区。 | 读取落后时明确报告缺口,而不是返回其它声音。 |
| 最新写入位置 | 该位置之前的声音已完整写入。 | 区分“还没收到”与“已经过期”。 |
| 声音片段与格式 | 采样内容、采样率、声道,以及连续位置。 | 按范围读取,并按接口要求重组长度;不同格式不混读。 |
多个读取位置是什么?每个功能各有一张“读到哪里”的书签
共享的是声音内容,独立的是读取进度。假设池内保留 400–1100 毫秒的声音:对话还要从 600 毫秒补发,录音转存已经读到 900 毫秒,唤醒检测已读到 1000 毫秒。三个功能可以分别读取自己需要的内容,一个位置前进不会改变另外两个。
| 功能 | 保存的读取位置 | 怎样前进 |
|---|---|---|
| 唤醒检测 | 下一段尚未交给检测器的新声音起点。 | 读到新片段后推进;需要重叠窗口时,在检测器内保留历史,或明确读取仍在池内的回看范围。 |
| 录音转存 | 下一段尚未交给录音存储队列的声音起点。 | 成功交给有容量限制的存储队列后推进;真正写到文件的进度另行记录,不能混为一谈。 |
| 实时对话 | 进入对话后,用本次保留区中的待上传位置。 | 按音频服务约定的输入交接结果推进;网络确认、重试和远端识别进度另行管理,不能仅凭读取就宣称远端已收到。 |
为什么不共用一个读取位置?
| 做法 | 产生的问题 | 独立读取位置解决什么 |
|---|---|---|
| 一个功能取走,队列就删掉 | 录音拿走的片段,唤醒和对话可能就拿不到。 | 同一片段能被需要它的各个功能分别读取。 |
| 等所有功能处理完,公共位置才前进 | 最慢功能决定大家的进度;对话补发旧问题时,唤醒也跟着停。 | 各功能按自己的速度和长度读取,读慢只处置该功能。 |
| 每个功能复制一份完整公共池 | 容易隔离,但空间和复制开销随功能数量增加。 | 日常短时内容共用,只对确实需要长保留的内容分配专用空间。 |
收益是同一声音可复用、不同进度不绑定、不同读取长度能共存。代价是必须管理读取有效范围、缺口与超限。它本身不保证无锁、不阻塞或永不丢音;只有一个顺序消费者时,一条简单队列通常足够。
为什么还要单独的保留区?公共窗口不能等所有慢功能
| 区域 | 保留什么 | 怎样释放/满了怎么办 |
|---|---|---|
| 共享短实时区 | 同一音源最近 N 秒,供各功能及时读取和短暂回看。 | 按固定窗口推进;覆盖前保证本次读取的快照已完成,不覆盖仍在读取的内存。落后的读取者收到“所需范围已过期”。 |
| 本次对话保留区 | 需要保护的问题开头,以及连接或追赶期间尚未交出的声音。 | 按本次读取/交接规则释放;容量或等待期限到界时明确失败或执行已确认的降级方式,不悄悄删掉开头。 |
| 录音存储队列 | 已从公共区取出、等待写入文件或后续保存的内容。 | 成功落盘后释放内存;持续写不动时停止或记录缺口并提示,不能无限堆积。 |
本方案不让一个过期读取位置阻止公共窗口前进。需要长保留的内容应在有效窗口内转存出去。若改成直接引用公共片段,必须另加引用释放与总内存预算;不能一面说“窗口固定”,一面又让慢读取者无限固定旧片段。
写入和读取怎样交接?
- 写入:接收适配器按采样位置追加完整片段,再发布新的写入位置;收音回调不等待磁盘、网络或业务处理。
- 读取:检查音源、连续段、起点和长度;整个范围仍在池中才交出声音,成功交接后仅推进该功能的位置。
- 还没收到:保留读取位置,等待后续数据或按实时接口约定返回暂缺;不阻塞设备收音。
- 已经过期或中间缺音:明确返回缺口;唤醒可重置窗口,实时对话按场景决定重试,完整录音标记缺失。
- 并发保护:检查范围和取得片段需要一致快照,避免检查后又被覆盖;大范围复制、解码和磁盘写入不放在设备回调或长时间公共锁内。
具体 case:唤醒后紧接问题,连接要等一会儿
- 待机时短实时区持续滚动保存,唤醒检测按自己的进度读取。
- 检测到唤醒后,根据声音中的准确位置确定问题起点,校验这段仍在公共窗口内。
- 在同一个有序交接点登记本次保留区,记录当时的写入边界:保存“问题起点到该边界”的快照,边界之后的新声音按序进入本次保留区。两段用同一采样位置衔接,既不漏也不重。
- 连接等待期间,本次保留区继续保存;录音转存与唤醒检测仍各自读取,公共短实时区照常滚动。
- 连接就绪且允许上传,从本次保留区的问题起点发送;在下游允许并能处理快于实时的输入时才按需追赶;追上后继续沿同一读取位置消费新声音,不另开一条容易重复的实时路径。
- 取消、保留区到界或连续性断裂时,结束本次保留并释放资源;其它功能的读取位置不受影响。
实际实现需保证“取得历史快照”和“开始接收后续声音”的边界一致;可以用有序事件处理或短时间状态同步完成。若复制尚未完成,快照片段必须暂时有效且有明确空间上限,不能任其被滚动窗口覆盖。
输入缓冲让什么变平稳,又不能保证什么?
设备按实际到达写入;需要固定长度的识别接口从池中重组所需长度,需要固定节奏的接口再按其节奏交付。录音与离线处理不必为了“平滑”强行匀速或等待预填。缓冲吸收有限的到达抖动和处理延误,但无法弥补长期处理能力不足,也不能恢复已经丢失的真实原声。
所以第 03 章的设计关系是:短实时区负责共享最近声音,独立读取位置负责各功能进度,专用保留区负责不能丢的长内容,容量与缺口规则负责让慢功能不拖垮整条输入链。
04 / 输出缓冲:回复存在哪里,怎样接成连续播放?
要解决的问题:回复可能成批产生,多个功能可能同时要求播音;手机和外接设备却需要按各自的播放时钟持续取数。必须明确声音先放哪里、谁安排先后、何时开始播,以及没播出的内容怎样结束或清理。
本方案选择:各回复先独立排队,输出执行器按调度规则选择或混音,再为每个目的地形成一条待播序列。手机按播放回调取数;外接设备经过有界发送队列与设备内部缓冲,最终由设备时钟播放。每条队列都有自己的进度与上限,整条路线另算总积压。
输出池与设备播放端怎样连接?
先分清:压缩声音与采样声音不是同一种数据
采样声音是按时间排列的声音数值,可以直接用来调整音量、混音和按采样点取数;压缩声音是编码器打包后的内容,通常更省传输空间,需要先解码才能做这些处理。改变采样率、混音与重新压缩是不同操作。
一条回复在各层是什么形态?
本方案的基础路径不在目的地输出池混存两种形态。服务若已经交付采样声音,无须再解码;若走系统播放器或协议支持的压缩直通路线,可跳过应用解码与重编码,但该路线不能同时假设应用能任意修改或混合其中的声音。解码后短片段也要纳入内存预算,不把整条长回复一次性全部展开。
“输出池”具体存什么?
这里把目的地输出池定义为“已经按规则安排、准备交给某个设备的声音”。建议以可按采样点切分的声音片段存放,压缩与封包放在外接发送侧。若成熟播放器已经提供等价能力,可复用它,不必另造相同的池。
| 位置 | 保存什么 | 释放与超限规则 |
|---|---|---|
| 各回复的独立队列 | 对话、翻译或提示音的内容、压缩或采样格式、回复与播放编号、声音位置、有效期限与是否结束。 | 交给目的地输出序列后可释放对应内容;尚未获准的回复不能无限提前解码或堆积,按产品规则等待、取消或拒绝。 |
| 目的地输出池 | 已选择或混合的声音、目标格式、连续播放位置,以及待交出范围。 | 后续系统或发送队列明确接管后,释放不再需要的片段;混合片段按目的地管理,不能靠一个源播放编号独立撤回。需保留重试副本时明确计账。达到上限时限流、暂停或处置过期内容。 |
| 有界发送队列 | 外接路线中待发送的完整包、发送偏移、所属播放与年龄。 | 按传输结果释放;整包未发与半包在途分开处理。不能把“排队成功”当作发送或播放完成。 |
| 设备内部播放缓冲 | 设备已收到但尚未播放的声音,可能还有解码后的采样缓冲。 | 按设备播放时钟消耗;反馈深度、可用空间与断供。应用控制不了的内部缓冲需测量估算。 |
为什么输出不是所有功能共用一个读取位置?
输入通常是一份声音供多个功能分别看;输出则是多个功能争取有限的播放时间。同一个目的地需要明确安排后的播放序列,不能让各功能直接抢同一个播放器,也不能让设备随意从不同回复里各取一段。
同一回复若要送到手机和另一台设备,两条路线分别记录读取进度、格式和延迟;内容适合时可以共享底层存储,但不能共用一个消费位置。需要同步播放时还需共同时间基准与延迟补偿,这是额外设计。
缓冲把成批到达的声音接成连续播放
声音到达时写入,设备需要时读取;先留出少量提前量,把短时到达间隔变化与连续播放隔开。图中的固定间隔表示同样时长的声音按顺序出声,不要求系统每次回调都恰好相隔 20 毫秒;回调要按实际要求填够采样点。
池中记录哪些位置,怎样知道还有多少声音?
| 记录 | 含义 | 不能与什么混淆 |
|---|---|---|
| 所属播放编号与格式 | 片段属于哪次播放、发往哪里,用什么采样率和声道。 | 旧播放结束消息不能操作新编号;不同格式不能按同一字节长度直接拼接。 |
| 写入位置 | 已准备好、可以交付的声音到哪里。 | 远端已经生成但应用还没收到的内容,不在池内。 |
| 交出位置 | 已由下一层明确接管的声音到哪里;不同目的地分别记录。 | 交给系统、交给蓝牙、设备收到、设备播完,是不同阶段。 |
| 待播深度与片段年龄 | 池中剩多少毫秒,以及最旧片段从约定起点等了多久。 | 队列深度不是当前这句话的总年龄;下一层仍在排队的声音也要计入端到端积压。 |
| 回复结束标记与播放状态 | 后续还会不会有声音,目前在预填、播放、重新预填还是结束。 | 暂时空队列不等于回复结束;收到结束标记也不等于设备已经播完尾音。 |
对于固定格式的采样声音,待交出深度可由“写入位置 − 交出位置”换算;压缩包使用采样时间或包时长换算,不能直接用压缩字节数推断时长。
同一句暂停后继续,和取消后重来,怎样区分?
为了让恢复规则一致,分清三件事:回复编号认内容,播放编号认一次连续的播放任务,连接代次认这次发送所用的连接。播放任务可以包含暂停,暂停不自动变成新任务。
一份内容、一次播放、一条连接分别有身份
| 发生什么 | 身份与内容位置 | 迟到片段怎样处理 |
|---|---|---|
| 同一句短暂断供,连接仍健康 | 保持回复、播放和连接身份,暂停/重新预填后从尚未消费的位置继续。 | 仍属于当前回复、顺序有效且未过期的片段可以接收;已经用修补或静音推进过的时间段,不能晚到后再补播一次。 |
| 用户取消或切到另一任务 | 原播放编号失效;重新播放即使使用相同回复内容,也要新建播放编号。 | 旧播放的队列写入与结束回调均不能操作新播放;新任务使用的新位置必须明确。 |
| 发送故障导致连接重建 | 换连接代次。基础方案先结束受影响播放;产品允许续播时,再以新播放编号从可确认的位置开始。 | 旧连接的回调作废;无法确认设备播到哪里时,不承诺无重复、无缺口续播,也不盲目重放在途片段。 |
这里的编号不一定都存在于现有线上协议中。本机可以拒绝旧回调,但要让设备拒绝旧包,必须有协议身份、可确认的清空/停止,或干净的新连接边界。
写入、开播、取数和结束怎样配合?
- 回复到达:校验播放编号、顺序与格式,放入对应回复队列;有界处理重复、缺口和迟到片段。
- 安排输出:按规则决定排队、打断或混音;输出执行器转换为目的地格式并顺序写池。非必需的提示或背景声音缺席时,不阻止主声音前进。
- 开始播放:按下节对应路线的预填条件开播;若回复已结束且全部声音比目标更短,也可直接播放,不等一个永远达不到的水位。
- 持续读取:手机回调从池中取本次需要的采样点;外接发送执行器按传输能力和设备提前量取出并封包。回调不等网络、磁盘、收音或业务锁。
- 短暂不足:按场景短时修补或补齐静音,实时回调及时返回并记录断供;可等待的完整播报则进入重新预填,避免反复开停。
- 正常结束:回复结束后停止增加声音,继续消费有效尾音;确认各相关层排空或达到约定结束条件,再报告播放结束。
- 主动打断:先禁止旧播放继续入池,再按下文“取消边界”处理未混合、已混合和设备已接管的内容;新播放需等旧发送安全退出,不直接拼在未知半包后面。
预填看哪个池?手机与外接路线分别判断
| 路线 | 开播前检查哪里 | 由谁判断与配合 |
|---|---|---|
| 手机直接播放 | 应用目的地输出池中已有足够可播的采样声音;系统内部不可控的缓冲另外计入总延迟。 | 应用输出执行器决定何时启动/供给系统播放器;启动后按系统回调填数,不人为再等第二份应用预填。 |
| 自定义外接设备路线 | 真正抗无线到达抖动的预填在设备播放缓冲;设备以实际收到的有效声音是否达到设备目标作为开播条件。 | 应用先在传输许可范围内发送,设备达到自身目标再出声。应用输出池负责平滑上游与限积压,不额外等待同样长度再开始首轮发送。 |
| 系统托管的耳机/已有音频组件 | 沿用该播放组件负责的预填与缓冲合同。 | 应用不假设能直接控制耳机内存;测量组件引入的延迟,避免在应用再复制一套相同预填。 |
目标按层命名:应用池的目标是“还能交出多少声音”,设备缓冲的目标是“已经收到、还能播多久”。二者可以同时存在,但一起分配延迟预算,不能把一份“100 毫秒”要求在两边各等一遍。
设备若只报告可写额度,应用只能知道能否继续提交,不能据此知道实际预填。没有播放深度反馈时,优先依赖设备明确的自动开播合同或可查询状态;否则按测得的发送与消费情况保守估算,并保留误差与等待上限,不能声称已精确知道设备何时出声。
水位怎么用?容量上限不是开播门槛
| 配置或状态 | 具体作用 | 处置 |
|---|---|---|
| 预填目标/正常目标深度 | 预填目标决定开口前攒多少;正常目标是播放中希望维持的提前量,两者可不同。 | 根据生成和到达抖动与延迟目标设定;不需要等待整个池填满。 |
| 低水位 | 提示提前量偏少,可能接近断供。 | 先查声音是否已生成、处理是否慢、传输是否卡;必要时重新预填。低水位不意味着应加速消耗。 |
| 高水位 | 提示提前量偏多,可能延迟增长。 | 查是短突发还是长期偏差;细小时钟差异可小幅伸缩,严重拥塞则限流或处置过期内容。 |
| 容量上限/有效期限 | 限制空间和陈音频。 | 能反向限流时暂停上游;完整内容可转入有界后备存储或暂停;实时内容按已确认规则丢过期片段或结束,不能任意切断一句话还声称完整。 |
| 重新预填 | 供给已不足,暂停或按实时接口补齐,等待足够内容后继续。 | 设总等待界和恢复条件;同一健康播放只是换到等待状态,身份不变;只有取消、重启或另起播放才换播放编号,详见本章身份规则。 |
具体 case:一次长回复分两批到达
下面只演示池深度变化:假设是手机直接播放,应用输出池的预填目标为 100 毫秒,实际播放保持正常速度;暂不计额外系统缓冲,所有数字都不是实测或通用默认值。这个表不能直接当作外接设备的开播判断。
| 时间 | 到达与播放 | 输出池剩余声音 |
|---|---|---|
| 0 毫秒 | 回复尚未到达,等待预填。 | 0 毫秒 |
| 40 毫秒 | 第一批 120 毫秒到达,超过预填目标,开始播放。 | 120 毫秒 |
| 100 毫秒 | 已播放 60 毫秒,新批次尚未到达。 | 60 毫秒 |
| 120 毫秒 | 又播放 20 毫秒,第二批 100 毫秒到达,随后收到回复结束标记。 | 40 + 100 = 140 毫秒 |
| 200 毫秒 | 继续播放 80 毫秒,剩下尾音。 | 60 毫秒 |
| 260 毫秒 | 最后 60 毫秒播放完;在此简化例子里可以结束。 | 0 毫秒 |
两批之间并没有新声音持续到达,但第一批的剩余内容让播放不中断。真实系统还必须检查系统与设备的在途尾音;不能看到应用池为零就断定扬声器已经播完。
混音以后,能不能只取消其中一个声音?
不能把已经混成一段的声音,按原播放编号直接减掉一份。基础方案采用保守规则:混音前分别取消;混音后若涉及取消,则停止并清理受影响的目的地输出,不承诺其它声音无缝保留。需要不停其它声音、只移除一个声音时,必须另外支持保留原始片段并重建尚未提交的混音。
| 取消发生在哪里 | 本方案能保证什么 | 不能承诺什么 |
|---|---|---|
| 仍在某个回复的独立队列 | 按播放编号取消该队列,禁止再写入;未参与的其它声音继续按原规则处理。 | 不能因此撤回已经送去混音或设备的声音。 |
| 已混入目的地输出池,尚未交出 | 停止该目的地的当前输出,清掉受影响的待播合成片段。 | 基础版不从合成结果中拆出提示音或对话;需要继续的声音按明确的新播放请求处理,不自动重播。 |
| 已交给系统或设备,但尚未确认播完 | 请求该层支持的停止/清空,等待可确认的结束边界;停止能力不足时隔离旧发送或连接,再决定是否能开新播放。 | 可能已有少量声音在设备内部,不能保证立即无声,也不能保证只停止混音中的一项。 |
| 已经实际播出 | 停止后续内容,并如实记录停止位置与已知误差。 | 已经出声的内容无法撤销。 |
例如提示音与对话已经混好,取消对话时,基础方案可能连同这部分提示音一起停止。产品若要求提示音完整不间断,就需要更强的未提交区重混能力,或把提示音安排在独立可控的设备混音通道;这属于明确的能力升级。
应用池与设备缓冲怎样共同维持平滑?
外接路线要一起看:各回复等待量、应用输出池、发送在途量和设备待播量,避免每层都预留一大段。设备有可用空间不代表需要把应用全部积压一次倾倒过去;发送侧保持受控提前量,设备时钟最终决定出声。
短时到达抖动靠缓冲;有证据的细小时钟差异才靠小幅重采样或保音高伸缩;切换突变靠淡入淡出;短丢包可由成熟接收或解码模块修补。它们解决不同问题:长期平均供给不足仍需暂停、降负载或结束,不能靠无限扩容和重复旧声音解决。
完整回复排得很长,不等于时钟漂移
| 看起来都像“池子变深” | 怎样理解与处理 |
|---|---|
| 一次收到一段完整长回复 | 这是内容提前生成,不是播放时钟慢。按正常速度播放,控制提前解码和存储占用;不为了清队列把整段随意加速。 |
| 持续实时声音中,深度长期缓慢单向变化 | 先排除处理阻塞和拥塞;结合实际收发速率与采样时间证据,确认时钟差异后,才做小幅补偿。 |
| 传输停住后突然大量到达,或处理线程迟迟不运行 | 这是拥塞或调度问题;先恢复处理、限流并按场景处置过期内容,不能用时钟补偿掩盖。 |
参考 Android 的回调与缓冲说明和 WebRTC 的接收缓冲与时间伸缩机制;具体水位、修补能力和状态反馈以实际音频组件与设备为准。
05 / 调度与播放冲突:多个功能同时使用设备怎么办?
问题:共用声音内容并不代表可以同时占用所有设备能力。多个功能各自开关收音、配置系统或向同一个发送器写数据,会相互覆盖;让全部工作串在一个调度器中,又会互相阻塞。
统一管理使用资格,实际处理分别执行
调度器记录每个请求的编号、所需音源、读取范围、输出位置、优先级和结束状态。它决定谁可以使用哪条路径;各功能与音频执行器按已发布规则运行。启停、打断和故障改变规则,声音片段不必逐个等待调度器审批。
| 实际冲突与约束 | 可选处理及代价 | 建议 |
|---|---|---|
| 持续录音与对话都需要同一个麦克风 | 互斥最简单,但录音中断;共享采集可并行,但要隔离读取与停止范围。 | 产品允许时共享采集、分别使用;最后一个有效使用者退出后再关闭输入。系统权限限制仍需单独处理。 |
| 对话、翻译都要使用同一语音通道 | 排队会增加等待;自动抢占会结束另一业务;并行需要服务、带宽与产品交互都支持。 | 先明确业务组合关系,再实现互斥、排队或抢占。不能由音频层默认把一次点击解释成自动切换。 |
| 提示、回复与音乐叠加后听不清 | 暂停音乐简单但打断体验;压低音乐需控制其播放端;本地混音仅能处理能取得的声音。 | 对每对声音明确叠加、压低、排队或打断;在有实际控制能力的一端执行。能力不足时定义回退。 |
| 多个播放任务共用同一个蓝牙发送器 | 各自直接写容易交错包;全局串行声音处理又会放大阻塞。 | 同一字节流只有一个有效发送者;各回复独立排队,由输出执行器按调度结果发送,传输阻塞不扩散到采集。 |
| 旧结束消息迟到,新播放已经开始 | 仅检查“目前有播放”无法区分请求;每次播放有编号会增加状态管理,但能隔离生命周期。 | 结束和失败处理只操作匹配编号的资源;旧回调只记旧记录。 |
| 系统收音权限改变或收到静音 | 不断重开麦克风可能增加抖动,也未必能获得权限。 | 先检查系统采集状态、权限和当前输入设备;区分用户沉默与系统静音。恢复必须符合平台规则。 |
| 扬声器声音再次进入麦克风 | 只靠增大缓冲不会消除回声;多处重复降噪也可能损伤语音。 | 明确由设备、系统或应用哪一层负责回声消除、降噪与音量控制;给回声处理提供合适的播放参考,避免重复处理。 |
跨应用的音乐、播客和电话不能只靠本章内部调度处理,详见下一章的系统与设备边界。语音增强机制参考 WebRTC 音频处理模块;具体播放参考的取得位置与延迟,需要结合实际输出路线验证。
06 / 系统音乐与其它应用:怎样共存,被打断后怎样恢复?
前面第 04 章管理的是本应用自己的声音。音乐播放器、播客、导航、电话与系统提示有自己的音频处理。本应用通常拿不到它们的原始声音,也不能靠调整自己的输出池决定它们是否暂停或降低音量。
本章场景来自近期讨论归纳和官方音频文档。以下是通用设计建议,不表示所有系统、第三方应用或外接设备已经具备同样能力。
先看声音经过哪里:不是所有音乐都进入我们的输出池
具体会遇到哪些问题?
| 问题/场景 | 常见误判 | 建议处理 |
|---|---|---|
| 听音乐时收到一句语音回复或导航提示 | 只降低本应用音量,认为背景音乐也会跟着降低。 | 通过系统支持的方式申请短暂共存或压低背景音乐;必要语音若无法听清,可改为暂停互斥。结束时及时释放本次影响。 |
| 听的是播客或有声书 | 把人声节目当音乐压低,用户同时错过两边的话。 | 区别音乐与人声内容;优先考虑临时暂停而非重叠讲话,但能否识别和暂停受平台及对方应用配置影响。 |
| 手机音乐已通过蓝牙送到同一台外接设备,回复走专用通道 | 把它当成两种音乐,手机先压低一次,设备再压低一次。 | 沿同一条音乐链路选择主要衰减位置;设备混音可靠时可由设备控制,手机侧策略必须与之协调,不能不看路线统一叠加。 |
| 多轮对话中,回复之间有安静间隔 | 一有空队列就恢复音乐,下一片回复到达又压低,导致音量反复起伏。 | 区分短暂停顿和本轮真正结束;采用有界的停声确认与音量渐变。整通对话都压低音乐是另一个产品选择,不能默认套用短提示策略。 |
| 电话、其它通话、语音助手或闹钟打断 | 应用还在产生声音就继续播,或只监听电话便声称所有中断都已覆盖。 | 按实际系统中断、焦点、设备通话状态和路线变化停止或暂停;不同场景是否发出同一通知必须验证,不能由来电测试外推。 |
| 中断期间用户手动暂停,或打开另一个播放器 | 中断结束后机械恢复旧回复,把用户的新选择盖过去。 | 恢复前重新检查用户意图、播放编号、内容时效和路线;不自动控制其它应用重新播放。 |
| 后台仍想播报,或开启麦克风后耳机音质/路线改变 | 池中有数据就认为系统会允许播放,或继续使用旧格式。 | 确认后台播放资格与实际输出位置;输入输出能力变化后重新检查采样率、声道、延迟和缓冲,不能靠扩池解决。 |
整体设计中的“系统音频协调”怎样工作?
内部调度决定“哪条回复可以播”;系统音频协调负责“此刻系统是否允许、以什么方式播”。播放器若已代管这部分规则,就复用它,避免两套逻辑反复申请和释放。
| 时机 | 调度器需要知道什么 | 对输出链路的动作 |
|---|---|---|
| 准备开播 | 这次是短提示、持续播报还是双向对话;目标设备与当前资格是否有效。 | 先决定共存/压低/互斥策略,再按平台结果启动;资格未取得时有界等待或报错,不无限预填。 |
| 系统要求压低或暂停本应用 | 当前播放仍有效吗?是短暂让位还是已经失去继续播放条件? | 能压低时平滑降低本应用声音;需要暂停时关闭喂帧门,限制上游积压。自定义外接路线也要按约定停止设备,不能只把手机播放器静音。 |
| 短暂让位期间 | 哪些内容必须完整,哪些已过期?用户是否取消或选择了其它内容? | 完整内容可保留有限续播位置;实时旧回复按时效处理。输入权限另行核对,输出中断不代表一定能继续收音。 |
| 收到可恢复提示 | 系统允许、用户仍想听、内容有效、设备就绪四项是否都成立。 | 同一健康播放暂停后可以续播;取消或连接重建按第 04 章换身份规则处理。必要时重新预填,不倾倒旧积压。 |
| 真正结束或失败退出 | 是否还有其它有效任务依赖同一系统资源或设备压低状态? | 最后一个相关任务结束后释放本应用申请;设备压低也按同一归属撤销。释放表示允许其它应用恢复,不保证它一定自动续播。 |
Android 和 iOS 怎样对应到这套通用规则?
Android:Audio Focus(系统协调多个应用播放优先级的机制)决定让位关系;临时压低与暂停并不等价,人声内容也未必被自动压低。目标版本为 Android 15/API 35 及以上时,请求资格还受前台应用或前台服务条件约束。申请结果、延迟获准和失去资格都应进入同一播放状态管理。见 Android 官方音频焦点说明。
iOS:AVAudioSession(本应用向系统声明音频用途及共存方式的配置)会影响混音、压低和中断。duckOthers(暂时降低其它音频会话音量)与会话激活时段相关,并非“本应用此刻有声音”就自动精确启停;Apple 建议短暂使用,不应不加判断地贯穿整通长对话。若持续收音,反复停用同一个会话也可能影响输入,需按实际能力设计。见 Apple 的暂时压低说明。
Apple 还提供对其它人声节目的临时中断选项,导航提示是其典型用例,但是否适用取决于音频用途与对方会话配置;中断事件及恢复建议须按部署系统版本处理。通用原则仍是“允许恢复”加上用户与任务检查,而不是收到结束通知就必然续播。见 人声节目共存选项、音频中断处理。本文不规定一组跨版本通用的 API 参数。
手机和设备都能压低音乐时,怎样避免重复控制?
例如:其它应用音乐 → 手机系统 → 蓝牙音乐通道 → 外接设备扬声器;本应用回复通过专用数据通道在设备混入。音乐是同一份,手机系统与设备只是两个控制位置。先选择负责衰减的主要位置,记录它属于哪次有效使用;切路线时先撤销旧位置的影响,再按新路线生效。
建议的产品基线是:短播报尽量短暂让位,电话等强中断优先,长对话的音乐策略单独确认。设备端若根据语音能量压低音乐,应验证停声确认和渐变;手机若仍被系统或音频组件额外压低,要定位并协调实际生效处。不能为了避免双重衰减,就一律跳过系统权限或中断处理。
具体 case:音乐中播一条回复,期间来了电话
- 先确认音乐从哪里来、回复在哪里混入,确定本次由系统还是设备负责压低。
- 取得所需资格后播放回复;音乐继续、降低或暂停,应符合该路线支持的策略。
- 收到实际强中断信号,立即停止本应用继续喂声,并处理设备尚未播出的部分;记录用户意图与可确认的续播位置,限制暂存。
- 电话结束只提供恢复机会:若用户已取消、选择别的内容或旧回复已失效,就结束本次任务。
- 确需续播时重新确认资格与路线,按编号规则和缓冲条件恢复;最后释放本次申请,由系统与音乐应用决定音乐怎样继续。
怎样验证共存,而不是只验证“本应用有声音”?
至少组合测试:音乐与人声节目、手机与外接输出、短提示与长对话、前台与后台、电话打断与用户主动取消。分别观察两边声音是否可懂、是否重复衰减、停声间隔是否抽动、退出后是否仍压低音乐,以及中断后是否错误自动续播。标明系统与设备版本;某个通知入口存在不等于所有第三方通话或闹钟都已实测。
场景来源:近期对话归纳中的重复衰减、停声恢复与中断覆盖问题,以及上面链接的 Android/Apple 官方文档,检索于 2026-11-04。工程个别实现不直接转为通用要求,本章没有新增本地工程链接。
07 / 传输与故障恢复:断开、拥塞和设备变化后怎样继续?
| 发生什么 | 立即做什么 | 什么情况下恢复 |
|---|---|---|
| 一个功能处理变慢 | 只限制该功能的等待队列;按用途丢旧声音、转存或报错。其它功能继续收音和播放。 | 回到预算内,且使用资格仍有效;重新检查它需要的声音是否还完整。 |
| 音源切换、重启或缺了一段声音 | 标记新的连续段。唤醒检测清掉不完整窗口;录音记缺口;受保护的问题开头明确判定是否失效。 | 从新起点重新读取,不能把两段声音冒充一段连续录音。 |
| 蓝牙发不动、声音越积越多 | 按发送能力减缓提交,观察最旧声音的年龄;超期停止当前播放。半包按协议安全收尾。 | 旧发送已退出或连接已隔离,并重新确认设备可用;不补播已过期的旧回复。 |
| 启动请求没有收到设备确认 | 在约定期限后查询设备状态,区分“确认消息没到”“明确拒绝”和“结果未知”。 | 确认状态后继续;结果未知时限制重试次数与等待总时长,不能不断重复启动。 |
| 连接断开 | 撤销本次发送资格、清理旧队列。手机是否继续播放,按用户已选路线执行。 | 重新连接、检查能力并完成新的启动。是否自动续播需产品明确;不能突然改为手机外放。 |
| 来电或系统暂停音频 | 暂停或结束不允许的收音与播放,记住用户是否仍希望继续。 | 系统允许、请求仍有效、设备就绪且用户未取消,才恢复。 |
| 用户结束、切功能或退出账号 | 先禁止新声音进入;正常结束可短暂排空,主动打断应清掉未播内容。释放本次使用资格。 | 资源释放完成后才让下一功能使用。功能切换是一键完成还是需要再次触发,由产品决定。 |
具体 case:蓝牙拥塞后,新旧播放怎样隔离?
蓝牙拥塞后:怎样避免旧声音干扰新播放?
- 每次播放有唯一编号。未发出的声音、正在发送的操作与结束回调,都记录属于哪一次播放。
- 观察排队时间与实际发送结果;超过期限后停止接收本次播放的新声音,清掉仍未发送的完整旧包。
- 已经写出一半的包不能直接截断后接新包;先安全完成或隔离旧连接,避免接收端把数据拼错。
- 确认旧发送操作已停止,或旧连接已被隔离,才允许新播放取得发送资格。
- 新播放先完成启动,再发送声音。旧回调迟到时只更新旧记录,不能关闭新播放。
怎样判断“确实在播放”?
分别记录:① 应用已排队;② 本机系统已接受数据;③ 设备已收到;④ 设备已播放。前一项不能证明后一项。设备若只提供“会话已启动”,也不能据此判断每一段声音都正常出声。
条件允许时,设备报告最近收到与播放的位置、剩余声音时长、播放断供次数;发送端据此调节节奏。短缺时的声音修补可以参考成熟实时语音实现,不能承诺还原丢失内容。设备反馈不够时,用实际录音和参考声音对齐验证。
换设备后,为什么需要重新检查格式?
耳机插拔或输入输出路线改变后,重新读取实际采样率、声道、缓冲时长与延迟;需要时重建格式转换器,清理旧格式残留,并重新预填。是否改用扬声器和是否自动恢复,按用户意图处理,不能只恢复之前的配置。参考 Apple 路由变化说明。
不同蓝牙承载,怎样保留同一套上层规则?
自定义 BLE GATT 传声音,需要应用处理分片、重组和发送流量控制;标准 LE Audio 使用专门的等时传输机制,需要两端共同支持。其它串行或配件通道也可承载压缩声音。方案应把这些差异放在传输适配层,上层统一使用启动、发送、停止、排队情况和失败反馈等接口。
08 / 缓冲预算:需要多少空间,能接受多少延迟?
问题:每层各留一段声音,叠加后可能产生过大延迟;只看容量又无法判断实际积压。输入与输出应分别计账,再计算端到端总预算。
统一四个口径:容量上限以字节计算;待处理声音深度以毫秒计算;包的年龄从约定的起点连续计时;故障超时是控制等待期限。换队列不应把同一声音的年龄清零。
| 路径 | 预算包括哪些部分 | 确定依据 |
|---|---|---|
| 输入 | 实时短池、开头保留、各格式视图与私有长存储。 | 设备到达抖动、消费者读取长度、允许的开头保留时间、内存和存储速度。 |
| 输出 | 回复私有队列、目的地输出池、在途声音与设备接收缓冲。 | 回复生成节奏、连接抖动、可接受开口与持续播放延迟,以及设备可用空间。 |
| 启停与恢复 | 设备确认、状态查询、正常排空与强制结束的等待总量。 | 平台与协议合同、正常往返测量;分别设单次期限与总期限。 |
示例一:空间预算
假设声音为每秒 16,000 个采样点、单声道、每点 16 位,则每秒占 32,000 字节。2 秒约 64 KB,200 毫秒约 6.4 KB,320 毫秒约 10.24 KB。若允许保留 120 秒,则仅声音本身约占 3.84 MB。这些是计算示例,不是所有产品的默认值。
每条路径的总内存还要加上格式转换、副本、索引、压缩缓存与音频服务自身的占用。多个读取者共享同一份声音时,只算一次声音内容,但各自的独立保留区仍要计入。
示例二:延迟预算
示例:从回复声音可用到外接设备出声,20 + 40 + 40 + 80 + 20 = 200 毫秒。这是待验证的预算分配,未含云端生成与网络内部缓冲;实际各段可能重叠,应统一测量起止点,避免重复计算。各段“95% 情况下的延迟”相加,也不等于整条路径的同一指标。
示例三:输入积压多久才能追上实时?
假设已经积压 3 秒,新的声音仍以正常速度进入;发送速度始终是正常的 1.6 倍,则理想情况下追赶需要 3 ÷ (1.6 − 1) = 5 秒。这说明“连上了”与“已经跟上实时”是两个状态。前提是语音服务允许并能处理快于实时的输入。网络带宽足够本身不代表接口允许加速;若接口只按实时速度接收,新声音又持续进入,就不能仅靠补发追上。此时需选择有界等待、暂缓接收新输入,或到界后终止并提示,不能擅自丢掉问题开头。倍率是理论演算,不是下游能力承诺。
蓝牙带宽要留出额外空间
假设压缩码率为 24 kbps,每包装 20 毫秒声音,声音数据约 60 字节;再假设自定义包头为 7 字节,则应用层约需 26.8 kbps。还必须加上分片、蓝牙传输开销、重传与其它并行业务。这里的格式和数值仅用于演算,应以实际协议和并发测量替换。
09 / 遇到的问题与一句话解决方案
用下面这张表快速找到处理方向;具体机制、条件与代价见前面的对应章节。
| 遇到的问题 | 一句话解决方案 |
|---|---|
| 多个功能都要用麦克风,重复收音或互相关掉 | 同一个麦克风只收一次音,各功能各自读取,最后一个使用者退出后再关闭。 |
| 唤醒后马上说问题,连接还没准备好 | 先保存问题开头,连接就绪后从准确位置接着发送。 |
| 声音一批一批到,识别接口却要固定长度 | 先放进输入池,再按识别接口需要的长度取出。 |
| 某个功能处理太慢,拖住其它功能 | 给每个功能单独限制积压,过慢时只处理它自己的队列。 |
| 录音或回复一直积压,内存越来越大 | 给每个池设上限,录音及时转存,过期回复按规则清理。 |
| 回复忽快忽慢,播放一顿一顿 | 按播放路线在应用池或设备缓冲留出少量提前量,再按设备播放节奏持续取走。 |
| 输入、写文件或蓝牙卡住,播放也跟着停 | 让这些工作分别执行,播放回调只取声音,不等待其它操作。 |
| 短暂缺了一点声音,马上听到断音 | 实时对话可短时修补并平滑接回,完整播报可暂停后重新攒够再播。 |
| 用久了声音越来越迟,或缓冲越来越空 | 先区分长回复提前生成、拥塞和时钟差异;确认时钟差异后才微调速度。 |
| 提示音、翻译和对话同时播放,互相盖住 | 先定好谁优先、谁排队、谁压低音量,再交给同一套播放安排执行。 |
| 切换声音时突然爆音或音量跳变 | 允许时短暂淡入淡出,混音时限制总音量。 |
| 扬声器的回复又被麦克风收进去 | 在明确的一层处理回声,并提供与实际播放相符的参考声音。 |
| 应用还在收音,却突然只有静音 | 先检查系统权限、采集状态和当前麦克风,不要盲目反复重启。 |
| 蓝牙拥塞或断开后,旧回复突然补播 | 清掉过期声音,确认旧发送已停止,再开启新的播放。 |
| 上一场播放的结束消息,关掉了新播放 | 取消后重开使用新播放编号,旧消息只清理旧资源;同一次短暂停顿保留编号。 |
| 拔耳机、换设备或来电后,恢复不正确 | 重新检查播放位置和音频格式,确认用户仍希望继续后再恢复。 |
| 手机显示发送成功,设备却没声音 | 分别检查本机发送、设备接收和实际播放,不能把发送成功当作已经出声。 |
| 本应用停止说话后,音乐仍然很小 | 检查系统会话和设备压低是否仍有效,最后一个相关任务退出时才释放对应影响。 |
| 手机音乐通过蓝牙播放,被压低了两次 | 沿同一条音乐链路选主要衰减位置,协调手机系统与设备控制。 |
| 电话结束后,旧回复抢着恢复 | 把中断结束当作恢复机会,重新检查用户意图、内容时效和当前路线。 |
| 播客和回复同时讲话,两个都听不清 | 区分音乐和人声节目,按系统能力选择暂停让位而非一律叠加压低。 |