Android下使用camera2和Surfaceview预览图像并取得YUV420p数据回调
16lz
2021-01-23
Android 5.0(21)之后,android.hardware.Camera被废弃(下面称为Camera1),还有一个android.graphics.Camera,这个android.graphics.Camera不是用来照相的,是用来处理图像的,可以做出3D的图像效果之类的,之前的Camera1则由android.hardware.Camera2来代替。
Camera2支持RAW输出,可以调节曝光,对焦模式,快门等,功能比原先Camera强大。
由于ImageFormat只有YUV420_888即YUV420系列,这里需要将其做一个转换,转成所需要的YUV420p:yyyyyyyyuuuuvvvv格式,参考文章:android camera2 拿到的yuv420数据到底是什么样的?
1、MainActivity.java文件:
package com.example.tongjiangsong.camera2base;import android.Manifest;import android.content.Context;import android.content.pm.PackageManager;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.ImageFormat;import android.hardware.camera2.CameraAccessException;import android.hardware.camera2.CameraCaptureSession;import android.hardware.camera2.CameraCharacteristics;import android.hardware.camera2.CameraDevice;import android.hardware.camera2.CameraManager;import android.hardware.camera2.CaptureRequest;import android.media.Image;import android.media.ImageReader;import android.os.Build;import android.os.Environment;import android.os.Handler;import android.os.HandlerThread;import android.support.annotation.RequiresApi;import android.support.v4.app.ActivityCompat;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.util.SparseIntArray;import android.view.Surface;import android.view.SurfaceHolder;import android.view.SurfaceView;import android.view.View;import android.widget.Button;import android.widget.ImageView;import android.widget.Toast;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.util.Arrays;public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); private static final String TAG = "mainactivity" ; ///为了使照片竖直显示 static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } private SurfaceView mSurfaceView; private SurfaceHolder mSurfaceHolder; private ImageView iv_show; private CameraManager mCameraManager;//摄像头管理器 private Handler childHandler, mainHandler; private String mCameraID;//摄像头Id 0 为后 1 为前 private ImageReader mImageReaderJPG; private ImageReader mImageReaderPreview; private CameraCaptureSession mCameraCaptureSession; private CameraDevice mCameraDevice; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button capture_btn = (Button)findViewById(R.id.capture_btn); capture_btn.setOnClickListener(this); initVIew(); } /** * 初始化 */ private void initVIew() { iv_show = (ImageView) findViewById(R.id.image_preview); //mSurfaceView mSurfaceView = (SurfaceView) findViewById(R.id.surface_view); mSurfaceView.setOnClickListener(this); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.setKeepScreenOn(true); // mSurfaceView添加回调 mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { //SurfaceView创建 // 初始化Camera initCamera2(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView销毁 // 释放Camera资源 if (null != mCameraDevice) { mCameraDevice.close(); MainActivity.this.mCameraDevice = null; } } }); } /** * 初始化Camera2 */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void initCamera2() { HandlerThread handlerThread = new HandlerThread("Camera2"); handlerThread.start(); childHandler = new Handler(handlerThread.getLooper()); mainHandler = new Handler(getMainLooper()); mCameraID = "" + CameraCharacteristics.LENS_FACING_FRONT;//后摄像头 mImageReaderJPG = ImageReader.newInstance(1080, 1920, ImageFormat.JPEG,1); mImageReaderJPG.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在这里处理拍照得到的临时照片 例如,写入本地 @Override public void onImageAvailable(ImageReader reader) { //mCameraDevice.close(); //mSurfaceView.setVisibility(View.GONE); //先验证手机是否有sdcard String status = Environment.getExternalStorageState(); if (!status.equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(getApplicationContext(), "你的sd卡不可用。", Toast.LENGTH_SHORT).show(); return; } // 获取捕获的照片数据 Image image = reader.acquireNextImage(); ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); //手机拍照都是存到这个路径 String filePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/"; String picturePath = System.currentTimeMillis() + ".jpg"; File file = new File(filePath, picturePath); try { //存到本地相册 FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(data); fileOutputStream.close(); iv_show.setVisibility(View.VISIBLE); //显示图片 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); iv_show.setImageBitmap(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { image.close(); } } }, mainHandler); mImageReaderPreview = ImageReader.newInstance(1080, 1920, ImageFormat.YUV_420_888,1); mImageReaderPreview.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在这里处理拍照得到的临时照片 例如,写入本地 @Override public void onImageAvailable(ImageReader reader) { // 获取捕获的照片数据 Image image = reader.acquireNextImage(); Log.i(TAG,"image format: " +image.getFormat()); // 从image里获取三个plane Image.Plane[] planes = image.getPlanes(); for (int i = 0; i < planes.length; i++) { ByteBuffer iBuffer = planes[i].getBuffer(); int iSize = iBuffer.remaining(); Log.i(TAG, "pixelStride " + planes[i].getPixelStride()); Log.i(TAG, "rowStride " + planes[i].getRowStride()); Log.i(TAG, "width " + image.getWidth()); Log.i(TAG, "height " + image.getHeight()); Log.i(TAG, "Finished reading data from plane " + i); } int n_image_size = image.getWidth()*image.getHeight()*3/2; final byte[] yuv420pbuf = new byte[n_image_size]; System.arraycopy(ImageUtil.getBytesFromImageAsType(image, 0), 0, yuv420pbuf, 0, n_image_size); image.close(); } }, null); //获取摄像头管理 mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { return; } //打开摄像头 mCameraManager.openCamera(mCameraID, stateCallback, mainHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 摄像头创建监听 */ private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) {//打开摄像头 mCameraDevice = camera; //开启预览 takePreview(); } @Override public void onDisconnected(CameraDevice camera) {//关闭摄像头 if (null != mCameraDevice) { mCameraDevice.close(); MainActivity.this.mCameraDevice = null; } } @Override public void onError(CameraDevice camera, int error) {//发生错误 Toast.makeText(MainActivity.this, "摄像头开启失败", Toast.LENGTH_SHORT).show(); } }; /** * 开始预览 */ private void takePreview() { try { // 创建预览需要的CaptureRequest.Builder final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 将SurfaceView的surface作为CaptureRequest.Builder的目标 previewRequestBuilder.addTarget(mSurfaceHolder.getSurface()); previewRequestBuilder.addTarget(mImageReaderPreview.getSurface()); // 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求 mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReaderPreview.getSurface()), new CameraCaptureSession.StateCallback() // ③ { @Override public void onConfigured(CameraCaptureSession cameraCaptureSession) { if (null == mCameraDevice) return; // 当摄像头已经准备好时,开始显示预览 mCameraCaptureSession = cameraCaptureSession; try { // 自动对焦 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 打开闪光灯 previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // 显示预览 CaptureRequest previewRequest = previewRequestBuilder.build(); mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show(); } }, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 点击事件 */ @Override public void onClick(View v) { switch (v.getId()) { case R.id.capture_btn: takePicture(); Toast.makeText(MainActivity.this, "拍照", Toast.LENGTH_SHORT).show(); break; } } /** * 拍照 */ private void takePicture() { if (mCameraDevice == null) return; // 创建拍照需要的CaptureRequest.Builder final CaptureRequest.Builder captureRequestBuilder; try { captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); // 将imageReader的surface作为CaptureRequest.Builder的目标 captureRequestBuilder.addTarget(mImageReaderJPG.getSurface()); // 自动对焦 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 自动曝光 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // 获取手机方向 int rotation = getWindowManager().getDefaultDisplay().getRotation(); // 根据设备方向计算设置照片的方向 captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)); //拍照 CaptureRequest mCaptureRequest = captureRequestBuilder.build(); mCameraCaptureSession.capture(mCaptureRequest, null, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }}
2、ImageUtil.java文件下
package com.example.tongjiangsong.camera2base;import android.graphics.ImageFormat;import android.media.Image;import android.util.Log;import java.nio.ByteBuffer;/** * yuv420p: yyyyyyyyuuvv * yuv420sp: yyyyyyyyuvuv * nv21: yyyyyyyyvuvu */public class ImageUtil { public static final int YUV420P = 0; public static final int YUV420SP = 1; public static final int NV21 = 2; private static final String TAG = "ImageUtil"; /*** * 此方法内注释以640*480为例 * 未考虑CropRect的 */ public static byte[] getBytesFromImageAsType(Image image, int type) { try { //获取源数据,如果是YUV格式的数据planes.length = 3 //plane[i]里面的实际数据可能存在byte[].length <= capacity (缓冲区总大小) final Image.Plane[] planes = image.getPlanes(); //数据有效宽度,一般的,图片width <= rowStride,这也是导致byte[].length <= capacity的原因 // 所以我们只取width部分 int width = image.getWidth(); int height = image.getHeight(); //此处用来装填最终的YUV数据,需要1.5倍的图片大小,因为Y U V 比例为 4:1:1 byte[] yuvBytes = new byte[width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8]; //目标数组的装填到的位置 int dstIndex = 0; //临时存储uv数据的 byte uBytes[] = new byte[width * height / 4]; byte vBytes[] = new byte[width * height / 4]; int uIndex = 0; int vIndex = 0; int pixelsStride, rowStride; for (int i = 0; i < planes.length; i++) { pixelsStride = planes[i].getPixelStride(); rowStride = planes[i].getRowStride(); ByteBuffer buffer = planes[i].getBuffer(); //如果pixelsStride==2,一般的Y的buffer长度=640*480,UV的长度=640*480/2-1 //源数据的索引,y的数据是byte中连续的,u的数据是v向左移以为生成的,两者都是偶数位为有效数据 byte[] bytes = new byte[buffer.capacity()]; buffer.get(bytes); int srcIndex = 0; if (i == 0) { //直接取出来所有Y的有效区域,也可以存储成一个临时的bytes,到下一步再copy for (int j = 0; j < height; j++) { System.arraycopy(bytes, srcIndex, yuvBytes, dstIndex, width); srcIndex += rowStride; dstIndex += width; } } else if (i == 1) { //根据pixelsStride取相应的数据 for (int j = 0; j < height / 2; j++) { for (int k = 0; k < width / 2; k++) { uBytes[uIndex++] = bytes[srcIndex]; srcIndex += pixelsStride; } if (pixelsStride == 2) { srcIndex += rowStride - width; } else if (pixelsStride == 1) { srcIndex += rowStride - width / 2; } } } else if (i == 2) { //根据pixelsStride取相应的数据 for (int j = 0; j < height / 2; j++) { for (int k = 0; k < width / 2; k++) { vBytes[vIndex++] = bytes[srcIndex]; srcIndex += pixelsStride; } if (pixelsStride == 2) { srcIndex += rowStride - width; } else if (pixelsStride == 1) { srcIndex += rowStride - width / 2; } } } } image.close(); //根据要求的结果类型进行填充 switch (type) { case YUV420P: System.arraycopy(uBytes, 0, yuvBytes, dstIndex, uBytes.length); System.arraycopy(vBytes, 0, yuvBytes, dstIndex + uBytes.length, vBytes.length); break; case YUV420SP: for (int i = 0; i < vBytes.length; i++) { yuvBytes[dstIndex++] = uBytes[i]; yuvBytes[dstIndex++] = vBytes[i]; } break; case NV21: for (int i = 0; i < vBytes.length; i++) { yuvBytes[dstIndex++] = vBytes[i]; yuvBytes[dstIndex++] = uBytes[i]; } break; } return yuvBytes; } catch (final Exception e) { if (image != null) { image.close(); } Log.i(TAG, e.toString()); } return null; }}
3、activity_main.xml文件下
<?xml version="1.0" encoding="utf-8"?>
3、设置权限:
更多相关文章
- android开发数据存储方式
- Android 获取摄像头像素,个数
- 通过Android 客户端上传数据到服务器
- 通过adb工具查看android sqlite3数据库
- Android实习生 —— 网络请求及数据解析
- Android P的Socket通信实现之传输图片数据
- Android数据存储的方法