短视频系统源码开发之摄像头预览实现思路: 在xml布局中定义一个TextureView,用于预览相机采集的音视频数据
SurfaceTextureListener的onSurfaceTextureAvailable回调中打开相机 成功打开相机后,设置相机参数。比如:对焦模式,预览大小,照片保存大小等等
设置相机预览时的旋转角度,然后调用startPreview()开始预览
关闭页面,释放相机资源
短视频系统源码开发之摄像头预览关键实现: Android 相机Camera类(老版本)的内部类中有个Parameters内部类,这个类包含了操作相机的很多参数。
例如:
控制闪光灯setFlashMode 控制旋转方向setRotation 控制图片大小setPictureSize 控制预览大小setPreviewSize 在这里我们打开摄像头然后预览画面到屏幕上再到拍照保存本地,这一个过程就涉及到camera的预览尺寸和照片尺寸
相应的api就是setPictureSize和setPreviewSize。
在这里一般都会首先获取相机支持的预览尺寸,通过API
代码语言:javascript复制parameters.getSupportedPreviewSizes()
得到然后结合想要预览的width,height得到最合适的Camera.Size
然后获取相机支持的图片尺寸,通过API
代码语言:javascript复制parameters.getSupportedPictureSizes()
计算最终的CameraSize算法如下
代码语言:javascript复制 /**
* 计算最完美的Size
* @param sizes
* @param expectWidth
* @param expectHeight
* @return
*/
private static Camera.Size calculatePerfectSize(List<Camera.Size> sizes, int expectWidth,
int expectHeight, CalculateType calculateType) {
sortList(sizes); // 根据宽度进行排序
// 根据当前期望的宽高判定
List<Camera.Size> bigEnough = new ArrayList<>();
List<Camera.Size> noBigEnough = new ArrayList<>();
for (Camera.Size size : sizes) {
if (size.height * expectWidth / expectHeight == size.width) {
if (size.width > expectWidth && size.height > expectHeight) {
bigEnough.add(size);
} else {
noBigEnough.add(size);
}
}
}
// 根据计算类型判断怎么如何计算尺寸
Camera.Size perfectSize = null;
switch (calculateType) {
// 直接使用最小值
case Min:
// 不大于期望值的分辨率列表有可能为空或者只有一个的情况,
// Collections.min会因越界报NoSuchElementException
if (noBigEnough.size() > 1) {
perfectSize = Collections.min(noBigEnough, new CompareAreaSize());
} else if (noBigEnough.size() == 1) {
perfectSize = noBigEnough.get(0);
}
break;
// 直接使用最大值
case Max:
// 如果bigEnough只有一个元素,使用Collections.max就会因越界报NoSuchElementException
// 因此,当只有一个元素时,直接使用该元素
if (bigEnough.size() > 1) {
perfectSize = Collections.max(bigEnough, new CompareAreaSize());
} else if (bigEnough.size() == 1) {
perfectSize = bigEnough.get(0);
}
break;
// 小一点
case Lower:
// 优先查找比期望尺寸小一点的,否则找大一点的,接受范围在0.8左右
if (noBigEnough.size() > 0) {
Camera.Size size = Collections.max(noBigEnough, new CompareAreaSize());
if (((float)size.width / expectWidth) >= 0.8
&& ((float)size.height / expectHeight) > 0.8) {
perfectSize = size;
}
} else if (bigEnough.size() > 0) {
Camera.Size size = Collections.min(bigEnough, new CompareAreaSize());
if (((float)expectWidth / size.width) >= 0.8
&& ((float)(expectHeight / size.height)) >= 0.8) {
perfectSize = size;
}
}
break;
// 大一点
case Larger:
// 优先查找比期望尺寸大一点的,否则找小一点的,接受范围在0.8左右
if (bigEnough.size() > 0) {
Camera.Size size = Collections.min(bigEnough, new CompareAreaSize());
if (((float)expectWidth / size.width) >= 0.8
&& ((float)(expectHeight / size.height)) >= 0.8) {
perfectSize = size;
}
} else if (noBigEnough.size() > 0) {
Camera.Size size = Collections.max(noBigEnough, new CompareAreaSize());
if (((float)size.width / expectWidth) >= 0.8
&& ((float)size.height / expectHeight) > 0.8) {
perfectSize = size;
}
}
break;
}
// 如果经过前面的步骤没找到合适的尺寸,则计算最接近expectWidth * expectHeight的值
if (perfectSize == null) {
Camera.Size result = sizes.get(0);
boolean widthOrHeight = false; // 判断存在宽或高相等的Size
// 辗转计算宽高最接近的值
for (Camera.Size size : sizes) {
// 如果宽高相等,则直接返回
if (size.width == expectWidth && size.height == expectHeight
&& ((float) size.height / (float) size.width) == CameraParam.getInstance().currentRatio) {
result = size;
break;
}
// 仅仅是宽度相等,计算高度最接近的size
if (size.width == expectWidth) {
widthOrHeight = true;
if (Math.abs(result.height - expectHeight) > Math.abs(size.height - expectHeight)
&& ((float) size.height / (float) size.width) == CameraParam.getInstance().currentRatio) {
result = size;
break;
}
}
// 高度相等,则计算宽度最接近的Size
else if (size.height == expectHeight) {
widthOrHeight = true;
if (Math.abs(result.width - expectWidth) > Math.abs(size.width - expectWidth)
&& ((float) size.height / (float) size.width) == CameraParam.getInstance().currentRatio) {
result = size;
break;
}
}
// 如果之前的查找不存在宽或高相等的情况,则计算宽度和高度都最接近的期望值的Size
else if (!widthOrHeight) {
if (Math.abs(result.width - expectWidth) > Math.abs(size.width - expectWidth)
&& Math.abs(result.height - expectHeight) > Math.abs(size.height - expectHeight)
&& ((float) size.height / (float) size.width) == CameraParam.getInstance().currentRatio) {
result = size;
}
}
}
perfectSize = result;
}
Log.d(TAG,"expectSize=" expectWidth "x" expectHeight " size=" perfectSize.width "x" perfectSize.height);
return perfectSize;
}
最终还要确定旋转角度防止图像旋转,反转
获取旋转角度的方法是
代码语言:javascript复制 /**
* 设置预览角度,setDisplayOrientation本身只能改变预览的角度
* previewFrameCallback以及拍摄出来的照片是不会发生改变的,拍摄出来的照片角度依旧不正常的
* 拍摄的照片需要自行处理
* @param activity
*/
private int calculateCameraPreviewOrientation(Activity activity) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(CameraParam.getInstance().cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation degrees) % 360;
result = (360 - result) % 360;
} else {
result = (info.orientation - degrees 360) % 360;
}
CameraParam.getInstance().orientation = result;
return result;
}
短视频系统源码开发之摄像头预览相关工作就完成了,在这里总结几点:
1.查看相机设置的previewSize和显示控件的大小比例是否一致
2.如果是拍照变形查看pictureSize和自己设置的图片宽高一致或者比例一致
3.查看是否是预览角度引起的
4.如果是自己通过OpengL渲染了图片然后显示到view上,有没有做图形变换,(涉及纹理坐标和顶点坐标的问题)