一、视频缩放与裁剪:从Matrix到裁剪模式
视频渲染中,缩放和裁剪是绕不开的话题。说白了,就是怎么把视频画面塞进你的SurfaceView里。我刚开始做视频播放器时,总觉得这很简单——不就是调个大小吗?结果第一次遇到不同分辨率的视频源,画面要么被拉伸变形,要么显示不全,用户反馈直接炸了。
嗯,今天我们就来彻底搞懂这件事。
1.1 Matrix变换:一切缩放裁剪的基础
Android里的Matrix,本质上是一个3x3的变换矩阵。它控制着坐标的平移、缩放、旋转和错切。对于视频渲染,我们主要用它来做两件事:缩放和平移。
我个人习惯把Matrix理解为「一个可以随时调整的投影仪镜头」。你旋转旋钮,画面就放大缩小;你移动镜头,画面就上下左右偏移。
来看一段实际代码。假设我们要把视频画面放大1.5倍,并居中显示:
Matrix matrix = new Matrix();
// 先缩放,后平移
matrix.setScale(1.5f, 1.5f, pivotX, pivotY);
// 或者用setScaleX/setScaleY单独控制
matrix.setScaleX(1.5f);
matrix.setScaleY(1.5f);
// 应用给SurfaceView的Canvas
canvas.drawBitmap(videoFrame, matrix, null);
这里有个坑,我踩过好几次——setScale和postScale的区别。setScale是直接设置,会覆盖之前的变换;postScale是追加变换,会保留之前的操作。如果你先平移再缩放,用setScale会把平移也清掉。所以,我建议统一用postXxx系列方法,或者每次重新构建Matrix。
1.2 setScaleX/setScaleY:精细控制每一轴
这两个方法,说白了就是分别控制X轴和Y轴的缩放比例。为什么需要分开控制?因为视频源的分辨率和屏幕分辨率往往不一致。
举个例子:视频是1920x1080(16:9),你的SurfaceView是1080x1920(9:16)。这时候如果统一缩放,画面会被压扁。你需要让X轴和Y轴各自独立缩放,才能保持比例。
Matrix matrix = new Matrix();
float scaleX = (float) surfaceWidth / videoWidth;
float scaleY = (float) surfaceHeight / videoHeight;
matrix.setScaleX(scaleX);
matrix.setScaleY(scaleY);
我在项目中遇到过一种情况:某些低端设备上,setScaleX/setScaleY的精度不够,导致画面边缘出现1像素的锯齿。解决方案是改用setScale(float, float, float, float)方法,指定缩放中心点,精度会更高。
1.3 视频裁剪模式:CENTER_CROP / FIT_CENTER / FIT_XY
这三种模式,是Android系统预定义的裁剪策略。你可以在ImageView里看到它们,但用在SurfaceView上,原理完全一样。
| 模式 | 行为 | 适用场景 |
|---|---|---|
| CENTER_CROP | 保持比例,缩放至填满容器,裁剪超出部分 | 视频背景、全屏播放(允许裁边) |
| FIT_CENTER | 保持比例,缩放至完全显示,留黑边 | 视频预览、缩略图(必须看到完整画面) |
| FIT_XY | 不保持比例,拉伸至填满容器 | 游戏画面、特殊效果(允许变形) |
这三种模式的实现,本质上就是Matrix的不同计算方式。我画了一张图,帮你理解它们的区别:
1.4 自定义裁剪算法:当系统模式不够用时
系统提供的三种模式,覆盖了大部分场景。但总有一些特殊情况,比如:
- 用户想要「顶部对齐裁剪」(类似Instagram的封面裁剪)
- 视频画面需要旋转后再裁剪
- 多个视频拼接时,每个视频的裁剪区域不同
这时候,你就得自己写裁剪算法了。核心思路其实很简单:先计算缩放比例,再计算偏移量。
我曾经做过一个视频编辑功能,用户需要自由拖动裁剪框。当时我写了一个通用的裁剪算法:
public Matrix calculateCropMatrix(
int videoWidth, int videoHeight,
int surfaceWidth, int surfaceHeight,
float cropLeft, float cropTop,
float cropRight, float cropBottom) {
Matrix matrix = new Matrix();
// 1. 计算视频的宽高比
float videoAspect = (float) videoWidth / videoHeight;
float surfaceAspect = (float) surfaceWidth / surfaceHeight;
// 2. 计算基础缩放(保持比例)
float scale;
float offsetX = 0, offsetY = 0;
if (videoAspect > surfaceAspect) {
// 视频更宽:按高度缩放
scale = (float) surfaceHeight / videoHeight;
offsetX = (surfaceWidth - videoWidth * scale) / 2f;
} else {
// 视频更高:按宽度缩放
scale = (float) surfaceWidth / videoWidth;
offsetY = (surfaceHeight - videoHeight * scale) / 2f;
}
// 3. 应用裁剪区域(归一化坐标 0~1)
float cropWidth = cropRight - cropLeft;
float cropHeight = cropBottom - cropTop;
float cropScaleX = 1f / cropWidth;
float cropScaleY = 1f / cropHeight;
// 4. 组合变换:先平移裁剪区域到原点,再缩放
matrix.preTranslate(-cropLeft * videoWidth, -cropTop * videoHeight);
matrix.preScale(cropScaleX, cropScaleY);
matrix.postScale(scale, scale);
matrix.postTranslate(offsetX, offsetY);
return matrix;
}
1.5 性能优化:别让Matrix成为瓶颈
Matrix操作本身很快,但如果你每帧都重新创建Matrix对象,GC就会频繁触发。我见过一个项目,每帧都new Matrix(),结果在低端机上卡成PPT。
我的建议是:
- 复用Matrix对象:在初始化时创建,每帧只调用set/post方法
- 避免在渲染线程中做复杂计算:裁剪参数变化时,在UI线程算好Matrix,渲染线程只负责应用
- 使用硬件加速:SurfaceView默认支持硬件加速,Matrix变换会由GPU完成,CPU只负责计算参数
1.6 实战:实现一个可切换裁剪模式的视频播放器
最后,我们把这些知识串起来。假设你要做一个视频播放器,支持用户切换裁剪模式:
public enum CropMode {
CENTER_CROP, FIT_CENTER, FIT_XY, CUSTOM
}
public Matrix getMatrixForMode(CropMode mode,
int vw, int vh,
int sw, int sh) {
Matrix matrix = new Matrix();
float sx = (float) sw / vw;
float sy = (float) sh / vh;
switch (mode) {
case FIT_XY:
matrix.setScale(sx, sy);
break;
case FIT_CENTER:
float scale = Math.min(sx, sy);
float dx = (sw - vw * scale) / 2f;
float dy = (sh - vh * scale) / 2f;
matrix.setScale(scale, scale);
matrix.postTranslate(dx, dy);
break;
case CENTER_CROP:
scale = Math.max(sx, sy);
dx = (sw - vw * scale) / 2f;
dy = (sh - vh * scale) / 2f;
matrix.setScale(scale, scale);
matrix.postTranslate(dx, dy);
break;
case CUSTOM:
// 调用自定义算法
matrix = calculateCropMatrix(vw, vh, sw, sh,
0.1f, 0.1f, 0.9f, 0.9f);
break;
}
return matrix;
}
嗯,到这里,视频缩放与裁剪的核心内容就讲完了。你可能会问:为什么不用SurfaceView自带的setScaleX/setScaleY?因为那些方法操作的是View本身,而不是视频内容。我们要操作的是「视频帧的绘制区域」,所以必须用Matrix来控制Canvas的变换。
记住一句话:Matrix是视频渲染的「遥控器」。你按哪个键,画面就怎么动。掌握了Matrix,你就掌握了视频渲染的主动权。
公众号:蓝海资料掘金营,微信deep3321