Camera1图像预览及保存图片

一、概述

  使用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

 

posted on 2023-01-06 11:42  飘杨......  阅读(363)  评论(0编辑  收藏  举报