美文网首页
Android实现偷拍(视频录制)功能的一种方法

Android实现偷拍(视频录制)功能的一种方法

作者: 朝阳夕晖蜂蜜 | 来源:发表于2021-02-03 14:48 被阅读0次

    目前有这样的一个需求,实现一个偷录的功能,至少要满足定时的4min录制,不随着页面的销毁而消失,参考其它的偷拍实现都不能满足当前的需求,就自己重新做了一个满足功能需求。
    参考Toast的设计思路,修改WindowManager,在窗体中打开相机,给予Camera2,满足拍照和录制的功能,具体实现如下:

    public class CaptureCamera2Toast {
        private static final String TAG = "CaptureCamera2Toast";
        private static CaptureCamera2Toast sCaptureToast2 = null;
        private final Context mContext;
        private View mView;
        private WindowManager mWM;
        private WindowManager.LayoutParams mParams;
        private TextureView mTextureView;
        private CameraDevice mCameraDevice;
        private CameraCaptureSession mCameraCaptureSession;
        private ImageReader mImageReader;
        private HandlerThread mBackgroundThread;
        private Handler mBackgroundHandler;
        private MediaRecorder mMediaRecorder;
        private Size mPreviewSize;
        private Size mVideoSize;
        private boolean mTakingPicture;
        private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
        private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
        private static final long VIDEOTIME = 1000 * 60 * 4;//默认录制时长最大为4min
        //判断当前是否处于视频录制状态
        private boolean isRecoreding;
        //抓拍照片最大值
        private static final int PICTURE_COUNTS = 100;
        //抓拍视频最大值
        private static final int VIDEO_COUNTS = 30;
        //判断当前是否正在拍照和录制状态
        private boolean isOpen;
        /**
         * error 当前相机处于工作中
         */
        private int ERROR_ISOPEN = -3;
        /**
         * error 相机打开失败
         */
        private int ERROR_CAMERA_OPEN_FAIL = -2;
        /**
         * error 相机断开连接
         */
        private int ERROR_CAMERA_DISCONNECT = -1;
        /**
         * A {@link Semaphore} to prevent the app from exiting before closing the camera.
         */
        private Semaphore mCameraOpenCloseLock = new Semaphore(1);
        /**
         * Whether the app is recording video now
         */
        private boolean mIsRecordingVideo;
    
        static {
            DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
            DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
            DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
            DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
        }
    
        static {
            INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
            INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
            INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
            INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
        }
    
    
        private CaptureCamera2Toast(Context context) {
            this.mContext = context;
            initWindowManager(mContext);
    
        }
    
        public static CaptureCamera2Toast getInstance(Context context) {
            if (sCaptureToast2 == null) {
                sCaptureToast2 = new CaptureCamera2Toast(context);
            }
            return sCaptureToast2;
        }
    
        private void initWindowManager(Context context) {
            //加载布局:布局填充器
            LayoutInflater view = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mView = view.inflate(R.layout.toast_camera2_capture, null);
            mTextureView = mView.findViewById(R.id.textureView);
    
            //初始化窗体管理器
            if (mWM == null) {
                mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            }
            mParams = new WindowManager.LayoutParams();
    
            mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
            mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
            mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
            mParams.format = PixelFormat.TRANSLUCENT;
            mParams.gravity = Gravity.END | Gravity.TOP;
            //mParams.type = WindowManager.LayoutParams.TYPE_TOAST;//Toast默认没有触摸功能
            mParams.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;//占据窗体前端
            mParams.setTitle("Toast");
    
        }
    
        private void startBackgroundThread() {
            mBackgroundThread = new HandlerThread("CameraBackground");
            mBackgroundThread.start();
            mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
        }
    
        /**
         * Stops the background thread and its {@link Handler}.
         */
        private void stopBackgroundThread() {
            try {
                if (mBackgroundThread != null) {
                    mBackgroundThread.quitSafely();
                    //mBackgroundThread.join();
                    mBackgroundThread = null;
                    mBackgroundHandler = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void show(boolean takingPicture) {
            Log.d(TAG, "show: " + isOpen);
            this.mTakingPicture = takingPicture;
            if (isOpen) {
                Log.d(TAG, "The camera is currently in use");
                captureError(ERROR_ISOPEN);
            } else {
                startBackgroundThread();
                mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
                if (mView != null) {
                    if (mView.getParent() != null) {
                        mWM.removeView(mView);
                    }
                    mWM.addView(mView, mParams);
                    isOpen = true;
                }
            }
        }
    
    
        public void hide() {
            isOpen = false;
            Log.i(TAG, "hide: " + isOpen);
            if (mView != null && mView.getParent() != null) {
                mWM.removeView(mView);
            }
            if (null != mImageReader) {
                mImageReader.close();
                mImageReader = null;
            }
            closeCamera();
            //sCaptureToast2 = null;
            stopBackgroundThread();
        }
    
        private void closeCamera() {
            try {
                mCameraOpenCloseLock.acquire();
                closePreviewSession();
                if (null != mCameraDevice) {
                    mCameraDevice.close();
                    mCameraDevice = null;
                }
                if (null != mMediaRecorder) {
                    mMediaRecorder.release();
                    mMediaRecorder = null;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException("Interrupted while trying to lock camera closing.");
            } finally {
                mCameraOpenCloseLock.release();
            }
        }
    
        private void closePreviewSession() {
            if (mCameraCaptureSession != null) {
                mCameraCaptureSession.close();
                mCameraCaptureSession = null;
            }
        }
    
        private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
    
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                openCamera(width, height);
            }
    
            @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 boolean openCamera(int width, int height) {
            try {
                CameraManager cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
                String[] cameraIdList = cameraManager.getCameraIdList();
                if (cameraIdList.length <= 0) {
                    Log.e(TAG, "Camera not exist");
                    return false;
                } else {
                    if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                        throw new RuntimeException("Time out waiting to lock camera opening.");
                    }
                    String cameraId = cameraIdList[0];
                    CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
                    StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    if (map != null) {
                        mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
                        mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                                width, height, mVideoSize);
                    }
                    configureTransform(width, height);
                    if (isOpen) {
                        cameraManager.openCamera(cameraId, mStateCallback, mBackgroundHandler);
                    }
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                captureError(ERROR_CAMERA_OPEN_FAIL);
                hide();
                Log.e(TAG, "openCamera: " + e.toString());
                return false;
            }
        }
    
        private void captureError(int error) {
            if (mTakingPicture) {
                if (mPicturePath != null) {
                    mPicturePath.getPictureError(error);
                }
            } else {
                if (mVideoPath != null) {
                    mVideoPath.getVideoError(error);
                }
            }
        }
    
        private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
    
            @Override
            public void onOpened(CameraDevice camera) {
                Log.d(TAG, "onOpened: " + camera.getId());
                if (mTakingPicture) {
                    initTakePicture();
                } else {
                    initMediaRecored();
                }
                mCameraDevice = camera;
                startPreview();
                mCameraOpenCloseLock.release();
            }
    
            @Override
            public void onDisconnected(CameraDevice camera) {
                Log.d(TAG, "onDisconnected: " + camera.toString());
                captureError(ERROR_CAMERA_DISCONNECT);
                mCameraOpenCloseLock.release();
                camera.close();
                hide();
                mCameraDevice = null;
            }
    
            @Override
            public void onError(CameraDevice camera, int error) {
                Log.e(TAG, "onError: " + error);
                captureError(error);
                mCameraOpenCloseLock.release();
                camera.close();
                hide();
                mCameraDevice = null;
            }
        };
    
        private void initTakePicture() {
            mImageReader = ImageReader.newInstance(1024, 600, ImageFormat.JPEG, 1);
            mImageReader.setOnImageAvailableListener(reader -> {
                // 拿到拍照照片数据
                Image image = reader.acquireNextImage();
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);//由缓冲区存入字节数组
                Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                File file = new File(Objects.requireNonNull(mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)).getAbsolutePath(),
                        "picture" + System.currentTimeMillis() + ".jpeg");
                Log.d(TAG, "initTakePicture: " + file.getAbsolutePath());
                if (mPicturePath != null) {
                    mPicturePath.getPicturePath(file.getAbsolutePath());
                }
                try {
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
                    fileOutputStream.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, mBackgroundHandler);
        }
    
        private void initMediaRecored() {
            mMediaRecorder = new MediaRecorder();
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            String videoPath = Objects.requireNonNull(mContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES)).getAbsolutePath() + "/movie" + System.currentTimeMillis() + ".mp4";
            Log.d(TAG, "initMediaRecored: " + videoPath);
            if (mVideoPath != null) {
                mVideoPath.getVideoPath(videoPath);
            }
            mMediaRecorder.setOutputFile(videoPath);
            mMediaRecorder.setVideoEncodingBitRate(1024 * 1024);
            mMediaRecorder.setVideoFrameRate(25);
            mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            try {
                mMediaRecorder.prepare();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    
        //开启预览
        private void startPreview() {
            Log.i(TAG, "startPreview: ");
            try {
                closePreviewSession();
                CaptureRequest.Builder captureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                if (mTextureView.isAvailable()) {
                    SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
                    if (surfaceTexture == null) return;
                    Surface previewSurface = new Surface(surfaceTexture);
                    List<Surface> surfaceList = new ArrayList<>();
                    surfaceList.add(previewSurface);
                    if (mTakingPicture) {
                        surfaceList.add(mImageReader.getSurface());
                    } else {
                        surfaceList.add(mMediaRecorder.getSurface());
                    }
                    captureRequest.addTarget(previewSurface);
                    mCameraDevice.createCaptureSession(surfaceList, new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            mCameraCaptureSession = session;
                            try {
                                if (isOpen) {
                                    mCameraCaptureSession.setRepeatingRequest(captureRequest.build(), null, mBackgroundHandler);
                                    if (mTakingPicture) {
                                        takePicture();
                                    } else {
                                        startRecordingVideo();
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                                Log.e(TAG, "onConfigured: " + e.getMessage());
                            }
                        }
    
                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
    
                        }
                    }, mBackgroundHandler);
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "startPreview: " + e.getMessage());
                hide();
            }
        }
    
    
        private void takePicture() {
            Log.d(TAG, "takePicture: ");
            if (mCameraDevice == null)
                return;
            try {
                // 创建拍照需要的CaptureRequest.Builder
                CaptureRequest.Builder captureRequestBuilder;
                captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                // 将imageReader的surface作为CaptureRequest.Builder的目标
                captureRequestBuilder.addTarget(mImageReader.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();
                int rotation = 1;
                // 根据设备方向计算设置照片的方向
                captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, DEFAULT_ORIENTATIONS.get(rotation));
                //拍照
                CaptureRequest mCaptureRequest = captureRequestBuilder.build();
                mCameraCaptureSession.capture(mCaptureRequest, mCaptureCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                super.onCaptureStarted(session, request, timestamp, frameNumber);
                Log.d(TAG, "onCaptureStarted: ");
            }
    
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                super.onCaptureCompleted(session, request, result);
                DeleteFiles.deleteLastFile(mContext, Environment.DIRECTORY_PICTURES, PICTURE_COUNTS);
                Log.d(TAG, "onCaptureCompleted: ");
                hide();
            }
    
            @Override
            public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
                super.onCaptureFailed(session, request, failure);
                Log.d(TAG, "onCaptureFailed: ");
                hide();
            }
        };
    
        private void startRecordingVideo() {
            Log.i(TAG, "startRecordingVideo: ");
            if (null == mCameraDevice || !mTextureView.isAvailable()) {
                return;
            }
            try {
                SurfaceTexture texture = mTextureView.getSurfaceTexture();
                assert texture != null;
                texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
                CaptureRequest.Builder captureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
                List<Surface> surfaces = new ArrayList<>();
    
                // Set up Surface for the camera preview
                Surface previewSurface = new Surface(texture);
                surfaces.add(previewSurface);
                captureRequest.addTarget(previewSurface);
    
                // Set up Surface for the MediaRecorder
                Surface recorderSurface = mMediaRecorder.getSurface();
                surfaces.add(recorderSurface);
                captureRequest.addTarget(recorderSurface);
    
                // Start a capture session
                // Once the session starts, we can update the UI and start recording
                mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
    
                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                        try {
                            if (isOpen) {
                                mCameraCaptureSession = cameraCaptureSession;
                                cameraCaptureSession.setRepeatingRequest(captureRequest.build(), null, mBackgroundHandler);
                                if (!isRecoreding()) {
                                    setRecoreding(true);
                                }
                                mMediaRecorder.start();
                                mIsRecordingVideo = true;
                                new Handler().postDelayed(() -> {
                                    stopRecordingVideo();
                                }, VIDEOTIME);
                            }
    
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
    
                    @Override
                    public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
    
                    }
                }, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        public boolean isRecoreding() {
            return isRecoreding;
        }
    
        public void setRecoreding(boolean recoreding) {
            isRecoreding = recoreding;
        }
    
        private void stopRecordingVideo() {
            try {
                if (mMediaRecorder != null && mIsRecordingVideo) {
                    mIsRecordingVideo = false;
                    mMediaRecorder.stop();
                    mMediaRecorder.reset();
                    mMediaRecorder = null;
                    setRecoreding(false);
                }
            } catch (Exception exception) {
                Log.e(TAG, "stopRecordingVideo: " + exception.getMessage());
            } finally {
                hide();
            }
        }
    
        private static Size chooseVideoSize(Size[] choices) {
            for (Size size : choices) {
                if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
                    return size;
                }
            }
            Log.e(TAG, "Couldn't find any suitable video size");
            return choices[choices.length - 1];
        }
    
        /**
         * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
         * This method should not to be called until the camera preview size is determined in
         * openCamera, or until the size of `mTextureView` is fixed.
         *
         * @param viewWidth  The width of `mTextureView`
         * @param viewHeight The height of `mTextureView`
         */
        private void configureTransform(int viewWidth, int viewHeight) {
            if (null == mTextureView) {
                return;
            }
            //        int rotation = getWindowManager().getDefaultDisplay().getRotation();
            int rotation = 1;
            Matrix matrix = new Matrix();
            RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
            RectF bufferRect = new RectF(0, 0, viewWidth, viewHeight);
            float centerX = viewRect.centerX();
            float centerY = viewRect.centerY();
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max(
                    (float) viewHeight / viewHeight,
                    (float) viewWidth / viewHeight);
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
            mTextureView.setTransform(matrix);
        }
    
        /**
         * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
         * width and height are at least as large as the respective requested values, and whose aspect
         * ratio matches with the specified value.
         *
         * @param choices     The list of sizes that the camera supports for the intended output class
         * @param width       The minimum desired width
         * @param height      The minimum desired height
         * @param aspectRatio The aspect ratio
         * @return The optimal {@code Size}, or an arbitrary one if none were big enough
         */
        private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
            // Collect the supported resolutions that are at least as big as the preview Surface
            List<Size> bigEnough = new ArrayList<>();
            int w = aspectRatio.getWidth();
            int h = aspectRatio.getHeight();
            for (Size option : choices) {
                if (option.getHeight() == option.getWidth() * h / w &&
                        option.getWidth() >= width && option.getHeight() >= height) {
                    bigEnough.add(option);
                }
            }
    
            // Pick the smallest of those, assuming we found any
            if (bigEnough.size() > 0) {
                return Collections.min(bigEnough, new CompareSizesByArea());
            } else {
                Log.e(TAG, "Couldn't find any suitable preview size");
                return choices[0];
            }
        }
    
        static class CompareSizesByArea implements Comparator<Size> {
    
            @Override
            public int compare(Size lhs, Size rhs) {
                // We cast here to ensure the multiplications won't overflow
                return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                        (long) rhs.getWidth() * rhs.getHeight());
            }
    
        }
    
        private PicturePath mPicturePath;
    
        public interface PicturePath {
            void getPicturePath(String path);
    
            void getPictureError(int error);
        }
    
        public void getPicturePath(PicturePath path) {
            this.mPicturePath = path;
        }
    
        private VideoPath mVideoPath;
    
        public interface VideoPath {
            void getVideoPath(String path);
    
            void getVideoError(int error);
        }
    
        public void getVideoPath(VideoPath path) {
            this.mVideoPath = path;
        }
    }
    

    toast_camera2_capture.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    
        <TextureView
            android:id="@+id/textureView"
            android:layout_width="0.1dp"
            android:layout_height="0.1dp"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"/>
    </RelativeLayout>
    

    因为是自定义了窗体管理器,要悬浮窗申请权限,其他动态权限自行申请

     //请求悬浮窗权限
        @TargetApi(Build.VERSION_CODES.M)
        private void getOverlayPermission() {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            intent.setData(Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, 0);
        }
    

    使用方式比较简单,直接 通过show()方法进行显示,hide()方法进行隐藏。

    相关文章

      网友评论

          本文标题:Android实现偷拍(视频录制)功能的一种方法

          本文链接:https://www.haomeiwen.com/subject/qyaptltx.html