背景:
针对视频满屏播放,有时候如果仅仅是设置某个控件宽高都是match_parent, 但是视频源却没有和设备的比例保持一致,就会存在画面被拉伸的情况,那么应该怎么避免呢?
解决方法:
第一步:确定视频源的比例:根据帧宽高算出比例大致是9:5
第二步:确定设备的宽高比例,我们设备屏幕是10寸,宽高是:1920*1200,比例是8:5
第三步:分析,从上面两个比值来看,视频源比例和设备比例不一致,在高度相同的情况下,视频源要宽一点,设备要窄一点。如果设置视频控件的宽高是match_parent, 在源和设备高度一致的情况下,会将视频做横向压缩。
第四步:计算,两种方案(a)控件宽度固定,高度适当做比例压缩:9/5 = 1920/h ===> h = 1920/( 9/5),高度从1200压缩至1065。(b)如果是控件高度固定,宽度做适当调整:9/5 = w/1200=====>w = 1200*(9/5),宽度从1920拉伸到2160。这个已经超出设备屏幕,所以采用方案a
实际需求中,或许也会要求其他缩放模式,那么可以借鉴exoplayer中的源码,做出适合项目需求的缩放比例。
分析 google 中exoplayer是如何计算视频播放比例:
exoplayer 中提供了如下几种尺寸适配方式:
/**
* Resize modes for {@link AspectRatioFrameLayout}.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL,
RESIZE_MODE_ZOOM})
public @interface ResizeMode {}
/**
* Either the width or height is decreased to obtain the desired aspect ratio.
* 压缩宽或是高来适配所需的宽高比
*/
public static final int RESIZE_MODE_FIT = 0;
/**
* The width is fixed and the height is increased or decreased to obtain the desired aspect ratio.
* 宽是固定的,拉伸或是压缩高度来适配所需的宽高比
*/
public static final int RESIZE_MODE_FIXED_WIDTH = 1;
/**
* The height is fixed and the width is increased or decreased to obtain the desired aspect ratio.
* 高度是固定的,拉伸或是压缩宽度来适配所需的宽高比
*/
public static final int RESIZE_MODE_FIXED_HEIGHT = 2;
/**
* The specified aspect ratio is ignored.
* 忽视指定的宽高比
*/
public static final int RESIZE_MODE_FILL = 3;
/**
* Either the width or height is increased to obtain the desired aspect ratio.
* 拉伸宽或是高来适配所需的宽高比
*/
public static final int RESIZE_MODE_ZOOM = 4;
从源码中可以看到:当设置了ResizeMode之后,会调用onMesasure来重新计算控件的宽高:
查看onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//RESIZE_MODE_FILL 模式是忽略设置的宽高比,所以直接return,不用去测量
if (resizeMode == RESIZE_MODE_FILL || videoAspectRatio <= 0) {
// Aspect ratio not set.
return;
}
int width = getMeasuredWidth();//获取控件的实际宽
int height = getMeasuredHeight();//获取控件的实际高
float viewAspectRatio = (float) width / height; //控件的宽高比
/**
*如果控件的宽高比和视频的宽高比在允许的范文内,也不用再次去测量
*/
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
// We're within the allowed tolerance.
return;
}
switch (resizeMode) {
case RESIZE_MODE_FIXED_WIDTH://宽度是固定的,高度要重新计算
/**
*比如说 视频的宽高比例是9/5,宽度是固定的,则高度 = width * (5/9) 也即 width / (9/5)
*/
height = (int) (width / videoAspectRatio);
break;
case RESIZE_MODE_FIXED_HEIGHT://高度固定,重新计算控件的宽度
/**
* 计算公式是:9 /5 = width(未知) /height(已知) =》width= height* (9/5)
*/
width = (int) (height * videoAspectRatio);
break;
case RESIZE_MODE_ZOOM://做拉伸
if (aspectDeformation > 0) {//如果这个大于0,说明视频源宽高比大于控件宽高比,
/**
* 控件的高度固定,宽做拉伸,比如说:视频源是1200/600,控件是1000/700,做拉伸就是1400/700
*/
width = (int) (height * videoAspectRatio);
} else {//如果这个小于0,说明视频源宽高比小于控件宽高比
/**
*高度做拉伸
*/
height = (int) (width / videoAspectRatio);
}
break;
default://默认情况,做压缩
if (aspectDeformation > 0) {
/**
* 控件的宽度固定,高度压缩,比如说:视频源是1200/600,控件是1000/700,做压缩就是1000/500
*/
height = (int) (width / videoAspectRatio);
} else {
width = (int) (height * videoAspectRatio);
}
break;
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}