
Android 使用 MediaCodec 编码 Bitmap 序列时,仅设置 KEY_FRAME_RATE 无法控制输出视频时长;必须为每帧显式指定 BufferInfo.presentationTimeUs,否则系统默认以最快可能速度写入数据,导致视频播放过快、时长严重缩水。
android 使用 mediacodec 编码 bitmap 序列时,仅设置 `key_frame_rate` 无法控制输出视频时长;必须为每帧显式指定 `bufferinfo.presentationtimeus`,否则系统默认以最快可能速度写入数据,导致视频播放过快、时长严重缩水。
在使用 MediaCodec + MediaMuxer 将 ArrayList<bitmap></bitmap> 编码为 MP4 视频时,一个常见误区是认为只要配置了 MediaFormat.KEY_FRAME_RATE = 30,就能自动按 30 FPS(即每帧间隔 ≈ 33.33 ms)生成视频。但事实并非如此:KEY_FRAME_RATE 仅作为编码器内部参考参数(如影响 GOP 结构或码率分配),并不约束帧的时间戳。MediaMuxer.writeSampleData() 完全依赖 BufferInfo.presentationTimeUs 来决定该帧在视频时间轴上的位置。若未设置或重复设置为 0,所有帧将被压缩到极短时间窗口内(甚至同一时刻),最终导致播放器以远超正常速度解码——例如 300 帧被压成 3 秒,实际等效于 100 FPS 播放,视觉上就是“三倍速”。
✅ 正确做法:手动计算并注入精确时间戳
你需要为每一帧计算其应呈现的绝对时间(单位:微秒),并赋值给 bufferInfo.presentationTimeUs。推荐实现如下:
private long frameIndex = 0;
private final long frameDurationUs = 1_000_000L / FRAME_RATE; // 例如 30 FPS → 33333 μs
private long nextFrameTimeUs() {
return frameIndex++ * frameDurationUs;
}然后在 mediaMuxer.writeSampleData(...) 前插入:
bufferInfo.presentationTimeUs = nextFrameTimeUs(); mediaMuxer.writeSampleData(videoTrackIndex, encodedData, bufferInfo);
⚠️ 关键注意事项:
-
presentationTimeUs必须严格递增,且从0开始(首帧为0); - 单位必须是微秒(μs),不可误用毫秒(ms)或秒(s);
- 时间戳需在调用
writeSampleData之前设置,且每次写入一帧只设一次; - 若需支持可变帧率(VFR),可基于实际采集时间戳动态计算,但恒定帧率(CFR)场景下线性累加最稳妥。
? 补充优化建议
-
避免在循环内频繁创建对象:
Canvas canvas = inputSurface.lockCanvas(null)虽然安全,但建议复用Paint等资源,减少 GC 压力; -
增加帧同步容错:在
dequeueOutputBuffer循环中加入超时机制(如dequeueOutputBuffer(bufferInfo, 10000)),防止死锁; -
确保 I 帧间隔合理:
I_FRAME_INTERVAL = 10表示每 10 秒一个关键帧,若视频较短(如 1 或0(自适应); -
结尾显式发送 EOS:在 for 循环结束后,应调用
mediaCodec.signalEndOfInputStream()并等待BUFFER_FLAG_END_OF_STREAM,而非依赖隐式结束——这能确保编码器输出所有延迟帧。
✅ 验证效果
完成上述修改后,300 帧 @ 30 FPS 将准确生成 10 秒视频(300 ÷ 30 = 10),600 帧则为 20 秒,播放速度恢复正常。你还可以用 ffprobe 验证:
ffprobe -v quiet -show_entries stream=duration -of default VID_*.mp4
掌握时间戳控制,是 Android 硬编码从“能跑”迈向“精准可控”的关键一步。

















