package com.whyc.widget; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; 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.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; import android.media.MediaMuxer; import android.media.MediaScannerConnection; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.Surface; import android.view.TextureView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.whyc.util.BitmapUtil; import com.whyc.util.YUVUtil; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static android.os.Environment.DIRECTORY_DOCUMENTS; /** * 这个采用ImageReader视频帧来获取视频和处理图片 * */ public class Camera2TextureView4 extends TextureView { private Context mContext; private CameraDevice mCameraDevice; private CaptureRequest.Builder previewCaptureBuilder = null; private CaptureRequest.Builder videoCaptureBuilder = null; private CameraCaptureSession mCaptureSession; private ImageReader mImageReader; private Surface previewSurface; private MediaCodec mMediaCodec; private MediaMuxer mMediaMuxer; private int videoWidth = 1920; private int videoHeight = 1080; /** 线程处理器*/ private HandlerThread videoThread; private HandlerThread previewThread; private HandlerThread imageThread; private HandlerThread cameraThread; private Handler videoThreadHandler; private Handler previewThreadHandler; private Handler imageThreadHandler; private Handler cameraThreadHandler; //视频和图片的文件夹路径 File fileDir = new File(Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getAbsolutePath() + "/yc_test/"); String recorderPath = fileDir.getAbsolutePath() + File.separator + System.currentTimeMillis() + ".mp4"; //主页面的视图 private LinearLayout llUpText; private TextView tvDevice; ExecutorService imageExecutor = Executors.newSingleThreadExecutor(); public Camera2TextureView4(Context context) { super(context,null); } public Camera2TextureView4(Context context, AttributeSet attrs) throws IOException { super(context, attrs); mContext = context; } public void init(LinearLayout llUpText, TextView tvDevice) { this.llUpText = llUpText; this.tvDevice = tvDevice; setSurfaceTextureListener(mSurfaceTextureListener); } private SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { initCamera2(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }; @SuppressLint("MissingPermission") private void initCamera2() { //获取权限 // ActivityCompat.requestPermissions(Camera2TextureViewActivity.this, permissionArray, 1); CameraManager cm = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); try { String cameraId = cm.getCameraIdList()[0]; //初始化预览线程 initPreviewThreadHandler(); cameraThread = new HandlerThread("Camera2Video"); cameraThread.start(); cameraThreadHandler = new Handler(cameraThread.getLooper()); // cm.openCamera(cameraId,mDeviceStateCallback,previewThreadHandler); // cm.openCamera(cameraId,mDeviceStateCallback,cameraThreadHandler); cm.openCamera(cameraId,mDeviceStateCallback,null); }catch (Exception e){ e.printStackTrace(); } } private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice cameraDevice) { //预览和图像监测 mCameraDevice = cameraDevice; SurfaceTexture surfaceTexture = getSurfaceTexture(); surfaceTexture.setDefaultBufferSize(1920, 1080); previewSurface = new Surface(surfaceTexture); //图像监测 mImageReader = ImageReader.newInstance(videoWidth, videoHeight, ImageFormat.YUV_420_888, 2); try { createPreviewSession(); }catch (Exception e){ e.printStackTrace(); } } @Override public void onDisconnected(@NonNull CameraDevice camera) { } @Override public void onError(@NonNull CameraDevice camera, int error) { } }; private void createPreviewSession() { try { mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { try { mCaptureSession = session; previewCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewCaptureBuilder.addTarget(previewSurface); /*设置预览的界面*/ //设置自动对焦模式 previewCaptureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); // 设置自动曝光模式 previewCaptureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, 90); startPreview(); }catch (Exception e){ e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { } }, previewThreadHandler); }catch (Exception e){ e.printStackTrace(); } } private void startPreview() { //启动预览 try { if(previewThreadHandler == null){ initPreviewThreadHandler(); } mCaptureSession.setRepeatingRequest(previewCaptureBuilder.build(), null, previewThreadHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /*MediaRecorder mediaRecorder = null; private void prepareVideoRecorder(String recorderPath,Surface inputSurface) { MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(RESULT_OK, null); VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("virtualDisplay", 1920, 1080, 60, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, null, null); mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mediaRecorder.setVideoSize(1920, 1080); mediaRecorder.setOutputFile(recorderPath); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mediaRecorder.setVideoEncodingBitRate(512 * 1000); mediaRecorder.setInputSurface(inputSurface); mediaRecorder.setVideoFrameRate(30); // 可以根据需要调整帧率 try { mediaRecorder.prepare(); } catch (IOException e) { e.printStackTrace(); } }*/ /**视频录制*/ public void createRecorderSession() throws IOException { try { //关闭预览 mCaptureSession.stopRepeating(); } catch (CameraAccessException e) { e.printStackTrace(); } //关闭预览线程 stopPreviewThreadHandler(); //初始化录制线程 initThreadHandlerForRecording(); mImageReader.setOnImageAvailableListener(mImageReaderListener, imageThreadHandler); String recorderPath = fileDir.getAbsolutePath()+File.separator + System.currentTimeMillis() + ".mp4"; try { mMediaCodec = createAndConfigureEncoder(videoWidth, videoHeight); mMediaMuxer = new MediaMuxer(recorderPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); // mMediaMuxer.setOrientationHint(90); videoCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); videoCaptureBuilder.addTarget(previewSurface); videoCaptureBuilder.addTarget(mImageReader.getSurface()); mCaptureSession.setRepeatingRequest(videoCaptureBuilder.build(), null, videoThreadHandler); } catch (Exception e) { e.printStackTrace(); } /*try { mCameraDevice.createCaptureSession(Arrays.asList(previewSurface,mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { mCaptureSession = session; try { //camera2 视频录制模式 // captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE); mCaptureSession.setRepeatingRequest(captureBuilder.build(), null, mBackgroundHandler); } catch (Exception e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { } }, null); } catch (Exception e) { e.printStackTrace(); }*/ } private int mVideoTrackIndex = -1; int I420size = videoWidth*videoHeight*3/2; private ImageReader.OnImageAvailableListener mImageReaderListener = reader -> { Image image = reader.acquireNextImage(); // Image imageCopy = ImageReader.newInstance(videoWidth, videoHeight, ImageFormat.YUV_420_888, 1).acquireLatestImage(); byte[] nv12 = new byte[I420size]; // byte[] nv21 = new byte[I420size]; YUVUtil.YUVToNV21_NV12(image, nv12, videoWidth, videoHeight, "NV12"); long now = image.getTimestamp(); image.close(); //图片截图 imageExecutor.execute(() -> { SharedPreferences camera2Time = mContext.getSharedPreferences("camera2Time", Context.MODE_PRIVATE); long lastTime = camera2Time.getLong("time", 0); long secondsGap = (now - lastTime) / 1000000000; if (lastTime == 0 || secondsGap > 2) { camera2Time.edit().putLong("time", now).apply(); //需要将nv12转化为nv21后进行图片存储 byte[] nv21 = convertNV12toNV21(nv12); Bitmap bitmap = BitmapUtil.nv21ToBitmap(nv21, videoWidth, videoHeight); BitmapUtil.saveBitmapToFile(bitmap); } }); // 提交数据给MediaCodec try { int inputBufferIndex = mMediaCodec.dequeueInputBuffer(0); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = mMediaCodec.getInputBuffer(inputBufferIndex); inputBuffer.clear(); inputBuffer.put(nv12); long nanoTime = System.nanoTime(); long presentationTimeUs = nanoTime / 1000; // 将纳秒转换为微秒 mMediaCodec.queueInputBuffer(inputBufferIndex, 0, nv12.length, presentationTimeUs, 0); } // 获取编码后的数据汇入H264 封装成mp4 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0); if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { mVideoTrackIndex = mMediaMuxer.addTrack(mMediaCodec.getOutputFormat()); if (mVideoTrackIndex != -1) { mMediaMuxer.start(); } } while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(outputBufferIndex); if (mVideoTrackIndex != -1) { mMediaMuxer.writeSampleData(mVideoTrackIndex, outputBuffer, bufferInfo); mMediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0); if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { break; } } } } catch (Exception e) { //中断错误,忽略 } }; public byte[] convertNV12toNV21(byte[] nv12) { int length = nv12.length; byte[] nv21 = new byte[length]; // Copy Y component System.arraycopy(nv12, 0, nv21, 0, length - (length / 4)); // Copy and swap U/V components for (int i = length - (length / 4), j = length - (length / 4); i < length; i += 2, j += 2) { nv21[j] = nv12[i + 1]; nv21[j + 1] = nv12[i]; } return nv21; } public void stopRecording() { try { //停止录制视频,停止画面图片监听 mMediaMuxer.stop(); mMediaCodec.stop(); //停止录像Repeating mCaptureSession.stopRepeating(); //线程监控停止 mImageReader.setOnImageAvailableListener(null, null); //停止录制视频线程 stopThreadHandlerForRecording(); //这里只是演示test // llUpText.setVisibility(View.GONE); startPreview(); } catch (IllegalStateException | CameraAccessException e) { e.printStackTrace(); } finally { mMediaCodec.release(); mMediaMuxer.release(); } // 关闭相机预览会话 // if (captureSession != null) { // captureSession.close(); // captureSession = null; // } //图库更新 // addToGallery(recorderPath); } private void addToGallery(String videoFilePath) { // 发送广播通知图库更新 MediaScannerConnection.scanFile(mContext, new String[]{videoFilePath}, null, (path, uri) -> { // 添加到相册成功的回调 Toast.makeText(mContext, "视频已保存至相册", Toast.LENGTH_SHORT).show(); }); } private MediaCodec createAndConfigureEncoder(int width, int height) { MediaCodecInfo codecInfo = selectCodec("video/avc"); MediaCodec codec = null; try { codec = MediaCodec.createByCodecName(codecInfo.getName()); }catch (Exception e){ e.printStackTrace(); } MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); // format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); format.setInteger(MediaFormat.KEY_BIT_RATE, width*height*4); format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); return codec; } private MediaCodecInfo selectCodec(String mimeType) { int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); if (!codecInfo.isEncoder()) { continue; } String[] types = codecInfo.getSupportedTypes(); for (String type : types) { if (type.equalsIgnoreCase(mimeType)) { return codecInfo; } } } return null; } private void initPreviewThreadHandler(){ //预览的处理线程 previewThread = new HandlerThread("camera2Preview"); previewThread.start(); previewThreadHandler = new Handler(previewThread.getLooper()); } private void stopPreviewThreadHandler(){ //预览的处理线程 /* try { previewThread.quitSafely(); previewThread.join(); } catch (InterruptedException e) { e.printStackTrace(); }finally { previewThread = null; previewThreadHandler = null; }*/ } private void initThreadHandlerForRecording() { //录像的处理线程 videoThread = new HandlerThread("Camera2Video"); videoThread.start(); videoThreadHandler = new Handler(videoThread.getLooper()); //图像监测的处理线程 imageThread = new HandlerThread("camera2Image"); imageThread.start(); imageThreadHandler = new Handler(imageThread.getLooper()); } private void stopThreadHandlerForRecording() { try { videoThread.quitSafely(); videoThread.join(); videoThread = null; videoThreadHandler = null; imageThread.quitSafely(); imageThread.join(); imageThread = null; imageThreadHandler = null; } catch (InterruptedException e) { e.printStackTrace(); } } }