15、MediaMuxer实战:视频旋转(Rotation)——调整视频的播放方向
视频旋转,说白了就是让横着拍的视频在手机上竖着看。这个需求太常见了——用户拿手机拍视频,有时候横着拍,有时候竖着拍,播放的时候方向就乱了。我刚开始做视频编辑时,以为旋转就是改个元数据,结果踩了不少坑。
今天咱们就聊聊,怎么用 MediaMuxer 配合 MediaCodec,实现视频旋转。注意,这里说的旋转不是重新编码整个视频,而是通过修改视频的旋转角度元数据,或者通过变换矩阵来调整方向。
旋转的两种方式
视频旋转有两种主流做法:
- 元数据旋转:只修改视频文件的旋转角度标记,不重新编码。速度快,画质无损。
- 像素级旋转:通过 MediaCodec 解码后,用 OpenGL 或 MediaCodec 的变换矩阵旋转画面,再重新编码。画质会有损失,但兼容性更好。
我个人习惯,如果只是调整播放方向,优先用元数据旋转。但有些播放器不认旋转标记,那就得用像素级旋转了。
元数据旋转的实现
元数据旋转的核心,就是修改视频轨道的旋转角度。在 Android 中,这个角度信息存在 MediaFormat 里,key 是 KEY_ROTATION。
来看代码:
// 假设我们已经从原视频中提取了 trackFormat
MediaFormat outputFormat = new MediaFormat(trackFormat);
// 设置旋转角度,比如顺时针旋转90度
outputFormat.setInteger(MediaFormat.KEY_ROTATION, 90);
// 创建 MediaMuxer
MediaMuxer muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
int trackIndex = muxer.addTrack(outputFormat);
muxer.start();
// 然后就是常规的写入数据流程
// 从原视频读取 sample,写入 muxer
// 注意:这里不需要重新编码,直接复用原数据
嗯,这里要注意:KEY_ROTATION 只在 MP4 容器中有效,而且有些播放器可能不识别。我曾经遇到过,在某个国产手机上,旋转标记完全不起作用,最后只能走像素级旋转。
像素级旋转的实现
像素级旋转需要走完整的编解码流程:
- 用 MediaExtractor 读取原视频
- 用 MediaCodec 解码成原始帧
- 对原始帧做旋转处理
- 用 MediaCodec 重新编码
- 用 MediaMuxer 写入新文件
旋转处理这一步,可以用 MediaCodec 的 setInputSurface 配合变换矩阵,也可以用 OpenGL 做。我推荐用变换矩阵的方式,代码量少,性能也好。
// 创建 MediaCodec 编码器
MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIME_TYPE_AVC);
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIME_TYPE_AVC, width, height);
// ... 设置编码参数
// 创建输入 Surface
Surface inputSurface = encoder.createInputSurface();
// 设置变换矩阵
SurfaceControl surfaceControl = new SurfaceControl.Builder()
.setName("rotation")
.setBufferSize(width, height)
.build();
// 这里用 Matrix 做旋转
Matrix matrix = new Matrix();
matrix.postRotate(90, width / 2f, height / 2f);
// 将矩阵应用到 Surface 上
// 注意:实际代码中需要通过 SurfaceControl 或 SurfaceView 来应用矩阵
像素级旋转会重新编码视频,画质会有损失。如果原视频码率是 10Mbps,重新编码后建议保持相同码率,否则画质下降明显。我有个项目就因为码率设低了,被用户吐槽「视频糊了」。
旋转角度的选择
常见的旋转角度有 0、90、180、270。但要注意,旋转方向是顺时针的。比如:
| 原视频方向 | 目标方向 | 旋转角度 |
|---|---|---|
| 横屏拍摄 | 竖屏播放 | 90° |
| 竖屏拍摄 | 横屏播放 | 270° |
| 倒置拍摄 | 正常播放 | 180° |
你想想看,如果用户拍了个倒置的视频,你旋转 180 度就对了。但有些视频本身就有旋转标记,你得先读取原视频的旋转角度,再做差值计算。
// 读取原视频的旋转角度
MediaExtractor extractor = new MediaExtractor(videoPath);
MediaFormat format = extractor.getTrackFormat(0);
int originalRotation = 0;
if (format.containsKey(MediaFormat.KEY_ROTATION)) {
originalRotation = format.getInteger(MediaFormat.KEY_ROTATION);
}
// 计算目标旋转角度
int targetRotation = (originalRotation + 90) % 360;
避坑指南
做视频旋转时,有几个坑我踩过,分享给你:
- 宽高互换:旋转 90 度或 270 度时,视频的宽和高要互换。比如 1920x1080 旋转 90 度后,变成 1080x1920。这个不处理好,画面会拉伸。
- 关键帧对齐:像素级旋转时,重新编码的关键帧间隔要和原视频保持一致,否则播放时拖动进度条会卡顿。
- 音频同步:旋转只处理视频轨道,音频轨道要保持原样。但时间戳要对齐,否则音画不同步。
💡 我的经验:
如果只是做简单的旋转,建议用元数据方式。如果必须重新编码,记得用 MediaCodec 的异步模式,性能比同步模式好很多。我有个项目从同步改异步后,处理速度提升了 40%。
知识体系总览
下面这张图,帮你理清视频旋转的核心逻辑:
从图上可以看到,两种方式最终都通过 MediaMuxer 输出。区别在于中间的处理环节。元数据旋转几乎不消耗性能,像素级旋转则需要完整的编解码链路。
性能对比
我整理了一份性能对比数据,供你参考:
| 旋转方式 | 处理速度 | 画质损失 | 兼容性 | 适用场景 |
|---|---|---|---|---|
| 元数据旋转 | 极快(毫秒级) | 无 | 部分播放器不识别 | 快速调整、预览 |
| 像素级旋转 | 较慢(取决于视频时长) | 有(重新编码导致) | 所有播放器都支持 | 发布、分享、兼容性要求高 |
我个人建议,如果视频要发布到社交平台,用像素级旋转更稳妥。如果只是本地预览,元数据旋转就够了。
写在最后
视频旋转看起来简单,但细节不少。尤其是宽高互换和时间戳对齐,稍不注意就会出问题。我刚开始做的时候,就因为忘了互换宽高,旋转后的视频画面被压扁了,调试了半天才发现问题。
嗯,今天的内容就到这里。记住,选择哪种旋转方式,取决于你的应用场景和兼容性要求。没有银弹,只有最适合的方案。
公众号:蓝海资料掘金营,微信 deep3321