zoukankan      html  css  js  c++  java
  • Android开发 MediaRecorder使用Camera2配合录制视频

    前言

      之前这个博客碰到了一些问题比如在获取mMediaRecorder.getSurface();的时候老实提示没有初始化导致报错。然后个人因为业务也没需求要Camera2录像,所以一直没有深究。但是最近有大神(感谢利工)指出其实是因为之前用这个行代码设置

    这行代码有一个大问题我一致没有注意到,这个MediaRecorder.VideoSource.CAMERA 属性其实是给Camera1使用的。

    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    需要修改成Camera2的

    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

    这个在官方注释里也有说明,如下

        public final class VideoSource {
          /* Do not change these values without updating their counterparts
           * in include/media/mediarecorder.h!
           */
            private VideoSource() {}
            public static final int DEFAULT = 0;
            /** Camera video source
             * <p>
             * Using the {@link android.hardware.Camera} API as video source.
             * </p>
             */
            public static final int CAMERA = 1;
            /** Surface video source
             * <p>
             * Using a Surface as video source.
             * </p><p>
             * This flag must be used when recording from an
             * {@link android.hardware.camera2} API source.
             * </p><p>
             * When using this video source type, use {@link MediaRecorder#getSurface()}
             * to retrieve the surface created by MediaRecorder.
             */
            public static final int SURFACE = 2;
        }

    现在主要问题解决了,我在详细讲解如何使用Camera2配合MediaRecorder录制视频

    详解部分

    需要的权限

        <uses-permission android:name="android.permission.RECORD_AUDIO"/><!--音频录制权限-->
        <uses-permission android:name="android.permission.CAMERA"/><!--摄像头权限-->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><!--存储权限-->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    Xml布局

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MedioRecorderCamera2Activity">
    
        <TextureView
            android:id="@+id/textureview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
        <Button
            android:id="@+id/btn_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"/>
    
        <Button
            android:id="@+id/btn_finish"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="结束"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintRight_toRightOf="parent"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>

    代码部分

    import androidx.annotation.NonNull;
    import androidx.appcompat.app.AppCompatActivity;
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.graphics.Camera;
    import android.graphics.ImageFormat;
    import android.graphics.SurfaceTexture;
    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.CaptureFailure;
    import android.hardware.camera2.CaptureRequest;
    import android.hardware.camera2.CaptureResult;
    import android.hardware.camera2.TotalCaptureResult;
    import android.hardware.camera2.params.StreamConfigurationMap;
    import android.media.MediaRecorder;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.HandlerThread;
    import android.util.DisplayMetrics;
    import android.util.Log;
    import android.util.Size;
    import android.view.Surface;
    import android.view.TextureView;
    import android.view.View;
    import android.widget.Button;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class MedioRecorderCamera2Activity extends AppCompatActivity {
        private static final String TAG = MedioRecorderCamera2Activity.class.getSimpleName();
        private Button mBtnStatr,mBtnFinish;
        private TextureView mTextureView;
        private CameraManager mCameraManager;
        private CameraDevice mCameraDevice;
        private CameraCaptureSession mCameraCaptureSession;
        private CameraDevice.StateCallback mCameraDeviceStateCallback;
        private CameraCaptureSession.StateCallback mSessionStateCallback;
        private CameraCaptureSession.CaptureCallback mSessionCaptureCallback;
        private CaptureRequest.Builder mPreviewCaptureRequest;
        private CaptureRequest.Builder mRecorderCaptureRequest;
        private MediaRecorder mMediaRecorder;
        private String mCurrentSelectCamera;
        private Handler mChildHandler;
    
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_medio_recorder_camera2);
            mTextureView = findViewById(R.id.textureview);
            mBtnStatr = findViewById(R.id.btn_start);
            mBtnFinish = findViewById(R.id.btn_finish);
            initClickListener();
            initChildHandler();
            initTextureViewStateListener();
            initMediaRecorder();
            initCameraDeviceStateCallback();
            initSessionStateCallback();
            initSessionCaptureCallback();
    
        }
    
        private void initClickListener(){
            mBtnStatr.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    config();
                    startRecorder();
    
                }
            });
            mBtnFinish.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    stopRecorder();
    
                }
            });
        }
    
        /**
         * 初始化TextureView的纹理生成监听,只有纹理生成准备好了。我们才能去进行摄像头的初始化工作让TextureView接收摄像头预览画面
         */
        private void initTextureViewStateListener(){
            mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    //可以使用纹理
                    initCameraManager();
                    selectCamera();
                    openCamera();
    
                }
    
                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                    //纹理尺寸变化
    
                }
    
                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    //纹理被销毁
                    return false;
                }
    
                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {
                    //纹理更新
    
                }
            });
        }
    
        /**
         * 初始化子线程Handler,操作Camera2需要一个子线程的Handler
         */
        private void initChildHandler(){
            HandlerThread handlerThread = new HandlerThread("Camera2Demo");
            handlerThread.start();
            mChildHandler = new Handler(handlerThread.getLooper());
        }
    
        /**
         * 初始化MediaRecorder
         */
        private void initMediaRecorder(){
            mMediaRecorder = new MediaRecorder();
        }
    
        /**
         * 配置录制视频相关数据
         */
        private void configMediaRecorder(){
            File file = new File(getExternalCacheDir(),"demo.mp4");
            if (file.exists()){
                file.delete();
            }
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置音频来源
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);//设置视频来源
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);//设置输出格式
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
            mMediaRecorder.setVideoEncodingBitRate(8*1024*1920);//设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。
            mMediaRecorder.setVideoFrameRate(30);//设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。
            Size size = getMatchingSize2();
            mMediaRecorder.setVideoSize(size.getWidth(),size.getHeight());
            mMediaRecorder.setOrientationHint(90);
            Surface surface = new Surface(mTextureView.getSurfaceTexture());
            mMediaRecorder.setPreviewDisplay(surface);
            mMediaRecorder.setOutputFile(file.getAbsolutePath());
            try {
                mMediaRecorder.prepare();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
    
        }
    
        /**
         * 重新配置录制视频时的CameraCaptureSession
         */
        private void config(){
            try {
                mCameraCaptureSession.stopRepeating();//停止预览,准备切换到录制视频
                mCameraCaptureSession.close();//关闭预览的会话,需要重新创建录制视频的会话
                mCameraCaptureSession = null;
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
            configMediaRecorder();
            Size cameraSize = getMatchingSize2();
            SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
            surfaceTexture.setDefaultBufferSize(cameraSize.getWidth(),cameraSize.getHeight());
            Surface previewSurface = new Surface(surfaceTexture);
            Surface recorderSurface = mMediaRecorder.getSurface();//从获取录制视频需要的Surface
            try {
                mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                mPreviewCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                mPreviewCaptureRequest.addTarget(previewSurface);
                mPreviewCaptureRequest.addTarget(recorderSurface);
                //请注意这里设置了Arrays.asList(previewSurface,recorderSurface) 2个Surface,很好理解录制视频也需要有画面预览,第一个是预览的Surface,第二个是录制视频使用的Surface
                mCameraDevice.createCaptureSession(Arrays.asList(previewSurface,recorderSurface),mSessionStateCallback,mChildHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 开始录制视频
         */
        private void startRecorder(){
            mMediaRecorder.start();
    
    
        }
    
        /**
         * 暂停录制视频(暂停后视频文件会自动保存)
         */
        private void stopRecorder(){
            mMediaRecorder.stop();
            mMediaRecorder.reset();
        }
    
        /**
         * 初始化Camera2的相机管理,CameraManager用于获取摄像头分辨率,摄像头方向,摄像头id与打开摄像头的工作
         */
        private void initCameraManager(){
            mCameraManager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
    
        }
    
        /**
         * 选择一颗我们需要使用的摄像头,主要是选择使用前摄还是后摄或者是外接摄像头
         */
        private void selectCamera(){
            if (mCameraManager != null) {
                Log.e(TAG, "selectCamera: CameraManager is null");
    
            }
            try {
                String[] cameraIdList = mCameraManager.getCameraIdList();   //获取当前设备的全部摄像头id集合
                if (cameraIdList.length == 0){
                    Log.e(TAG, "selectCamera: cameraIdList length is 0");
                }
                for (String cameraId : cameraIdList){ //遍历所有摄像头
                    CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);//得到当前id的摄像头描述特征
                    Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); //获取摄像头的方向特征信息
                    if (facing == CameraCharacteristics.LENS_FACING_BACK){ //这里选择了后摄像头
                        mCurrentSelectCamera = cameraId;
    
                    }
                }
    
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        private void initCameraDeviceStateCallback(){
            mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    //摄像头被打开
                    try {
                        mCameraDevice = camera;
                        Size cameraSize = getMatchingSize2();//计算获取需要的摄像头分辨率
                        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();//得到纹理
                        surfaceTexture.setDefaultBufferSize(cameraSize.getWidth(),cameraSize.getHeight());
                        Surface previewSurface = new Surface(surfaceTexture);
                        mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                        mPreviewCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                        mPreviewCaptureRequest.addTarget(previewSurface);
                        mCameraDevice.createCaptureSession(Arrays.asList(previewSurface),mSessionStateCallback,mChildHandler);//创建数据捕获会话,用于摄像头画面预览,这里需要等待mSessionStateCallback回调
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
    
                }
    
                @Override
                public void onDisconnected(@NonNull CameraDevice camera) {
                    //摄像头断开
    
                }
    
                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                    //异常
    
                }
            };
        }
    
        private void initSessionStateCallback(){
            mSessionStateCallback = new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mCameraCaptureSession = session;
                    try {
                        //执行重复获取数据请求,等于一直获取数据呈现预览画面,mSessionCaptureCallback会返回此次操作的信息回调
                        mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(),mSessionCaptureCallback,mChildHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
    
                }
    
                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
    
                }
            };
        }
    
        private void initSessionCaptureCallback(){
            mSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                    super.onCaptureStarted(session, request, timestamp, frameNumber);
                }
    
                @Override
                public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
                    super.onCaptureProgressed(session, request, partialResult);
                }
    
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
                }
    
                @Override
                public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
                    super.onCaptureFailed(session, request, failure);
                }
            };
        }
    
        /**
         * 打开摄像头,这里打开摄像头后,我们需要等待mCameraDeviceStateCallback的回调
         */
        @SuppressLint("MissingPermission")
        private void openCamera(){
            try {
                mCameraManager.openCamera(mCurrentSelectCamera,mCameraDeviceStateCallback,mChildHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 计算需要的使用的摄像头分辨率
         * @return
         */
        private Size getMatchingSize2(){
            Size selectSize = null;
            try {
                CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentSelectCamera);
                StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
                DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); //因为我这里是将预览铺满屏幕,所以直接获取屏幕分辨率
                int deviceWidth = displayMetrics.widthPixels; //屏幕分辨率宽
                int deviceHeigh = displayMetrics.heightPixels; //屏幕分辨率高
                Log.e(TAG, "getMatchingSize2: 屏幕密度宽度="+deviceWidth);
                Log.e(TAG, "getMatchingSize2: 屏幕密度高度="+deviceHeigh );
                /**
                 * 循环40次,让宽度范围从最小逐步增加,找到最符合屏幕宽度的分辨率,
                 * 你要是不放心那就增加循环,肯定会找到一个分辨率,不会出现此方法返回一个null的Size的情况
                 * ,但是循环越大后获取的分辨率就越不匹配
                 */
                for (int j = 1; j < 41; j++) {
                    for (int i = 0; i < sizes.length; i++) { //遍历所有Size
                        Size itemSize = sizes[i];
                        Log.e(TAG,"当前itemSize 宽="+itemSize.getWidth()+"高="+itemSize.getHeight());
                        //判断当前Size高度小于屏幕宽度+j*5  &&  判断当前Size高度大于屏幕宽度-j*5  &&  判断当前Size宽度小于当前屏幕高度
                        if (itemSize.getHeight() < (deviceWidth + j*5) && itemSize.getHeight() > (deviceWidth - j*5)) {
                            if (selectSize != null){ //如果之前已经找到一个匹配的宽度
                                if (Math.abs(deviceHeigh-itemSize.getWidth()) < Math.abs(deviceHeigh - selectSize.getWidth())){ //求绝对值算出最接近设备高度的尺寸
                                    selectSize = itemSize;
                                    continue;
                                }
                            }else {
                                selectSize = itemSize;
                            }
    
                        }
                    }
                    if (selectSize != null){ //如果不等于null 说明已经找到了 跳出循环
                        break;
                    }
                }
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
            Log.e(TAG, "getMatchingSize2: 选择的分辨率宽度="+selectSize.getWidth());
            Log.e(TAG, "getMatchingSize2: 选择的分辨率高度="+selectSize.getHeight());
            return selectSize;
        }
    
    
    }

    End

  • 相关阅读:
    linux tftp 服务
    AtomicInteger
    深入理解JVM(三)——垃圾收集策略具体解释
    Android 虚拟现实(virtual reality)入门指南
    Java千百问_05面向对象(005)_接口和抽象类有什么差别
    postman发送json格式的post请求
    什么是Session分布式共享
    如何设计一个单点登录系统(3)?
    如何设计一个单点登录系统(2)?
    如何设计一个单点登录系统(1)?
  • 原文地址:https://www.cnblogs.com/guanxinjing/p/11009192.html
Copyright © 2011-2022 走看看