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 容器中有效,而且有些播放器可能不识别。我曾经遇到过,在某个国产手机上,旋转标记完全不起作用,最后只能走像素级旋转。

像素级旋转的实现

像素级旋转需要走完整的编解码流程:

  1. 用 MediaExtractor 读取原视频
  2. 用 MediaCodec 解码成原始帧
  3. 对原始帧做旋转处理
  4. 用 MediaCodec 重新编码
  5. 用 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%。

知识体系总览

下面这张图,帮你理清视频旋转的核心逻辑:

视频旋转核心流程 输入视频 选择方式 元数据旋转 像素级旋转 修改 KEY_ROTATION 直接复用原数据 MediaCodec 解码 变换矩阵旋转 MediaCodec 编码 输出旋转后视频

从图上可以看到,两种方式最终都通过 MediaMuxer 输出。区别在于中间的处理环节。元数据旋转几乎不消耗性能,像素级旋转则需要完整的编解码链路。

性能对比

我整理了一份性能对比数据,供你参考:

旋转方式 处理速度 画质损失 兼容性 适用场景
元数据旋转 极快(毫秒级) 部分播放器不识别 快速调整、预览
像素级旋转 较慢(取决于视频时长) 有(重新编码导致) 所有播放器都支持 发布、分享、兼容性要求高

我个人建议,如果视频要发布到社交平台,用像素级旋转更稳妥。如果只是本地预览,元数据旋转就够了。

写在最后

视频旋转看起来简单,但细节不少。尤其是宽高互换和时间戳对齐,稍不注意就会出问题。我刚开始做的时候,就因为忘了互换宽高,旋转后的视频画面被压扁了,调试了半天才发现问题。

嗯,今天的内容就到这里。记住,选择哪种旋转方式,取决于你的应用场景和兼容性要求。没有银弹,只有最适合的方案。


公众号:蓝海资料掘金营,微信 deep3321