Android 音视频采集:时间戳漂移排查方案
## Android 音视频采集:时间戳漂移问题
Android 音视频采集:时间戳漂移 是这篇文章的主轴,我不再泛讲相机方向、权限申请或普通录制流程,而是只盯一种更容易把线上回放拖垮的问题:采集端看起来成功,编码端也没有直接报错,但音画会越来越不同步,或者首帧正常、几分钟后明显漂移。真正难的不是知道它会漂,而是要把漂移发生在采集打点、编码时间基、还是封装回放这三层里哪一层先失真。
我处理这类问题时,第一步不是立刻改 MediaCodec 参数,而是先把麦克风输入时间、相机帧时间、编码输出时间和文件封装时间放进同一条时间线。只要这四组时间戳不能对齐,后面任何‘感觉已经修好’的结论都不可信。
## Android 音视频采集:时间戳漂移解决方案
这类故障最稳的修法不是大改整条录制链,而是先固定时间基,再缩小漂移范围,最后补回放验证。我的顺序通常是三步:先把音视频都统一到 `elapsedRealtimeNanos` 或同一来源的单调时钟;再确认音频采样率、视频帧率和 muxer 写入节奏是否被中途重置;最后用可重复回放样本做回归,不让问题只停留在‘主观听起来差不多’。
第二步一定要补的是时间戳日志,不然每次都只能在播放器现象上猜。对于 Android 音视频采集:时间戳漂移,我更看重每 30 帧/每 1 秒聚合一次偏差,而不是把整段 logcat 打满。因为真正有用的是看偏差曲线有没有持续扩张,而不是单帧抖动。
## Android 音视频采集:时间戳漂移示例代码
先给一组围绕时间基对齐的核心实现,再补编码排查和回放校验。
1. 统一采集时间基
class AvClock {
fun nowUs(): Long = android.os.SystemClock.elapsedRealtimeNanos() / 1000L
}
class AudioStampWriter(private val clock: AvClock) {
fun nextPtsUs(): Long = clock.nowUs()
}
class VideoStampWriter(private val clock: AvClock) {
fun nextPtsUs(): Long = clock.nowUs()
}
2. 编码输出校验
fun checkMonotonicPts(samples: List<Long>): Boolean {
if (samples.isEmpty()) return true
for (i in 1 until samples.size) {
if (samples[i] < samples[i - 1]) return false
}
return true
}
fun driftUs(audioPtsUs: Long, videoPtsUs: Long): Long {
return kotlin.math.abs(audioPtsUs - videoPtsUs)
}
3. 现场排查命令
adb logcat -v time | grep -E "AudioPTS|VideoPTS|MuxPTS"
adb shell dumpsys media.camera
ffprobe -hide_banner -show_packets sample.mp4 | head -n 80
## Android 音视频采集:时间戳漂移注意点
最容易误判的点有三个。第一,采集线程和编码线程各自用不同时间源,导致日志看起来都递增,但两条时间线根本不是一个基准。第二,音频采样率协商成功了,却在重建录制器或切后台恢复时回到了默认值。第三,muxer 写入顺序正常,但播放器在低端机上因为首包时间异常把漂移放大。
所以别把‘播放器里听起来有点对不上’直接等同于编码错误。先确认是源头时间基乱了,还是文件本身正常、只是某类播放器容错差。这个区分不做,后面很容易把 Android 音视频采集:时间戳漂移 再写回一篇泛泛的录制问题总结。
## Android 音视频采集:时间戳漂移报错与排查
1. 音频越录越快
当你看到回放里人声越来越抢前,先别急着怀疑解码器。优先检查 AAC 输入时间戳是不是按采样数推进,而视频却按 wall clock 推进。两边时间基一旦不一致,前几秒可能完全正常,长录制才会慢慢炸出来。
fun nextAudioPtsUs(totalSamples: Long, sampleRate: Int): Long {
return totalSamples * 1_000_000L / sampleRate
}
2. 首段正常,切后台后漂移
如果前台录制没问题,一切到后台或锁屏恢复后就不同步,重点看录制器重建时有没有重新初始化时钟对象,或者相机/音频输入是否复用了旧状态。很多‘偶发漂移’其实是恢复流程里把时间起点重置了。
adb shell dumpsys media.audio_flinger
adb shell dumpsys media.metrics
## Android 音视频采集:时间戳漂移可运行片段
最后留一个最小样例,目的就是让时间戳漂移能被稳定复现和稳定验证。
1. 最小漂移检查对象
data class DriftSample(
val audioPtsUs: Long,
val videoPtsUs: Long
)
fun isDriftExceeded(sample: DriftSample, thresholdUs: Long = 80_000L): Boolean {
return kotlin.math.abs(sample.audioPtsUs - sample.videoPtsUs) > thresholdUs
}
2. 本地回归入口
fun runDriftRegression(samples: List<DriftSample>) {
val exceeded = samples.count { isDriftExceeded(it) }
println("drift_exceeded=$exceeded")
}
3. 回归验证命令
ffprobe -hide_banner -show_streams sample.mp4
adb logcat -d | grep -E "drift_exceeded|AudioPTS|VideoPTS"
