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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
2014-01-06 Java使用线程并发库模拟弹夹装弹以及发射子弹的过程