测试与发布证据

为 Android MediaCodec 解码音频建立时间轴合同

区分压缩媒体 PTS、MediaCodec 解码输出缓冲区、应用处理时间与 AudioRecord 采集来源,并在格式变化或 flush 后重建本地时间段。

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

直接回答

MediaCodec/BufferInfo 与同步 INFO_OUTPUT_FORMAT_CHANGED 为 API 16;AudioTimestamp.nanoTime 为 API 19;getOutputBuffer 与异步 callback 为 API 21;KEY_PCM_ENCODING 为 API 24。较旧同步客户端使用 buffer 数组和 BufferInfo 范围。先固定目标 API 与解码路径,不要假设较新信号存在于旧版本。 媒体时间必须与采集时间分开。MediaExtractor.getSampleTime 是当前输入样本的微秒媒体 PTS;MediaCodec 输出 presentationTimeUs 也是微秒,并由对应输入 PTS 导出。AudioTimestamp.nanoTime 则是纳秒;AudioRecord 获取时还要记录所请求的 MONOTONIC 或 BOOTTIME clock base。单位换算并不会建立共同原点或 epoch。应分别保存时钟类型、单位、帧/样本关联和原点证据;不能因为数值接近就对齐。 每个输出都要先保存实际 MediaFormat 和缓冲区元数据,再释放 index。API 21+ 的 getOutputBuffer 会把 ByteBuffer position/limit 设置到有效数据;更旧的数组路径要结合 BufferInfo offset/size 检查所选 buffer。同步路径观察 INFO_OUTPUT_FORMAT_CHANGED,异步路径使用 API 21 回调。记录采样率和声道数,并仅在 API 提供时读取 PCM encoding。零长度输出没有样本窗口,必须忽略其 PTS;EOS flag 也可能伴随非空末尾数据,先消费经检查的字节范围,再标记 stream terminal。 把有效输出格式变化当作格式 epoch。将请求的 MediaFormat 与返回格式分开;实际 sample rate、channel count 或 PCM encoding 可能缺失或不同。为每个缓冲区记录实际解释值,不确定时标记 unknown。不要用压缩帧或上一 epoch 的格式计算 PCM 帧。格式变化后,重新验证缓冲是否按新帧宽对齐,以及下游接口能否表达此转变。 MediaExtractor.seekTo 只移动 extractor 的样本游标,本身不会使 MediaCodec buffer index 失效。应用必须声明 seek 后如何向 decoder 提交输入,以及其协议是否 flush、重新配置或替换 codec。只有 codec reset 操作才使该 generation 的 index 失效。如果 owner 调用 flush,就作废旧 index 和关联,并通过串行化转换确认新 epoch 的输出边界;不能因为回调在 seek 调用之后到达,就把它标为新片段。 flush 在不同处理模式下行为不同。Android 文档说明 flush 会立即使旧输入/输出 index 失效;此前尚未处理的异步 callback 仍可能送达,但它们的 index 已无效,必须丢弃。异步模式在 flush 返回后还要调用 start 才会恢复 callbacks;不要把这条额外规则套到同步模式。将 callback 处理与 seek/flush 控制交给同一 codec owner 串行化并建立边界,只接受新 generation 获得的 buffer。若无法安全区分迟到事件和新输出,应在测试过的生命周期下 reset 或替换 codec,而不是释放失效或复用中的 index。 仅对已确认的 PCM 推导时间窗。若交错 PCM 的采样率为 R、声道数为 C、每个样本 B 字节,则一帧占 C × B 字节;只有 size 能被该帧宽整除时,frameCount = size / (C × B) 才成立。在此前提下,估算结束时间(微秒)为 startPtsUs + round(frameCount × 1,000,000 / R)。使用溢出安全运算并记录取整规则。压缩帧、打包方式未知的平面布局、零长度 EOS、partial 数据或格式 epoch 未知时都不要套用。 用合成输入和实际 decoder 路径测试完整 owner 协议:记录 API/driver 身份、输入 PTS、输出范围、格式变化、延迟输出、零长度与非空 EOS、extractor 单独 seek、seek 后 flush 和迟到异步 callback。断言旧 index 被丢弃且新 generation 未误收旧数据。记录微秒 PTS、纳秒 AudioTimestamp.nanoTime 和所选 timebase,但没有独立证据就不推导映射。本文只定义解码器输出方法,不代表 Cove 集成、麦克风同步、模型兼容或设备性能。

decoder seek 后,旧音频块被误当成当前数据

Android 应用解码本地压缩录音,再将 PCM 分块交给后续阶段。extractor seek 后,第一次读取留下的元数据仍待处理。decoder PTS 为微秒,AudioTimestamp.nanoTime 为纳秒且带 clock base;应用还没有建模 codec flush、格式变化或两者之间经过验证的映射。

决策

为每个解码输出窗口记录有效缓冲区范围、实际格式、呈现时间戳、flags 和应用 seek epoch;丢弃上一 epoch 中迟到的数据。

预期结果

下游音频块依照实测 decoder 输出排序或标记不连续,不伪造连续的采集时间,也不声称模型质量结果。

需要收集的证据

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

  • ANDROID-MEDIACODEC-PCM-TIMELINE-2026-10:BufferInfo 的 offset、size、flags 和 presentationTimeUs 必须与实际输出格式及 epoch 一起解释。
  • ANDROID-AUDIO-01:PCM 输入合同要分别记录声道、采样率和编码。
  • COVE-ASR-01:现有 PCM 处理路径不证明应用读取任意 MediaCodec 音频输出。

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

实施流程

  1. 声明时钟、API 级别和代次. 分别记录微秒媒体 PTS、带 clock base 的纳秒 AudioTimestamp.nanoTime 和应用处理时间。BufferInfo 为 API 16;getOutputBuffer 与异步 callback 为 API 21;KEY_PCM_ENCODING 为 API 24。只有 owner 执行过已验证 flush/reconfigure/reset 协议时才创建新 decoder generation,不能仅因 MediaExtractor.seekTo 递增。
  2. 保存实际格式和缓冲区元数据. 记录返回的 MediaFormat、采样率与声道数;仅在支持 API 上读取 KEY_PCM_ENCODING。API 21+ getOutputBuffer 的 position/limit 标出有效数据;较旧同步路径结合 BufferInfo offset/size 检查数组缓冲。释放 index 前保存 PTS/flags。
  3. 验证输出字节和 EOS 顺序. 按当前缓冲区验证有效字节范围。零长度 EOS 忽略 PTS;若 EOS 带有非空输出,先处理该范围再标记结束。只有 PCM、通道/编码已知且字节对齐时才计算帧数。
  4. 隔离 codec reset 与迟到回调. MediaExtractor.seekTo 只移动样本游标,不会使 codec index 失效。若 seek 协议执行 flush,该代次的 index 才失效。异步模式丢弃 outstanding callbacks,并在 flush 返回后调用 start;将 owner/callback 串行化,避免把旧事件标为新输出。
  5. 用夹具验证单位和所有权. 对空/非空 EOS、仅 extractor seek、seek 后 flush、迟到异步 callback、延迟输出和格式变化记录输入/输出 PTS、范围、格式、flags、时钟单位/base 与 codec generation。保留未知映射,不推导采集时间。

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

验收标准

  • 每个解码音频块都能关联实际输出格式、有效字节范围、flags、PTS 和应用 epoch。
  • 只有确认 PCM 布局、采样率、声道数和缓冲区长度对齐时才计算帧数或结束时间。
  • 仅 extractor seek 不使 codec index 失效;flush/reset 才会。测试异步 flush 后 start 和迟到 callback 丢弃,并在终止前消费非空 EOS 数据。
  • 本文不把 decoder PTS 等同 AudioRecord 原点、墙上时钟、Cove 模型输入支持或性能结果。

决策对照表

维度选择或证据解读方式
Decoder 输出 PTS把 `BufferInfo.presentationTimeUs` 保留为媒体时间轴数值。它来源于输入呈现时间戳,不自动代表采集时钟或墙上时钟。
应用接收时间另行记录 callback/dequeue 到达时间用于诊断。能观察调度行为,但不能替代解码输出附带的媒体时间戳。
AudioRecord 时间戳明确保留 AudioTimestamp.nanoTime、framePosition 和 MONOTONIC/BOOTTIME base。它是纳秒,BufferInfo PTS 是微秒;换算单位不能证明两者共享时钟原点。
派生 PCM 窗口仅对已确认 PCM 按 PTS_us + round(frames × 1,000,000 / sampleRate) 估算 endUs。须格式确认且帧对齐;不适用于零长度 EOS、压缩数据、未知布局或旧代次。

需要避免的失败模式

  • 不要把 MediaCodec 的媒体 PTS 等同 AudioRecord 采集时间或墙上时钟。
  • 不要忽略输出 offset/size,也不要把零长度 EOS 当成 PCM 样本。
  • 不要假设配置请求与实际输出格式相同,或 flush 后时间戳会归零。
  • 不要把格式连续、PTS 连续或某台设备结果写成 Cove 模型支持或性能指标。

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

最小复现记录

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

发布检查表

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

来源与相关证据

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

常见问题

AudioTimestamp.nanoTime 与 BufferInfo.presentationTimeUs 能否换算后对齐?

不能。AudioTimestamp.nanoTime 是纳秒,AudioRecord 路径还有 MONOTONIC 或 BOOTTIME base;BufferInfo PTS 是由输入 PTS 得出的微秒媒体时间。除以 1000 只变单位,不变时钟原点或采集到媒体的映射。

带数据的 EOS 输出应如何处理?

EOS flag 也可能附在有效末尾数据上。先消费经 offset/size 检查的字节范围,再标记 decoder stream 结束。零长度 EOS 无样本窗口,应忽略其 PTS。

extractor seek 会使 codec index 失效吗?异步 flush 后要做什么?

MediaExtractor.seekTo 只移动 extractor 样本游标。若 codec owner 再执行 flush,旧 index 才失效;未处理 callbacks 必须丢弃,不能重新标记。Android 要求异步 flush 返回后调用 start 恢复 callback。两者都不保证 PTS 归零。