Android Camera、Camera2详解

2023-11-16

前言

Android5.0之前使用android.hardware包下的Camera类进行拍照、录视频等功能。5.0以后,新增了android.hardware.camera2包,利用新的机制、新的类进行拍照、录视频。

使用Camera

一、拍照

由于手机摄像头配置不同、摄像头摆放方向不同、位置不同等等因素,与摄像头相关参数如:摄像头个数、支持的像素、画面预览方向、闪光灯、对焦方式、帧率等等都不一样,必须根据当前手机的配置动态获取。获取方法如下:

Camera.Parameters p = mCamera.getParameters();
List<Camera.Size> preSizes = p.getSupportedPreviewSizes();
List<Camera.Size> preSizes = p.getSupportedPictureSizes();
......
......

获取其它参数就是getSupportedXXX,返回的都是list,手动遍历,选择最合适的参数。

拍照主要由两部分功能组成,一个是预览界面,一个是“获取正确的照片”。实现步骤如下:
1、初始化Camera

public boolean initCamera() {
    mCamera = null;
    try {
        mCamera = Camera.open();
    } catch (Exception e) {
    }
    return null != mCamera;
}

2、设置预览orientation

private void setPreviewOrientation(){
    int result = 0;

    // 计算Activity转动的角度degree。如果在manifest中设置为portrait,则degree总是为0
    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int degree = 0;
    switch (rotation) {
        case Surface.ROTATION_0:
            degree = 0;
            break;
        case Surface.ROTATION_90:
            degree = 90;
            break;
        case Surface.ROTATION_180:
            degree = 180;
            break;
        case Surface.ROTATION_270:
            degree = 270;
            break;
    }

    // 获取后置摄像头信息,获取方法见后面
    Camera.CameraInfo info = getBackCameraInfo();

    // 摄像头转动角度减去Activity转动角度,得到角度差,然后在设置预览画面角度时把这个角度差补偿回来
    if (null != info) {
        int offset = info.orientation - degree;

        // 如果角度差为正值,result就等于offset;
        // 如果角度差为-90度,result就转270度,刚好到-90度的位置;如果角度差为-180度....以此类推
        result = (offset + 360) % 360;
    }

    // 设置预览画面转动角度
    mCamera.setDisplayOrientation(result);
}


//遍历所有摄像头信息,根据info.facing判断该摄像头是前置还是后置
public static Camera.CameraInfo getBackCameraInfo() {
    int cameraNums = Camera.getNumberOfCameras();
    for (int i = 0; i < cameraNums; i++) {
        // CameraInfo类只是存储了一些字段,刚new出来,这些字段都为null
        Camera.CameraInfo info = new Camera.CameraInfo();
        // 虽然方法名为getXXX,但实际上的作用是:获取第cameraId个摄像头,把这个摄像头的信息存储到info中
        Camera.getCameraInfo(i, info);
        if (Camera.CameraInfo.CAMERA_FACING_BACK == info.facing) {
            return info;
        }
    }
    return null;
}

3、设置预览画面大小、比例

// 获取该摄像头支持的预览尺寸sizes,遍历sizes,计算出最合适的尺寸bestSize。具体算法就不贴出了
private void setPreviewSize(){
    Camera.Parameters p = mCamera.getParameters();
    List<Camera.Size> preSizes = p.getSupportedPreviewSizes();
    Camera.Size bestSize = KuCameraUtil.getBestSize(preSizes, minTotalPix, maxTotalPix, rate);
    if (null != bestSize && bestSize.width > 0 && bestSize.height > 0) {
        p.setPreviewSize(bestSize.width, bestSize.height);
        // camera设置完previewSize后,负责显示预览的surfaceView/textureView也需要进行设置大小
        mCamera.setParameters(p);
    }
}

4、初始化显示预览画面的控件surfaceView或textureView

// 预览控件既可以用surfaceView,也可以用textureView,这两个类的详细介绍有空再整理,推荐使用textureView
private void initTextureView(){
    mTextureView = (TextureView) findViewById(R.id.preview);
    // 获取camera的previewSize,根据其设置mTextureView控件的大小、比例
    Camera.Size size = mCamera.getPreviewSize();
    if (null != size) {
        // 宽度固定为屏幕宽度,按照previewSize的宽高比设置高度
        float rate = (float) size.height / (float) size.width;
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     mTextureView.getLayoutParams();
        params.width = ScreenUtils.getScreenWidth(this);
        params.height = (int) (rate >= 1 ? params.width * rate : params.width / rate);
        mTextureView.setLayoutParams(params);
}

    // mTextureView生命周期回调
    mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            // 开始预览
            mKuCamera.startPreview(surface);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            // 结束预览
            mKuCamera.stopPreview();
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        }
    });
}

5、执行拍照,获取拍照图片

// 拍照
private void takePhoto(){
    mCamera.takePhoto(new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] bytes, Camera camera) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            if (bitmap != null) {
                try {
                    File file = new File(KuPathUtil.getImageDir(), KuPathUtil.getNowTimeStr() + "jpg");
                    OutputStream os = new FileOutputStream(file);
                    os.write(bytes);
                    os.flush();
                    os.close();
                    // 获取到的图片是摄像头捕获到的原始图片,也需要对其进行旋转,旋转方法见后面
                    PhotoUtil.setPicOrientation(file.getAbsolutePath(), mNeedOrientation);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    });
}


// 旋转图片
public static void setPicOrientation(String filePath, int degree) {
    try {
        ExifInterface exifInterface = new ExifInterface(filePath);

        String orientation = String.valueOf(ExifInterface.ORIENTATION_NORMAL);
        // 角度转换为对应的ORIENTATION_ROTATE值
        if (90 == degree) {
            orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);
        } else if (180 == degree) {
            orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);
        } else if (270 == degree) {
            orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);
        }

        //设置选择角度
        exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, orientation);
        exifInterface.saveAttributes();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

6、停止预览、释放camera资源

private void release() {
    if (null != mCamera) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
}


二、录视频

1、初始化camera、设置预览orientation、设置previewSize、初始化surfaceView/textureView与上面拍照的流程一模一样。其中,如果使用surfaceView,在MediaRecorder中也需要设置,使用textureView则不需要。

2、进行录制

private void startRecording(){
    try {
        // 初始化recorder
        mRecorder = new MediaRecorder();

        // 解锁摄像头,连接到摄像头
        mCamera.unlock();
        mRecorder.setCamera(mCamera);

        // 设置音、视频源
        mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        // 设置文件输出格式、音视频编码格式。设置顺序必须按照下面的顺序来,否则会报错
        // 关于音视频格式的优缺点,参考http://blog.csdn.net/wcl0715/article/details/676137和http://blog.csdn.net/androidzhaoxiaogang/article/details/6865644
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

        // 设置orientation,这个跟预览画面的角度一样,按照上面的代码获取角度
        mRecorder.setOrientationHint(mNeedOrientation);

        // 设置比特率,这个参数对视频文件的大小影响最大(格式相同情况下)
        mRecorder.setVideoEncodingBitRate(800 * 800);

        // 设置帧率。设置得过低会闪屏,设置高一点也不会增加文件大小,建议设置30左右
        mRecorder.setVideoFrameRate(30);

        // 设置视频size。通过camera.getParameters().getSupportedVideoSizes()获取到所有支持的sizes,选一个最合适的
        if (size.width > 0 && size.height > 0) {
            mRecorder.setVideoSize(size.width , size.height);
        }

        // 到时间后,可以通过MediaRecorder.OnInfoListener接收到回调;到时间后录制不会自动停止,但最终视频文件只截取前面10s
        mRecorder.setMaxDuration(10000);

        mRecorder.setOutputFile(videoPath);

        // 准备、开始
        mRecorder.prepare();
        mRecorder.start();
        return true;
    } catch (IOException e) {
        Log.i("wk", "record prepare failed,IOException:" + e.toString());
        return false;
    }
}

3、停止录制、释放MediaRecorder资源

private void stopRecording() {
    if (null != mRecorder) {
        mRecorder.stop();
        mRecorder.release();
        mRecorder = null;
    }
}

4、锁定Camera、停止预览、释放Camera资源


使用Camera2

Camera2网上已经有很多介绍了,我写了一个使用Camera2录像的demo,使用方法、注意事项等就直接在代码注释中,就不单独用文字描述了,偷懒。

为了方便理清camera2使用的主逻辑,一些配置代码、计算代码放在各个helper类里。这种代码设计不是最优设计,实际开发中不要照搬。

先是使用的代码,各个helper类的代码放在后面。

/**
 * 一、Camera2使用
 * Camera2的操作都是基于管道的,就是发送请求、等待回应的过程,使用起来没有代码结构不如Camera那种线性调用清晰。通过下面四个回调就能说清楚使用过程:
 * 
 * 1.等待surface创建成功的回调,即SurfaceTextureListener(或者是SurfaceView的listener,demo用的是TextureView)
 * 做Camera开发就必须要预览,要预览就得有Surface,所以第一步就是等待Surface创建完成;
 * 
 * 2.等待Camera启动完成的回调,即CameraDevice.StateCallback
 * Camera的启动需要一个过程,只有Camera启动后才可进行各种操作
 * 
 * 3.等待会话建立的回调,即CameraCaptureSession.StateCallback
 * 要向Camera发送各种操作请求,就必须先建立会话通道
 * 
 * 4.等待操作请求的回调,即CameraCaptureSession.CaptureCallback
 * 向Camera发起了"拍照"请求后,Camera需要一定时间才能完成,等待完成后就可以对图像数据进行处理了
 * 
 * 总结起来就是:创建surface、启动camera、创建camera会话、发起拍照请求
 * 
 * 二、预览、拍照/录像中的方向问题
 * 1.摄像头总是采集某个矩形范围内的图像,摄像头的方向决定了这个矩形的哪个边是底、哪个边是左/右;
 * 
 * 2.摄像头会把采集到的图像按照摄像头的方向传输给屏幕,用于显示预览图,所以需要根据摄像头方向和屏幕方向,决定预览显示的旋转角度;
 * 
 * 3.摄像头采集的图像就是最终的成像数据,所以需要根据摄像头方向和拍摄时的手机方向,决定最终成像需要旋转的角度
 */
public class VideoActivity extends Activity implements View.OnClickListener {
    private static final String TAG = "VideoActivity";

    private TextureView mTextureView;

    private CameraDevice mCameraDevice;

    // 方便理清camera2使用的主逻辑,一些配置代码、计算代码放在各个helper类里
    private CameraHelper mCameraHelper;
    private RecorderHelper mRecorderHelper;
    private TextureHelper mTextureHelper;

    // 一个device同一时间只能存在一个session,新的session启动时会关闭其它session;
    // 一个session对应一个request、surfaceList,注意处理好一一对应关系
    private CaptureRequest.Builder mRequest;
    private List<Surface> mSurfaceList = new ArrayList<>();
    private CameraCaptureSession mSession;

    // camera2中用到的几个回调,通过指定handler,回调方法就会在该handler所在线程被调用
    private HandlerThread mBackgroundThread;
    private Handler mBackgroundHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video);

        mTextureView = findViewById(R.id.texture);
        findViewById(R.id.video_record).setOnClickListener(this);
        findViewById(R.id.video_stop).setOnClickListener(this);

        mCameraHelper = new CameraHelper(this);
        mRecorderHelper = new RecorderHelper(this);
        mTextureHelper = new TextureHelper(this);
    }

    @Override
    public void onResume() {
        super.onResume();

        // 启动后台线程,用于执行回调中的代码
        startBackgroundThread();

        // 如果Activity是从stop/pause回来,TextureView是OK的,只需要重新开启camera就行
        if (mTextureView.isAvailable()) {
            openCamera();
        } else {
            // Activity创建时,添加TextureView的监听,TextureView创建完成后就可以开启camera就行了
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }

    @Override
    public void onPause() {
        // 关闭camera,关闭后台线程
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.video_record:
                if (!mRecorderHelper.isRecording()) {
                    startRecord();
                }
                break;
            case R.id.video_stop:
                if (mRecorderHelper.isRecording()) {
                    stopRecord();
                }
                break;
        }
    }

    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void openCamera() {
        // 设置预览大小、方向/角度
        mTextureHelper.configPreview(mTextureView, mTextureView.getWidth(), mTextureView.getHeight());

        // 开启后置摄像头
        mCameraHelper.openCamera(mCameraHelper.getBackCameraId(), new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
                // 如果openCamera()方法的第三个参数指定了handler,那么下面的代码就会在该handler所在线程中执行,如果不指定就在openCamera()方法所在线程执行
                mCameraDevice = cameraDevice;
                startPreviewSession();

                if (null != mTextureView) {
                    mTextureHelper.configPreview(mTextureView, mTextureView.getWidth(), mTextureView.getHeight());
                }
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                cameraDevice.close();
                mCameraDevice = null;
            }

            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int error) {
                cameraDevice.close();
                mCameraDevice = null;
                finish();
            }

        }, null);
    }

    private void addTextureViewSurface() {
        // 获取TextureView中的surface,添加到request中、添加到surfaceList中
        Surface previewSurface = mTextureHelper.getSurface(mTextureView);

        if (null != previewSurface) {
            mRequest.addTarget(previewSurface);
            mSurfaceList.add(previewSurface);
        }
    }

    private void addRecorderSurface() {
        // 获取MediaRecorder中的surface,添加到request中、添加到surfaceList中
        Surface recorderSurface = mRecorderHelper.getSurface();

        if (null != recorderSurface) {
            mRequest.addTarget(recorderSurface);
            mSurfaceList.add(recorderSurface);
        }
    }

    private void closeCamera() {
        // 关闭camera预览,关闭MediaRecorder
        closePreviewSession();
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        mRecorderHelper.release();
    }

    private void startPreviewSession() {
        if (null == mCameraDevice || !mTextureView.isAvailable()) {
            return;
        }

        try {
            // 创建新的会话前,关闭以前的会话
            closePreviewSession();

            // 创建预览会话请求
            mSurfaceList.clear();
            mRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            addTextureViewSurface();

            // 启动会话
            // 参数1:camera捕捉到的画面分别输出到surfaceList的各个surface中;
            // 参数2:会话状态监听;
            // 参数3:监听器中的方法会在指定的线程里调用,通过一个handler对象来指定线程;
            mCameraDevice.createCaptureSession(mSurfaceList, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mSession = session;
                    updatePreview();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                }

                @Override
                public void onClosed(@NonNull CameraCaptureSession session) {
                    super.onClosed(session);
                }
            }, mBackgroundHandler);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void startRecordSession() {
        if (null == mCameraDevice || !mTextureView.isAvailable()) {
            return;
        }

        try {
            closePreviewSession();

            // 创建录像会话请求
            mSurfaceList.clear();
            mRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
            addTextureViewSurface();
            addRecorderSurface();

            // 启动会话。可以看出跟上面的"预览session"是一样的,只是surfaceList多加了一个
            mCameraDevice.createCaptureSession(mSurfaceList, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mSession = session;
                    updatePreview();

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mRecorderHelper.start();
                        }
                    });
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    Toast.makeText(VideoActivity.this, "Failed", Toast.LENGTH_SHORT).show();
                }
            }, mBackgroundHandler);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void updatePreview() {
        if (null == mCameraDevice) {
            return;
        }

        try {
            mRequest.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

            // 这个接口是预览。作用是把camera捕捉到的画面输出到surfaceList中的各个surface上,每隔一定时间重复一次
            mSession.setRepeatingRequest(mRequest.build(), null, mBackgroundHandler);

            // 这个接口是拍照。由于拍照需要获得图像数据,所以这里需要实现CaptureCallback,在回调里获得图像数据
//            mSession.capture(CaptureRequest request, CaptureCallback listener, Handler handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void closePreviewSession() {
        if (mSession != null) {
            try {
                mSession.stopRepeating();
                mSession.abortCaptures();
                mSession.close();
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private void startRecord() {
        // 设置Recorder配置,启动录像会话
        int sensorOrientation = mCameraHelper.getSensorOrientation(mCameraHelper.getBackCameraId());
        int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
        mRecorderHelper.configRecorder(sensorOrientation, displayRotation);

        startRecordSession();
    }

    private void stopRecord() {
        // 关闭录像会话,停止录像,重新进入预览
        mRecorderHelper.stop();
        startPreviewSession();
    }

    // TextureView状态监听
    private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
            openCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
            mTextureHelper.configPreview(mTextureView, width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        }
    };
}

接下来是三个helper类的代码,首先是TextureHelper类:

public class TextureHelper {
    private Activity mActivity;

    // 这里只是展示用法,实际开发中需要根据摄像头的支持size来取
    private Size mPreviewSize = new Size(960, 720);

    public TextureHelper(Activity activity) {
        mActivity = activity;
    }

    // 配置预览图的大小、方向/角度
    public void configPreview(TextureView textureView, int targetWidth, int targetHeight) {
        int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, targetWidth, targetHeight);
        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 scale = Math.max((float) targetHeight / mPreviewSize.getHeight(), (float) targetWidth / mPreviewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        }
        textureView.setTransform(matrix);
    }

    // 根据TextureView和预览size,获取surface
    public Surface getSurface(TextureView textureView) {
        SurfaceTexture texture = textureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(960, 720);
        return new Surface(texture);
    }
}

然后是CameraHelper类:

public class CameraHelper {
    private CameraManager mManager;
    private String mBackCameraId;

    public CameraHelper(Context context) {
        mManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    }

    public String getBackCameraId() {
        if(!TextUtils.isEmpty(mBackCameraId)){
            return mBackCameraId;
        }

        try {
            String[] ids = mManager.getCameraIdList();
            for (String cameraId : ids) {
                CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                // 根据摄像头的朝向判断是否是后置摄像头
                if (null != facing && CameraMetadata.LENS_FACING_BACK == facing) {
                    mBackCameraId = cameraId;
                    return cameraId;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return "";
    }

    public int getSensorOrientation(String cameraId) {
        try {
            CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
            return characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return -1;
    }

    public Size[] getSupportSize(String cameraId, Class klass) {
        try {
            CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            if (null != map) {
                return map.getOutputSizes(klass);
//                map.getOutputSizes(MediaRecorder.class);// 支持的录像视频size
//                map.getOutputSizes(MediaRecorder.class);// 支持的录像视频size
//                map.getOutputSizes(ImageReader.class);// 支持的拍照照片size
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 注意,camera、recorder权限都是隐私权限,6.0以后需要动态权限配置
    @SuppressLint("MissingPermission")
    public void openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler) {
        try {
            mManager.openCamera(cameraId,callback,handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

最后是RecorderHelper类:

public class RecorderHelper {
    private Context mContext;
    private MediaRecorder mRecorder;
    private String mPath;
    private boolean hasPrepared;

    private static final int SENSOR_DEFAULT_DEGREES = 90;
    private static final int SENSOR_INVERSE_DEGREES = 270;
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();

    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);
    }

    public RecorderHelper(Context context) {
        mContext = context;
    }

    private void initIfNecessary() {
        if (null == mRecorder) {
            mRecorder = new MediaRecorder();
        }
    }

    private void updatePath() {
        final File dir = mContext.getExternalFilesDir(null);
        if (null != dir) {
            mPath = dir.getAbsolutePath() + "/" + System.currentTimeMillis() + ".mp4";
        }
    }

    public void configRecorder(int sensorOrientation, int displayRotation) {
        initIfNecessary();

        // 设置存储路径
        updatePath();
        if (TextUtils.isEmpty(mPath)) {
            return;
        }
        mRecorder.setOutputFile(mPath);

        // 设置音、视频采集源
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

        // 设置音、视频编码格式,以及文件封装格式。设置顺序必须跟下面一模一样,否则报错
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

        // 设置比特率、帧率和分辨率
        mRecorder.setVideoEncodingBitRate(800 * 800);
        mRecorder.setVideoFrameRate(30);
        // 这里只是展示用法,实际开发中需要根据摄像头的支持size来取
        mRecorder.setVideoSize(960, 720);

        // 根据camera方向和屏幕角度,设置录制视频的角度补偿
        if (SENSOR_DEFAULT_DEGREES == sensorOrientation) {
            mRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(displayRotation));
        } else if (SENSOR_INVERSE_DEGREES == sensorOrientation) {
            mRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(displayRotation));
        }

        try {
            mRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

        hasPrepared = true;
    }

    public void start() {
        if (hasPrepared) {
            mRecorder.start();
        }
    }

    // 停止之后,MediaRecorder不需要置空,下次使用时需要重新配置
    public void stop() {
        if (hasPrepared) {
            mRecorder.stop();
            mRecorder.reset();
            hasPrepared = false;
        }
    }

    public void release() {
        if (null != mRecorder) {
            mRecorder.release();
            hasPrepared = false;
            mRecorder = null;
        }
    }

    public Surface getSurface() {
        return hasPrepared ? mRecorder.getSurface() : null;
    }

    public boolean isRecording() {
        return hasPrepared;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Camera、Camera2详解 的相关文章

  • Android 通知进度条冻结

    这是我正在使用的代码 http pastebin com 3bMCKURu http pastebin com 3bMCKURu 问题是 一段时间后 文件变得更重 通知栏下拉速度变慢 最后它就冻结了 你的通知太频繁了 这就是它冻结的原因 让
  • 检测到设备正在振动?

    我使用下面的代码来振动设备 public void vibrator try Vibrator vibrator Vibrator getSystemService Context VIBRATOR SERVICE vibrator vib
  • Android - 如何一次只允许播放一个 MediaPlayer 实例?

    我正在尝试创建一个简单的 Sound board Android 应用程序 使用 ListView 项目作为按钮 顺便说一句 我是一个新手程序员 我的想法是 我按下一个按钮 就会播放一个特定的声音文件 如果我在播放声音时按下任何按钮 它应该
  • Android 应用程序在后台运行时保存数据

    目前我正在开发 xmmp 客户端 当应用程序位于前台时 该客户端工作得很好 但由于事实上 当应用程序处于后台时 我在 Application 类中保存了大量数据 复杂的 ArrayList 字符串和布尔值作为公共静态 每个字段都被垃圾收集
  • Android SoundPool 堆限制

    我正在使用 SoundPool 加载多个声音剪辑并播放它们 据我所知 它的功能 100 正确 但在 load 调用期间 我的日志中充斥着以下内容 06 09 11 30 26 110 ERROR AudioCache 23363 Heap
  • Firebase Analytics 禁用受众国家/地区跟踪

    我正在开发一个严格不允许位置跟踪的应用程序 我想使用 Firebase Analytic 的其他功能 例如 PageTransitions 和 Crashalitics 但如果我无法禁用受众位置跟踪 我就无法使用其中任何功能 这是我在 An
  • 如果我们使用后退按钮退出,为什么 Android 应用程序会重新启动?

    按住主页按钮并返回应用程序时 应用程序不会重新启动 为什么使用后退按钮会重新启动 如果我们使用后退按钮退出 有什么方法可以解决在不重新启动的情况下获取应用程序的问题吗 请帮忙 当您按下Home按钮 应用程序将暂停并保存当前状态 最后应用程序
  • 在 Android 中使用 DataOutputStream 在 POST 正文中发送特殊字符 (ë ä ï)

    我目前正在开发一个具有大量服务器端通信的 Android 应用程序 昨天 我收到一份错误报告 称用户无法发送 简单 特殊字符 例如 我搜索过但没有找到任何有用的东西 可能重复 没有答案 https stackoverflow com que
  • 应用程序未安装在 Android 模拟器上

    我正在 android Geocoder 中开发一个应用程序 当我运行该应用程序时 它会显示 2011 01 11 11 08 13 GeoTourProject 自动目标模式 使用现有模拟器 emulator 5554 运行兼容的 AVD
  • minHeight 有什么作用吗?

    在附图中 我希望按钮列与图像的高度相匹配 但我也希望按钮列有一个最小高度 它正确匹配图像的高度 但不遵守 minHeight 并且会使按钮向下滑动 我正在为按钮列设置这些属性
  • MediaCodec 创建输入表面

    我想使用 MediaCodec 将 Surface 编码为 H 264 使用 API 18 有一种方法可以通过调用 createInputSurface 然后在该表面上绘图来对表面中的内容进行编码 我在 createInputSurface
  • Android构建apk:控制MANIFEST.MF

    Android 构建 APK 假设一个 apk 包含一个库 jar 例如 foo jar 该库具有 META INF MANIFEST MF 这对于它的运行很重要 但在APK中有一个包含签名数据的MANIFEST MF 并且lib jar
  • 如何创建像谷歌位置历史记录一样的Android时间轴视图?

    我想设计像谷歌位置历史这样的用户界面 我必须为我正在使用的应用程序复制此 UIRecyclerView 每行都是水平的LinearLayout其中包含右侧的图标 线条和视图 该线是一个FrameLayout具有圆形背景和半透明圆圈Views
  • Android 设备上的静默安装

    我已经接受了一段时间了 在 Android 上静默安装应用程序是不可能的 也就是说 让程序安装捆绑为 APK 的应用程序 而不提供标准操作系统安装提示并完成应用程序安装程序活动 但现在我已经拿到了 Appbrain 快速网络安装程序的副本
  • 通过 ADB 拔出设备:“找不到服务”

    我必须测试我的应用程序在打瞌睡模式下的行为 根据文档 https developer android com training monitoring device state doze standby html testing doze 我
  • 当手机旋转(方向改变)时如何最好地重新创建标记/折线

    背景 开发一个使用 Android Google Map v2 的本机 Android 应用程序 使用android support v4 app FragmentActivity 在 Android v2 2 上运行 客观的 在更改手机方
  • Android 如何聚焦当前位置

    您好 我有一个 Android 应用程序 可以在谷歌地图上找到您的位置 但是当我启动该应用程序时 它从非洲开始 而不是在我当前的城市 国家 位置等 我已经在developer android com上检查了信息与位置问题有关 但问题仍然存在
  • android Accessibility-service 突然停止触发事件

    我有一个 AccessibilityService 工作正常 但由于开发过程中的某些原因它停止工作 我似乎找不到这个原因 请看一下我的代码并告诉我为什么它不起作用 public class MyServicee extends Access
  • 为什么Android的ImageReader类这么慢?

    我尝试了适用于 Android 3 4 1 的全新 OpenCVJavaCamera2View但它太慢了 仅显示相机视图约 15 fps 当我尝试较旧的JavaCameraView相反 它给了我很好的结果 30fps 这是我相机的极限 我想
  • 如何删除因 Google Fitness API 7.5.0 添加的权限

    将我的 play services fitness api 从 7 0 0 更新到 7 5 0 后 我注意到当我将新版本上传到 PlayStore 时 它 告诉我正在添加一个新权限和 2 个新功能 我没有这样做 有没有搞错 在做了一些研究来

随机推荐

  • java.net.SocketTimeoutException: Read timed out 问题解决

    问题描述 今天开发时发现 jdbc hive 连接执行 一个 hive sql 查询语句时 总是报org apache thrift transport TTransportException java net SocketTimeoutE
  • crmeb5.0修改会员价格展示条件

    api components 组件目录 components goodList index vue 商品展示组件 components productWindow index vue 产品属性组件 components shareRedPa
  • 软件系统架构有哪几种?

    互联网飞速发展的当下 有一种极其重要的门类也随之应运而生 那就是软件工程 而软件工程中 又有非常重要的一环 那就是软件架构 这也是各个互联网公司无论大小都必备的一个系统基础 那么什么是软件架构呢 事实上 架构在软件发明时的 N 多年以前 就
  • Java导入xml文件

    需求 前后端分离项目 后端Springboot框架 将学生信息通过xml文件格式导入 一个学生信息 以及该学生选择的学科 student xml文件格式如下 StudentController java PostMapping import
  • 逆向爬虫27 sojson反调加密

    逆向爬虫27 sojson反调加密 目标 掌握sojson的加密的特点和原理 使用静态文件替换sojson反调 一 sojson加密特点和原理 sojson是一种常用的js反调和加密手段 在学习如何处理它之前 我们需要先了解它的特点和原理
  • LightGBM参数介绍

    Xgboost和LightGBM部分参数对照 Xgboots LightGbm booster default gbtree boosting default gbdt eta default 0 3 learning rate defau
  • python 提示 keyError 的4种解决方法

    https blog csdn net u011089523 article details 72887163 在读取dict的key和value时 如果key不存在 就会触发KeyError错误 如 Python t a 1 b 2 c
  • SSD-Pytorch训练自己的VOC数据集&遇到的问题及解决办法

    SSD 训练 data init py data config py data voc0712 py layers modules multibox loss py ssd py train py 预训练文件vgg16 reducedfc
  • ‘settings.xml‘ has syntax errors 解决办法

    settings xml has syntax errors 解决办法 文章目录 settings xml has syntax errors 解决办法 参考链接 又是一个小知识点 pom xml中的
  • 基于pwntools编写pwn代码

    目录 预备知识 pwn pwntools 实验目的 实验环境 实验步骤一 1 Pwntools安装及模块 已装 2 常用模块详细介绍 实验步骤二 实验步骤三 预备知识 pwn Pwn 是一个黑客语法的俚语词 是指攻破设备或者系统 发音类似
  • 快速选择算法

    quick select 算法 LintCode 5 第k大元素 题目 在数组中找到第k大的元素 样例 给出数组 9 3 2 4 8 第三大的元素是 4 给出数组 1 2 3 4 5 第一大的元素是 5 第二大的元素是 4 第三大的元素是
  • Lingo软件的基本语法

    目录 基本语法 集合 数据 数据计算段 变量的初始化 模型的目标函数和约束条件 实时数据处理 注意 基本语法 集合 sets 集合名称1 成员列表1 属性1 1 属性1 2 属性1 n1 集合名称2 成员列表2 属性2 1 属性2 2 属性
  • 系统时间显示踩坑记录

    问题 签到前时间每秒变化显示 原始做法是 截取获取的系统时间的后九位 但是红米手机的系统时间最后两位不是秒 而是上下午的英文字母 导致小时截取不到 如图中间位置 希望实现如图左 方法 分别获取系统的时分秒 处理下不是两位的数字 然后拼接起来
  • 如何将文件重置或恢复到特定版本?

    问题描述 如何在特定的提交哈希 我通过 git log 和 git diff 确定 处将修改后的文件恢复到其先前的版本 解决方案1 一个优秀的自由职业者 应该有对需求敏感和精准需求捕获的能力 而huntsbot com提供了这个机会 假设您
  • LibCurl教程2

    http blog csdn net ljob2006 article details 4390612 2 1 LibCurl编程流程 在基于LibCurl的程序里 主要采用callback function 回调函数 的形式完成传输任务
  • 0401hive入门-hadoop-大数据学习.md

    文章目录 1 Hive概述 2 Hive部署 2 1 规划 2 2 安装软件 3 Hive体验 4 Hive客户端 4 1 HiveServer2 服务 4 2 DataGrip 5 问题集 5 1 Could not open clien
  • js踩坑 foreach中return不能跳出循环,for中才可以

    js踩坑 foreach中return不能跳出循环 for中才可以 如代码所示 const list 1 2 3 4 5 list forEach e gt if e 3 return console log e 结束 运行结果 通过上面的
  • iOS 关于UIWebView常见使用方法

    Step UIWebView 1 UIWebView常用方法 1 声明 property nonatomic strong UIWebView webView 1 代理 UIWebViewDelegate 2 ATS配置 info plis
  • html 字体形状,二十款漂亮的CSS字体样式

    样式一 body margin 0 padding 0 line height 1 5em font family Times New Roman Times serif font size 14px color 000000 backgr
  • Android Camera、Camera2详解

    前言 Android5 0之前使用android hardware包下的Camera类进行拍照 录视频等功能 5 0以后 新增了android hardware camera2包 利用新的机制 新的类进行拍照 录视频 使用Camera 一