本文共 22420 字,大约阅读时间需要 74 分钟。
1、预览
2、角度旋转
3、实时帧数据获取ImageReader的参数设置
4、实时帧数据格式转换
package com.neatech.stface.tools;import android.Manifest;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.graphics.ImageFormat;import android.graphics.Matrix;import android.graphics.RectF;import android.graphics.SurfaceTexture;import android.hardware.Camera;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.hardware.camera2.params.StreamConfigurationMap;import android.media.ExifInterface;import android.media.Image;import android.media.ImageReader;import android.os.Handler;import android.os.HandlerThread;import android.os.Message;import android.support.annotation.NonNull;import android.support.v4.app.ActivityCompat;import android.util.Log;import android.util.Size;import android.view.Surface;import android.view.TextureView;import android.widget.RelativeLayout;import com.neatech.stface.R;import com.neatech.stface.broadcast.CameraErrorReceiver;import com.neatech.stface.view.FaceDrawerView;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.List;/** * 功能:视频预览 * 作者:陈晗 * 时间:2019-3-7 16:47 */public class CameraPreviewHelper { private String Tag = "CameraPreviewHelper"; private Context mContext; private TextureView mTextureView; private HandlerThread mHandlerThread; private Handler childHandler; private Handler mHandler; private Activity activity; //private TrackThread mTrackThread; private LivenessTackThread mLiveTackThread; private TrackAndSearchThread trackAndSearchThread; private FaceDrawerView mFaceDrawerView; private String mCameraId; private Size mPreviewSize; private CameraManager mCameraManager;//摄像头管理器 private CameraDevice mCameraDevice; private SurfaceTexture mSurfaceTexture = null; private CaptureRequest.Builder mCaptureRequestBuilder; private CaptureRequest mCaptureRequest; private CameraCaptureSession mCameraCaptureSession; private boolean isConnect = false; private ImageReader mImageReader; private int capacity = -1; private static byte[] yv12bytes; private static byte[] imgData; //1280*720 img private int imgFormat; private int imgHeight; private int ingWidth; public final static int picMode = 640; //720p ,1080p 640p public static final int CAPTURE_IMG_PREPARED = 1; public static final int TRY_TO_CAPTURE_IMG = 2; // private static boolean isLiveness = false; public static int preview_width; //preview public static int preview_higth; public static int surface_width; //surface public static int surface_higth; public static int delaytest = 1; public CameraPreviewHelper(Activity activity, TextureView textureView) { mContext = activity.getBaseContext(); mTextureView = textureView; this.activity = activity; mFaceDrawerView = new FaceDrawerView(mContext); ((RelativeLayout) activity.findViewById(R.id.rootview_relativelayout_camera_verity)).addView(mFaceDrawerView); mHandlerThread = new HandlerThread(Tag); mHandlerThread.start(); childHandler = new Handler(mHandlerThread.getLooper()); mHandler = new Handler(activity.getMainLooper(), mHandlerCallback); } /** * open camera and start preview * * @return */ public boolean Connect(boolean isliveness) { if (mContext == null || mTextureView == null) { Log.e("asda", "Connect"); return false; } // isLiveness = false;// isliveness; mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); // mSurfaceTexture = mTextureView.getSurfaceTexture(); if (mCameraDevice != null) mCameraDevice.close(); isConnect = true;// if (isLiveness) {// mLiveTackThread = new LivenessTackThread(activity, mFaceDrawerView);// mLiveTackThread.start();// } else {// mTrackThread = new TrackThread(activity, mFaceDrawerView, mTextName);// mTrackThread.start(); trackAndSearchThread = new TrackAndSearchThread(activity, mFaceDrawerView); trackAndSearchThread.start(); // } return true; } public void DisConnect() { mContext = null;// if (mSurfaceTexture != null) {// mSurfaceTexture.release();// Log.e(Tag, "mSurfaceTexture release");////// } activity = null;// if (mTrackThread != null)// mTrackThread.stopTrack(); if (trackAndSearchThread != null) trackAndSearchThread.stopTrack(); if (mTextureView != null) { mTextureView = null; } // mPreviewed=false; if (childHandler != null) { childHandler.removeCallbacksAndMessages(null); childHandler = null; } if (mHandler != null) mHandler.removeCallbacksAndMessages(null); mHandler = null; if (mHandlerThread != null) { mHandlerThread.quit(); mHandlerThread = null; } if (null != mCameraCaptureSession) { mCameraCaptureSession.close(); mCameraCaptureSession = null; } if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } if (null != mImageReader) { mImageReader.close(); mImageReader = null; } } TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // mSurfaceTexture=surface; try { if (mCameraManager == null) { //获取摄像头管理 mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); String[] CameraIdList=mCameraManager.getCameraIdList();//获取可用相机列表 Log.e(Tag,"可用相机的个数是:"+CameraIdList.length); mCameraId = CameraIdList[0]; CameraCharacteristics cameraCharacteristics=mCameraManager.getCameraCharacteristics(mCameraId);//获取某个相机(摄像头特性) cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);//检查支持 //打开摄像头 try { if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { return; } if (mCameraManager == null) return; //镜面效果 surface_width = width; surface_higth = height; //获取StreamConfigurationMap,他是管理摄像头支持的所有输出格式和尺寸 StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); //获得最接近的尺寸大小 mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class),width,height); Log.d(Tag, "getOptimalPreviewSize: w" + mPreviewSize.getWidth() + " h" + mPreviewSize.getHeight()); if (picMode == 720) { mPreviewSize = null; mPreviewSize = new Size(1280, 720); preview_higth = mPreviewSize.getWidth(); preview_width = mPreviewSize.getHeight(); } else { preview_higth = mPreviewSize.getWidth(); preview_width = mPreviewSize.getHeight(); } //如果视频显示需要角度旋转 用该函数进行角度转正 configureTransform(surface_width, surface_higth); /** * 实时帧数据获取类 * 由于获取实时帧所以选用YV12或者YUV_420_888两个格式,暂时不采用JPEG格式 * 在真机显示的过程中,不同的数据格式所设置的width和height需要注意,否侧视频会很卡顿 * YV12:width 720, height 960 * YUV_420_888:width 720, height 960 * JPEG:获取帧数据不能用 ImageFormat.JPEG 格式,否则你会发现预览非常卡的,因为渲染 JPEG 数据量过大,导致掉帧,所以预览帧请使用其他编码格式 */ mImageReader = ImageReader.newInstance(720, 960, ImageFormat.YV12, 10);//YUV_420_888 mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mHandler); //打开摄像头 mCameraManager.openCamera(mCameraId, stateCallback, mHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); if (activity != null) activity.sendBroadcast(new Intent(CameraErrorReceiver.ACTION_CAMERA_OPEN_ERROR)); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; Log.d(Tag, "onSurfaceTextureDestroyed: stopPreview"); // mPreviewed = false; } Log.e(Tag, "onSurfaceTextureDestroyed"); return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }; /** * 摄像头创建监听 */ private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) {//打开摄像头 mCameraDevice = camera; if (mCameraDevice == null) return; SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture(); mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight()); Surface previewSurface = new Surface(mSurfaceTexture); try { mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mCaptureRequestBuilder.addTarget(previewSurface); //设置实时帧数据接收 mCaptureRequestBuilder.addTarget(mImageReader.getSurface()); mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { try{ mCameraCaptureSession = session; mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); //开始预览 mCameraCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { } },childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onDisconnected(CameraDevice camera) {//关闭摄像头 if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } } @Override public void onError(CameraDevice camera, int error) {//发生错误 if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } Log.e(Tag, "打开摄像头失败"); } }; /** * 收到实时帧数据进行处理: * 一定要使用Image image = reader.acquireLatestImage();image.close();要不然会很卡顿 * 由于ImageReader不兼容NV21,所以将YV12转换为NV21 */ private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireLatestImage(); if (!trackAndSearchThread.isHandleImg) { imgFormat = ImageFormat.NV21; imgHeight = image.getHeight(); ingWidth = image.getWidth(); imgData = ImageUtil.getBytesFromImageAsType(image, ImageUtil.NV21); if (mHandler != null) mHandler.sendEmptyMessage(CAPTURE_IMG_PREPARED); } image.close(); } }; Handler.Callback mHandlerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { // Log.e(Tag,"handleMessage"); //准备图片 if (msg.what == TRY_TO_CAPTURE_IMG) { } if (msg.what == CAPTURE_IMG_PREPARED) { // mFaceDrawerView.drawFaces(facelist, mFaceDrawerView.getWidth(), 720, mContext.getResources().getColor(R.color.white)); // synchronized (imgData) { //uiHandler.sendEmptyMessage(CAPTURE_IMG_PREPARED); } //mTrackThread.handleImgData(imgData, ingWidth, imgHeight, imgFormat); trackAndSearchThread.handleImgData(imgData, ingWidth, imgHeight, imgFormat); } return false; } }; /** * 解决预览变形问题 * * @param sizeMap * @param width * @param height * @return */ //选择sizeMap中大于并且最接近width和height的size private Size getOptimalSize(Size[] sizeMap, int width, int height) { ListsizeList = new ArrayList<>(); for (Size option : sizeMap) { if (width > height) { if (option.getWidth() > width && option.getHeight() > height) { sizeList.add(option); } } else { if (option.getWidth() > height && option.getHeight() > width) { sizeList.add(option); } } } if (sizeList.size() > 0) { return Collections.min(sizeList, new Comparator () { @Override public int compare(Size lhs, Size rhs) { return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight()); } }); } return sizeMap[0]; } private Size getOptimalPreviewSize(Size []sizes, int w, int h) { final double ASPECT_TOLERANCE = 0.1; double targetRatio = (double) w / h; if (sizes == null) return null; Size optimalSize = null; double minDiff = Double.MAX_VALUE; int targetHeight = h; // Try to find an size match aspect ratio and size Size size = null; for (int i = 0; i < sizes.length; i++) { size = sizes[i]; double ratio = (double) size.getWidth() / size.getHeight(); if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.getHeight() - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.getHeight() - targetHeight); } } // Cannot find the one match the aspect ratio, ignore the requirement if (optimalSize == null) { minDiff = Double.MAX_VALUE; for (int i = 0; i < sizes.length; i++) { size = sizes[i]; if (Math.abs(size.getHeight() - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.getHeight() - targetHeight); } } } return optimalSize; } public void updateList() { if (trackAndSearchThread != null) trackAndSearchThread.isUpdateFuture = true; } private void configureTransform(int viewWidth, int viewHeight) { if (null == mTextureView || null == mPreviewSize || null == activity) { return; } int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); Matrix matrix = new Matrix(); RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); float centerX = viewRect.centerX(); float centerY = viewRect.centerY(); if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); float scale1 = Math.max( (float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth()); float scale2 = Math.min( (float)mPreviewSize.getHeight() / viewHeight, (float) mPreviewSize.getWidth() / viewWidth); matrix.postScale(scale1, scale2, centerX, centerY); matrix.postRotate(90 * rotation, centerX, centerY); } mTextureView.setTransform(matrix); }}
package com.neatech.stface.tools;import android.graphics.ImageFormat;import android.media.Image;import java.nio.ByteBuffer;/** * 功能:YV12转其他格式 * 作者:chenhan * 时间:2019-3-7 16:47 *//** * 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.YV12) / 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(); } } return null; }}
转载地址:http://psxab.baihongyu.com/