一、概述
使用Camera1实现相机预览,并可以保存预览截屏,此处测试的是后置摄像头,旋转90°
二、代码示例
1.自定义SurfaceView类
/**
* Camera1预览封装
*/
class Camera1PreviewSurfaceView(context: Context?, attrs: AttributeSet?) : SurfaceView(
context,
attrs
), SurfaceHolder.Callback,
Camera.PreviewCallback {
private var camera: Camera? = null//相机
private var cameraSize: Camera.Size? = null//相机预览尺寸
private var buffer: ByteArray? = null//相机预览的缓存数据
private var isCapture: Boolean = false//是否拍照
init {
holder.addCallback(this)
}
/**
* 开始预览
*/
private fun startPreview() {
//打开照相机后置摄像头
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK)
//获取摄像机参数
var parameters = camera?.parameters
//获取预览尺寸
cameraSize = parameters?.previewSize
try {
//设置预览的view
camera?.setPreviewDisplay(holder)
//因为默认是横屏,旋转90使其变为竖屏
camera?.setDisplayOrientation(90)
//设置缓存buffer
buffer = ByteArray(cameraSize?.width!! * cameraSize?.height!! * 3 / 2)
camera?.addCallbackBuffer(buffer)
//设置预览数据回调
camera?.setPreviewCallbackWithBuffer(this)
//开始预览
camera?.startPreview()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* surface创建成功回调
*/
override fun surfaceCreated(holder: SurfaceHolder) {
startPreview()
}
/**
* SurfaceView发生改变回调
*/
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
/**
* SurfaceView销毁时的回调
*/
override fun surfaceDestroyed(holder: SurfaceHolder) {
}
/**
* Camera1相机预览数据回调
*/
override fun onPreviewFrame(data: ByteArray?, camera: Camera?) {
synchronized(Camera1PreviewSurfaceView::class.java) {
if (isCapture) {
var bf = CameraUtil.rotationAngle(cameraSize, buffer, data)
isCapture = false
CameraUtil.capture(cameraSize?.width!!, cameraSize?.height!!, bf)
}
}
//下面这段代码的byte不能直接放参数中的data,不然会造成对角线花屏
camera?.addCallbackBuffer(ByteArray(cameraSize?.width!! * cameraSize?.height!! * 3 / 2))
}
/**
* 开始拍照保存(其实这里仅仅是存储到sdcard中而已)
*/
fun startCapture() {
isCapture = true
}
}
2.预览类:
class Camera1PreviewActivity : BaseActivity() {
override fun videoPathCallback(vidoPath: String?) {
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera1_preview)
btnSaveImage.setOnClickListener {
//保存图片
cameraSurfaceView.startCapture()
}
}
}
3.旋转及保存图片的工具类
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Camera相机工具
*/
public class CameraUtil {
/**
* 旋转YUV数据
*
* @param size
* @param buffer
* @param data
*/
public static byte[] rotationAngle(Camera.Size size, byte[] buffer, byte[] data) {
int width = size.width;
int height = size.height;
// 旋转y
int y_len = width * height;
//u y/4 v y/4
int uvHeight = height / 2;
int k = 0;
for (int j = 0; j < width; j++) {
for (int i = height - 1; i >= 0; i--) {
// 存值 k++ 0 取值 width * i + j
buffer[k++] = data[width * i + j];
}
}
// 旋转uv
for (int j = 0; j < width; j += 2) {
for (int i = uvHeight - 1; i >= 0; i--) {
buffer[k++] = data[y_len + width * i + j];
buffer[k++] = data[y_len + width * i + j + 1];
}
}
return buffer;
}
private static int index = 0;
/**
* 保存一帧图片
*/
public static void capture(int width, int height, byte[] temp) {
//保存一张照片
String fileName = "IMG_" + String.valueOf(index++) + ".jpg"; //jpeg文件名定义
Log.e("图片保存路径:",fileName);
File sdRoot = Environment.getExternalStorageDirectory(); //系统路径
File pictureFile = new File(sdRoot, fileName);
if (!pictureFile.exists()) {
try {
pictureFile.createNewFile();
FileOutputStream filecon = new FileOutputStream(pictureFile);
//ImageFormat.NV21 and ImageFormat.YUY2 for now
YuvImage image = new YuvImage(temp, ImageFormat.NV21, height, width, null); //将NV21 data保存成YuvImage
//图像压缩
image.compressToJpeg(
new Rect(0, 0, image.getWidth(), image.getHeight()),
100, filecon); // 将NV21格式图片,以质量70压缩成Jpeg,并得到JPEG数据流
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static byte[] rotateYUVDegree270AndMirror(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate and mirror the Y luma
int i = 0;
int maxY = 0;
for (int x = imageWidth - 1; x >= 0; x--) {
maxY = imageWidth * (imageHeight - 1) + x * 2;
for (int y = 0; y < imageHeight; y++) {
yuv[i] = data[maxY - (y * imageWidth + x)];
i++;
}
}
// Rotate and mirror the U and V color components
int uvSize = imageWidth * imageHeight;
i = uvSize;
int maxUV = 0;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
maxUV = imageWidth * (imageHeight / 2 - 1) + x * 2 + uvSize;
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[maxUV - 2 - (y * imageWidth + x - 1)];
i++;
yuv[i] = data[maxUV - (y * imageWidth + x)];
i++;
}
}
return yuv;
}
}
三、遇到的问题
1.保存下来的图片对角线花屏
原因是因为在fun onPreviewFrame(data: ByteArray?, camera: Camera?)这个回调方法中的data参数即被回调修改了,也被旋转角度修改了
解决办法:将addCallbackBuffer中的参数置空就行
override fun onPreviewFrame(data: ByteArray?, camera: Camera?) {
synchronized(Camera1PreviewSurfaceView::class.java) {
if (isCapture) {
var bf = CameraUtil.rotationAngle(cameraSize, buffer, data)
isCapture = false
CameraUtil.capture(cameraSize?.width!!, cameraSize?.height!!, bf)
}
}
//下面这段代码的byte不能直接放参数中的data,不然会造成对角线花屏
camera?.addCallbackBuffer(ByteArray(cameraSize?.width!! * cameraSize?.height!! * 3 / 2))
}
对角线花屏示例图:
2.为什么要旋转角度90°
此处测试用的是后置摄像头。拍摄的原始像素图像是横向的,我们正常人观看是竖向的,所以要顺时针旋转90°,然后才是我们最终想要的图片。如果是前置摄像头需要旋转270°,且需要注意镜像问题
3.android的摄像头默认拍摄出来的数据是nv21,即yyyyyyyy vu vu 。I420是:yyyyyyyy uu vv。nv21要转i420要经过转换,但是他们的数据存大小都是一样的,都是4个y对应一个uv