测试与发布证据

Android AudioRecord 帧时间戳与 PCM 分块对齐

聚焦 AudioRecord.getTimestamp 的 framePosition/nanoTime 锚点、应用实际读取帧范围及 PCM 分块时间映射;不重复通用任务计时、格式验证、重采样或转写质量测试。

审核状态: 内容已校验;中文稿供用户审核 · 2026-10-08 · Fact IDs: ANDROID-AUDIO-01, ANDROID-TIMEBASE-2026-10, COVE-ASR-02, BENCHMARK-01, ANDROID-AUDIO-TIMESTAMP-2026-10

直接回答

采集时间戳和应用返回的缓冲区边界是不同事件。AudioRecord.getTimestamp 返回 AudioTimestamp,描述采集流水线最早可用位置的帧交付信息。framePosition 是相对于假设音频流起点的帧位置;nanoTime 是该帧在所选时钟基准下的估算采集时间。它并不定义为最近一次 read 缓冲区的结束时间,若没有帧坐标关联就把它直接贴到分块上,可能造成所有段落的时间偏移。 API 有多个边界要记录。AudioRecord.getTimestamp 从 API 24 起可用;当前暂时取不到时间戳时会返回错误,不能把失败调用中旧的字段当作当前状态。AudioTimestamp.framePosition 和 nanoTime 字段从 API 19 起存在,但 MONOTONIC/BOOTTIME 选择属于 API 24 的 timestamp 调用。Android 文档说明 MONOTONIC 与 System.nanoTime 同基准,BOOTTIME 与 elapsedRealtimeNanos 同基准。stop 后再次 startRecording 会重置 AudioRecord 帧计数,只有确认这类计数重置后才建立新 epoch;路由或格式变化应记录为事件,不应擅自归零。 应用实际读取的内容应单独计数。AudioRecord 的 read 重载可以返回字节、short 或 float 数量,返回值不会自动成为帧数。要依配置的采样编码和通道数把成功读取量换算为完整帧;无法组成完整帧的余量应记录为不完整,不能悄悄舍弃。每个 PCM 分块保存应用侧累计帧起止范围。一个样本帧包含配置中的各声道样本,但应用的 chunk 序号是自己的坐标,不会自动等于 AudioTimestamp 坐标。 只有在采集坐标关联已验证后,局部时间估算才成立。给定成功锚点 (P, T)、已证明与 P 使用同一采集起点的分块帧坐标 q、同一 recorder epoch、匹配时钟基准和稳定采样率 R,局部估算式为 t_est = T + (q - P) * 1e9 / R。这只是推导值,不是精度保证。如果第一段成功读取与 capture framePosition 的偏移没有验证,就把 q 和 t_est 标为未知,不要假设首次 read 从帧 0 开始。 只有在关联关系和容差已声明时,timestamp 差值与 read 帧差值才是诊断信号。采集 pipeline 和应用 read 之间存在缓冲,数据也可能延后、丢失、被丢弃或变换。一次轮询不匹配并不能说明发生了哪种情况。结合多个成功锚点、累计成功读取帧数、read 返回码、路由/录音状态和已知注入的不连续点来比较。分别保留调度延迟、短读、路由打断、应用丢弃和未分类时间线差异。 保留输入帧与变换后输出帧的独立坐标。分段记录输入帧范围和 session epoch;重采样或声道处理另记输出帧范围及映射,不能把原采样率直接用于变换后的样本。Android TIMEBASE_MONOTONIC 与 TIMEBASE_BOOTTIME 不可互换,墙上时间也不能替代采样位置。分两层测试:纯 PCM 加合成 (P,T)/read 范围只验证算术,不证明真实采集原点。若用扬声器播放合成脉冲再录制,应记录实际录到的 marker、路由、播放/采集方法和不确定性;源文件帧号不是 AudioRecord capture framePosition。只有测得可验证的读入原点关联才可估算,不能默认用最近 poll 校准;原点未知时映射保持 unknown。不要记录真实录音或转写。COVE-ASR-02 描述顺序分段和文本合并,不代表 Cove 已有 timestamp 映射;本文不报告丢帧率或真机精度。

转写分块需要可信的媒体时间偏移

Android 工程师把实时 AudioRecord 采集切成 PCM 分块以供语音处理,之后需要让每段转写回到录音中的近似位置。一次测试在短读后出现时间缺口,但应用只有 UI 显示时长,不清楚是采集帧延迟、确实丢失,还是稍后才被读取。

决策

仅在帧起点、epoch、采样率和时钟基准都已验证时,才把成功 AudioTimestamp 锚点与单独统计的应用读取帧线关联。无法证明时保留未知,并分开记录变换前后坐标。

预期结果

报告只给出证据支持的分块时间估算,或保留 unknown;不把差异包装成丢帧证明或精度结论。

需要收集的证据

只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:

  • ANDROID-AUDIO-01:Android 采集 API 定义了录音和音频格式边界;应用仍需记录实际读取的帧和状态。
  • ANDROID-AUDIO-TIMESTAMP-2026-10:官方 AudioRecord/AudioTimestamp 文档定义帧位置、估算时间及 timestamp 状态;锚点不是 read 缓冲区的精确边界。
  • ANDROID-TIMEBASE-2026-10:MONOTONIC 与 BOOTTIME 语义不同,原始时间值只能在兼容时钟域和生命周期内比较。
  • COVE-ASR-02:现有事实描述 PCM 分段、顺序推理与文本合并,不证明存在 framePosition 到 chunk 的时间映射。

发布结论只对所记录的配置和验收集有效;扩大适用范围需要更多记录。

实施流程

  1. 定义时间戳问题和 epoch. 记录偏移用途、录音器身份、开始/停止事件、API 级别、采样格式、通道数及时钟基准。仅在确认帧计数重置后建立新 epoch;路由或格式变化另记,不能假定计数归零。
  2. 只采纳成功的时间戳锚点. 在已知采集点轮询 getTimestamp,并把状态、framePosition、nanoTime 和 timebase 成组保存。只有 SUCCESS 才更新锚点;查询失败表示当前不可用,不能把旧字段冒充当前时间。
  3. 按成功 read 数量统计帧. 每次 read 记录重载和返回单位:字节、short 或 float。根据配置的编码和通道数换算完整样本帧,记录短读、尾数或错误,并独立维护应用读取帧范围,不与硬件 timestamp 位置混为一谈。
  4. 先证明读取坐标再估算时间. 证明应用帧坐标 q 与 capture 位置 P 使用相同起点、recorder epoch、稳定采样率和时钟基准;不能默认用最近的 timestamp poll 当原点。只有如此才能局部估算 t_est = T + (q - P) * 1e9 / R;否则将映射标为未知。
  5. 用独立证据排查时间线不连续. 在声明的容差下比较多个成功锚点和已接收 read 帧。结合 read 结果、路由回调、stop/start、重采样和主动丢弃记录异常。一次 timestamp/read 不匹配不能证明丢帧或判定原因。
  6. 区分夹具层次并保护隐私. 纯 PCM 加合成 P/T 锚点和 read 范围只验证算术。真实麦克风脉冲测试要记录实际录到的 marker、路由、播放/采集方法和不确定性;源文件帧标签不是采集 framePosition。原点未验证时映射保持 unknown;不记录真实 PCM 或转写。

让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。

验收标准

  • 每个锚点记录 AudioRecord session epoch、getTimestamp 状态、framePosition、nanoTime、timebase 和采样格式;失败查询不复用旧值,epoch 只随已确认的计数重置变化。
  • 成功 read 数量依据真实字节/short/float 单位和配置换算为完整帧;在偏移验证前,应用读帧索引与 capture framePosition 保持分离。
  • 帧时间公式仅用于同一已验证采集起点、epoch、稳定采样率和兼容时钟域;纯合成元组只验证算术,采集原点未知时映射保持 unknown。
  • 没有独立证据时不把 timestamp/read 差值称作丢帧数;分段、重采样、路由变化、短读及录音器重置分别记录。

决策对照表

维度选择或证据解读方式
任务时长时钟对具名耗时区间使用 monotonic 或 boottime 两端点回答经过多久;不能定位采集流中的单个样本帧。
AudioTimestamp 锚点同一采集 epoch 下成功取得 framePosition/nanoTime 对把估算采集时间关联到帧坐标;它不是 read 缓冲区边界,也不保证设备精度。
应用 read 分块索引从成功 AudioRecord read 结果累计出的帧范围描述交付给应用的数据;缓冲或主动丢弃可能使零点不同于采集 framePosition。
转写段落顺序分块 ID 和顺序合并文本结果能保持处理次序,但自身不包含源音频时间偏移。

需要避免的失败模式

  • 把 AudioTimestamp 当作最近 read 缓冲区的起点或终点。
  • 默认应用成功读取的累计帧序号与采集 pipeline 的 framePosition 使用同一零点。
  • 仅凭一个 timestamp 差值就宣称丢帧数量或原因。

还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。

最小复现记录

层级记录内容
设备厂商、型号、芯片、内存档位、操作系统版本
软件应用版本和 Git 提交号
模型系列、变体、版本、格式、文件长度、摘要、量化方式
运行时名称、版本、请求后端及实际后端证据
工作负载样例版本、输入摘要、提示词摘要、输出策略
结果完成、失败、取消、回退及保护隐私的诊断信息

发布检查表

  • ☐ 主要搜索问题已回答,且没有未经支持的数字。
  • ☐ 所有模型文件和运行时版本均已固定。
  • ☐ 代表性任务和失败处理策略均有说明。
  • ☐ 生命周期、取消、清理和回退均已测试。
  • ☐ 用户内容与网络边界均有说明。
  • ☐ 结论只适用于被记录的配置。
  • ☐ 新增测试结果时链接到原始方法或证据。

来源与相关证据

中文部署与排错专题见。奇连 AI 端侧专题。

常见问题

AudioRecord.getTimestamp 会给最近 read 缓冲区的结束时间吗?

不会。Android 说明该 timestamp 反映采集流水线最早可用位置的帧交付信息;AudioTimestamp.nanoTime 是 framePosition 对应采集时间的估算值。缓冲区偏移需另行建立。

可以把第一次成功 read 当作 frame 0 吗?

没有证据时不可以。纯 PCM 加合成 P/T 元组只验证算术,不证明真实录音起点。扬声器播放脉冲再录制时,应记录实际 marker、路由、方法与不确定性;源文件帧号不能替代采集 framePosition。不要默认用最近 poll 校准;原点未验证时映射保持 unknown。

timestamp 与 read 帧数不匹配就说明丢帧了吗?

不能这样判断。它可能来自缓冲、延后读取、录音器重置、路由变化、变换或数据丢失。要结合重复成功锚点、read 返回值和独立事件记录再分类。

AudioTimestamp.nanoTime 可以直接与墙上时间比较吗?

不可以直接比较。调用选择 MONOTONIC 或 BOOTTIME,它们属于不同的 Android 单调时钟域。存储并记录所选基准,仅比较起点和生命周期兼容的值;日历时间另存字段。