Android 预览基于UVC协议的摄像头并截图或录屏

先来看效果,演示视频时长比较长,转成gif之后仍然超过图片限制,所以直接放视频连接,结尾附带demo源码

device-2023-05-29-182209

该篇为Android 外接基于UVC协议的摄像头并实现预览的延申篇

在前者的基础上,增加了四个按钮,如下

四个按钮的功能顾名思义,下面将会分别介绍四个按钮对应的功能的实现逻辑

公共部分:定义两个文件的存放地址

    public static final String photoPath = "/sdcard/test/photo";
    public static final String videoPath = "/sdcard/test/video";

关键代码: 

(1)拍照

binding.btnPhoto.setOnClickListener(view -> {
                try {
                    File file = new File(photoPath + System.currentTimeMillis() + ".jpg");
                    ImageCapture.OutputFileOptions options =
                            new ImageCapture.OutputFileOptions.Builder(file).build();
                    //进行拍照
                    mCameraHelper.takePicture(options, new ImageCapture.OnImageCaptureCallback() {
                        @Override
                        public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                            Toast.makeText(MainActivity.this, "拍照成功", Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onError(int imageCaptureError, @NonNull String message, @Nullable Throwable cause) {
                            Toast.makeText(MainActivity.this, "拍照失败", Toast.LENGTH_SHORT).show();

                        }
                    });
                } catch (Exception e) {
                    Log.e(TAG, e.getLocalizedMessage(), e);
                }
            });

说明:直接调用libuvccamera库的takePicture方法

(2)录像

//当前是否正在开始录像
private boolean isRecording = false;
private MediaRecorderHelper mMediaEncoderHelper;

初始化:

private void initMediaEncoderHelper() {
        if (mCameraHelper != null) {
            if (mMediaEncoderHelper == null) {
                mMediaEncoderHelper = new MediaRecorderHelper(mCameraHelper);
            } else {
                mMediaEncoderHelper.setCameraClient(mCameraHelper);
            }
            mMediaEncoderHelper.setCallback(what -> {
                String msg = "录制过程中发生未知错误,录像结束";
                switch (what) {
                    case MediaRecorderHelper.ERROR_MAX_FILESIZE_REACHED:
                        msg = "录制文件大小达到最大,录像结束";
                        break;
                    default:
                        break;
                }
                Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
                stopRecord();
            });
        }
    }

 MediaRecorderHelper.java

public class MediaRecorderHelper implements MediaRecorder.OnInfoListener, MediaRecorder.OnErrorListener {
    private static final String TAG = MediaRecorderHelper.class.getSimpleName();

    public static final int ERROR_UNKNOWN = MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN;
    public static final int ERROR_SERVER_DIED = MediaRecorder.MEDIA_ERROR_SERVER_DIED;
    public static final int ERROR_MAX_FILESIZE_REACHED = MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED;

    private static final int FRAME_RATE = 30;
    private static final float BPP = 0.1f;

    private final Object mSync = new Object();

    private MediaRecorder mMediaRecorder;
    private Surface mRecordeSurface;

    private WeakReference<ICameraHelper> mCameraHelperWeak;

    private Size mSize;

    private volatile boolean mIsRecording;
    private volatile boolean mIsReady;

    private OnMediaRecorderCallback mCallback;

    private String mVideoFilePath = null;
    private String mVideoFilePathNext = null;

    private Handler mAsyncHandler = HandlerThreadHandler.createHandler(TAG);

    public MediaRecorderHelper(ICameraHelper client) {
        setCameraClient(client);
    }

    public void setCameraClient(ICameraHelper client) {
        this.mCameraHelperWeak = new WeakReference<>(client);
        this.mSize = client.getPreviewSize();
    }

    public void setCallback(OnMediaRecorderCallback callback) {
        mCallback = callback;
    }

    public void release() {
        if (mRecordeSurface != null) {
            mRecordeSurface.release();
            mRecordeSurface = null;
        }
    }

    public boolean isRecording() {
        return mIsRecording;
    }

    public void startRecording(String filePath) {
        if (DEBUG) Log.v(TAG, "startRecording:" + filePath);
        if (mIsReady) return;
        mIsReady = true;

        //初始化了一次mediaRecorder,并且这个mediaRecorder状态到了prepare()时这个surface 才可以使用
        initMediaRecorder();
        startRecord(filePath);

        mVideoFilePath = filePath;
    }

    private void initMediaRecorder() {
        synchronized (mSync) {
            if (mMediaRecorder == null) {
                mMediaRecorder = new MediaRecorder();
            } else {
                // 重置之前的实例
                mMediaRecorder.reset();
            }

//            if (mRecordeSurface != null) {
//                // 设置surface
//                mMediaRecorder.setInputSurface(mRecordeSurface);
//            }

            //视频源,意思是从Surface里面读取画面去录制
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            //音频源(因为没有录音设备,录音的话,会有电流的滋滋声,所以默认关闭录音)
//            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            //输出格式
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//            //捕获率
//            mMediaRecorder.setCaptureRate(mSize.fps);
            //帧率
            mMediaRecorder.setVideoFrameRate(FRAME_RATE);
            //码率
            mMediaRecorder.setVideoEncodingBitRate(calcBitRate());
            // 视频编码格式
            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            //音频编码格式
//            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            //视频宽高
            mMediaRecorder.setVideoSize(mSize.width, mSize.height);
//            mMediaRecorder.setMaxFileSize(1000 * 1000 * 10);

            mMediaRecorder.setOnErrorListener(this);
            mMediaRecorder.setOnInfoListener(this);
        }
    }

    private void startRecord(String filePath) {
        if (mMediaRecorder == null) {
            return;
        }

        mMediaRecorder.setOutputFile(filePath);

        try {
            mMediaRecorder.prepare();

            mRecordeSurface = mMediaRecorder.getSurface();
            if (mCameraHelperWeak.get() != null && mRecordeSurface != null) {
                mCameraHelperWeak.get().addSurface(mRecordeSurface, true);
            }

            mMediaRecorder.start();
        } catch (IOException e) {
            if (DEBUG) Log.e(TAG, "startRecord", e);
            onErrorCallback(ERROR_UNKNOWN);
            return;
        }

        mIsRecording = true;
    }

    private int calcBitRate() {
        final int bitrate = (int) (BPP * FRAME_RATE * mSize.width * mSize.height);
        Log.i(TAG, String.format("bitrate=%5.2f[Mbps]", bitrate / 1024f / 1024f));
        return bitrate;
    }

    public void pauseRecording() {
        if (DEBUG) Log.v(TAG, "pauseRecording");
        if (mMediaRecorder == null) {
            return;
        }
        synchronized (mSync) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                mMediaRecorder.pause();
            }
            if (mCameraHelperWeak.get() != null && mRecordeSurface != null) {
                mCameraHelperWeak.get().removeSurface(mRecordeSurface);
            }
        }
    }

    public void resumeRecording() {
        if (DEBUG) Log.v(TAG, "resumeRecording");
        if (mMediaRecorder == null) {
            return;
        }
        synchronized (mSync) {
            if (mCameraHelperWeak.get() != null && mRecordeSurface != null) {
                mCameraHelperWeak.get().addSurface(mRecordeSurface, true);
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                mMediaRecorder.resume();
            }
        }
    }

    public void stopRecording() {
        if (DEBUG) Log.v(TAG, "stopRecording");
        mIsReady = false;
        mIsRecording = false;
        if (mMediaRecorder != null) {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
            mMediaRecorder.release();
            mMediaRecorder = null;
        }

        synchronized (mSync) {
            if (mCameraHelperWeak.get() != null && mRecordeSurface != null) {
                mCameraHelperWeak.get().removeSurface(mRecordeSurface);
            }
        }

        scanMediaFile();
    }

    private void scanMediaFile() {
        if (mVideoFilePath != null) {
            String path = mVideoFilePath;
            mAsyncHandler.postDelayed(() -> {
                final Context context = UVCUtils.getApplication();
                try {
                    if (DEBUG) Log.i(TAG, "MediaScannerConnection#scanFile");
                    // Broadcasts the media file to the rest of the system
                    String[] paths = {path};
                    MediaScannerConnection.scanFile(context, paths, null, null);
                } catch (final Exception e) {
                    Log.e(TAG, "MediaScannerConnection:", e);
                }
            }, 500);
            mVideoFilePath = null;
        }
    }

    @Override
    public void onError(MediaRecorder mr, int what, int extra) {
        onErrorCallback(what);
    }

    @Override
    public void onInfo(MediaRecorder mr, int what, int extra) {
        if (DEBUG) Log.v(TAG, "onInfo:what=" + what + ";extra=" + extra);
        switch (what) {
            case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    // have to reach max file size, so we ready the next output file
                    mVideoFilePathNext = videoPath + System.currentTimeMillis() + ".mp4";
                    try {
                        mMediaRecorder.setNextOutputFile(new File(mVideoFilePathNext));
                    } catch (IOException e) {
                        if (DEBUG) Log.e(TAG, "mMediaRecorder.setNextOutputFile", e);
                    }
                }
                break;
            case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
                // when api version is less than Android O, we can't use setNextOutputFile to auto switch new output file
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                    onErrorCallback(what);
                }
                break;
            case MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED:
                scanMediaFile();
                mVideoFilePath = mVideoFilePathNext;
                mVideoFilePathNext = null;
                break;
            default:
                break;
        }
    }

    private void onErrorCallback(int what) {
        stopRecording();
        if (mCallback != null) {
            mCallback.onError(what);
        }
    }

    public interface OnMediaRecorderCallback {
        void onError(int what);
    }
}

1.isRecording = false

binding.btnVideo.setOnClickListener(view -> {
                if (isRecording) {
                    //暂停录像
                    isRecording = false;
                    binding.btnVideo.setText("开始录像");
                    stopRecord();
                } else {
                    //开始录像
                    binding.btnVideo.setText("暂停录像");
                    isRecording = true;
                    startRecord();
                }
            });

 定义四个方法:暂停录像、开始录像

private void stopRecord() {
        if (mMediaEncoderHelper.isRecording()) {
            mMediaEncoderHelper.stopRecording();
        }
        Toast.makeText(MainActivity.this, "录制成功", Toast.LENGTH_SHORT).show();
    }

    private void startRecord() {
        if (mCameraHelper != null && !mMediaEncoderHelper.isRecording()) {
            mMediaEncoderHelper.startRecording(videoPath + System.currentTimeMillis() + ".mp4");
        }
    }

(3)打开相册

binding.btnOpenSystemAlbum.setOnClickListener(view -> {
            Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
            startActivity(intent);
        });

(4)打开视频

 binding.btnOpenSystemVideo.setOnClickListener(view -> {
            Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
            startActivity(intent);
        });

demo源码

猜你喜欢

转载自blog.csdn.net/weixin_53324308/article/details/130934272