音视频学习

本文详细介绍了Camera2的升级过程,包括CameraManager的使用、CameraCharacteristics选择后置摄像头、MediaRecorder与CameraX的配置,以及MediaPlayer、SoundPool在音视频处理中的应用。CameraX的简化使用和依赖管理也做了深入讲解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

音视频学习

Camera2

Camera的学习与使用

  1. 通过Camera.open()创建Camera实例
  2. 再通过camera.unlock来进行释放锁,否则无法使用
  3. 然后通过MediaCorder.setCamera来进行设置使用

由于Camera已经过时了,谷歌如今推出的是Camera2以及CameraX

Camera2的学习与使用

  1. 通过context.getSystemService(Context.CAMERA_SERVICE) 获取CameraManager.

  2. 获取当前选中的CameraID

     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();
            }
        }
    
  3. 设置Handler来进行子线程操作

  4. 调用CameraManager .openCamera()方法参数为刚才获取的CameraID,回调CameraDevice.StateCallback()以及Handler,在回调中得到CameraDevice.

  5. 通过CameraDevice.createCaptureSession() 在回调中获取CameraCaptureSession.

  6. 通过Device调用createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) 构建PreviewCaptureRequest, 有三种模式可选 预览/拍照/录像.

  7. 通过 mCameraDevice发送createCaptureSession, capture表示只发一次请求, setRepeatingRequest表示不断发送请求.

在使用的过程中,Camera2需要进行权限声明式确认,createCaptureSession的第一个参数需要两个Surface,一个用于预览一个用于录制

MediaRecorder

  1. 首先声明权限

        <uses-permission android:name="android.permission.CAMERA"/>
        <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    
  2. 通过new MediaRecorder()来进行创建,获取MediaRecorder实例

  3. 通过setAudioSource设置音频来源

  4. 通过setVideoSource设置视频来源

  5. 通过setOutputFormat设置输出格式

  6. 通过setAudioEncoder设置音频编码格式,项目中需要考虑兼容问题,应该选择AAC

  7. 通过setVideoEncoder设置视频编码格式,项目中需要考虑兼容问题,应该选择H264

  8. 通过setVideoEncodingBitRate设置比特率 一般是 1分辨率 到 10分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。

  9. 通过setVideoFrameRate设置帧数,一般30即可

  10. 通过setVideoSize来进行宽度和高度设置,如果宽高不合适的话,会出现预览变形,视频播放失败等情况

    /通过CameraManager.getCameraCharacteristics(当前选中的CameraID)获取cameraCharacteristics对象,然后获取size
        private Size getMathSize() {
            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 deviceHeight = displayMetrics.heightPixels; //屏幕分辨率高
                /**
                 * 循环40次,让宽度范围从最小逐步增加,找到最符合屏幕宽度的分辨率,
                 * ,但是循环越大后获取的分辨率就越不匹配
                 */
                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(deviceHeight - itemSize.getWidth()) < Math.abs(deviceHeight - 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;
        }
    
  11. 通过setOrientationHint来进行录像旋转,否则录像预览是倒着的,录制的视频也是一样

  12. 通过setPreviewDisplay来进行录像预览设置

  13. 通过setOutputFile来设置输出文件位置

  14. 通过MediaRecorder.prepare来进行准备

  15. 通过MediaRecorder.Start来进行开始录制

  16. 通过MediaRecorder.Stop以及MediaRecorder.reset录制停止或者通过MediaRecorder.release进行资源释放

MediaPlayer

  1. 通过new MediaPlayer()进行MediaPlayer的实例创建

  2. 通过设置准备监听

    mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            mMediaPlayer.start();
        }
    });
    
  3. 设置视频结束设置

    mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                        @Override
                        public void onCompletion(MediaPlayer mediaPlayer) {
                            mMediaPlayer.stop();
                            mMediaPlayer.release();
                        }
                    });
    
  4. 通过setDataSource设置视频资源

  5. 通过setSurface设置播放的控件

  6. 通过prepareAsync来进行异步准备

SoundPool

  1. 通过new SoundPool.Builder().setMaxStreams(5).build()构建者模式创建实例

  2. 通过如下代码进行播放

    mSoundPool.play(sound.getSoundId(),0.5f,0.5f,1,0,1.0f);
    
  3. 在关闭时,通过SoundPool.unload以及SoundPool.release()来进行资源释放

CameraX

通过上面的Camera2的学习,可以看出Camera2的配置是比较复杂的,于是官方发布了CameraX来进行使用,简化用户操作

添加依赖

    // CameraX core library using the camera2 implementation
    def camerax_version = "1.0.1"
    // The following line is optional, as the core library is included indirectly by camera-camera2
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    // If you want to additionally use the CameraX Lifecycle library
    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
    // If you want to additionally use the CameraX View class
    implementation "androidx.camera:camera-view:1.0.0-alpha27"
    // If you want to additionally use the CameraX Extensions library
    implementation "androidx.camera:camera-extensions:1.0.0-alpha27"

添加权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

创建布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".CameraXFragment">

        <androidx.camera.view.PreviewView
            android:id="@+id/preview_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/btn_take_photo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="拍照"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

        <Button
            android:id="@+id/btn_take_video"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="录像"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/btn_jump"
            app:layout_constraintStart_toEndOf="@+id/btn_take_photo" />

        <Button
            android:id="@+id/btn_jump"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="跳转"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
   </layout>

创建预览界面

//可以绑定生命周期的ProcessCameraProvider,ProcessCameraProvider
        // 它会和宿主绑定生命周期,这样就不用担心打开相机和关闭的问题
        mProviderListenableFuture
                = ProcessCameraProvider.getInstance(requireContext());
        //注册监听,第一个参数是一个 runnable,第二个参数是线程池
        mProviderListenableFuture.addListener(() -> {
            try {
                // //将相机的生命周期和activity的生命周期绑定,camerax会自己释放,不用担心了
                mProcessCameraProvider = mProviderListenableFuture.get();
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            }
            //创建preview,支持角度换算
            mPreview = new Preview.Builder().build();
            //将Preview连接到 PreviewView,进行预览
            mPreview.setSurfaceProvider(mCameraXBinding.previewView.getSurfaceProvider());
            //创建图片的capture
            mImageCapture = new ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                    .setTargetResolution(new Size(1920, 1080))
                    .build();
            //创建录像的capture
            mVideoCapture = new VideoCapture.Builder()
                    .setTargetRotation(mCameraXBinding.previewView.getDisplay().getRotation())
                    .setVideoFrameRate(30)
                    .setBitRate(5 * 1024 * 1024)
                    .build();

            ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
            imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(requireContext()),
                    image -> image.close());
            //解绑
            mProcessCameraProvider.unbindAll();
            //将数据绑定到相机的生命周期中
            mCamera = mProcessCameraProvider.bindToLifecycle(requireActivity(),
                    CameraSelector.DEFAULT_BACK_CAMERA //选择后置摄像头
                    , mVideoCapture, mPreview);
            //点击聚焦
            mCameraXBinding.previewView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent motionEvent) {
                    float x = motionEvent.getX();
                    float y = motionEvent.getY();
                    FocusMeteringAction focusMeteringAction = new FocusMeteringAction.Builder(
                            mCameraXBinding.previewView.getMeteringPointFactory()
                                    .createPoint(x, y)).build();
                    mCamera.getCameraControl().startFocusAndMetering(focusMeteringAction);
                    return true;
                }
            });
        }, ContextCompat.getMainExecutor(requireContext()));

开始拍照

File file = new File(requireActivity().getExternalFilesDir("").getAbsoluteFile(), "camerax.jpg");
        if (file.exists()) {
            file.delete();
        }
        //创建图片文件的数据
        ImageCapture.OutputFileOptions outputFileOptions =
                new ImageCapture.OutputFileOptions.Builder(file).build();
        mProcessCameraProvider.bindToLifecycle(requireActivity(), CameraSelector.DEFAULT_BACK_CAMERA
                , mImageCapture);
        //开始拍照
        mImageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(requireActivity()),
                new ImageCapture.OnImageSavedCallback() {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        Uri savedUri = outputFileResults.getSavedUri();
                        if (savedUri == null) {
                            savedUri = Uri.fromFile(file);
                        }
                        Toast.makeText(requireActivity(), "????,???" + savedUri
                                , Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onError(@NonNull ImageCaptureException exception) {
                        Log.e("TAG", "onError: ");
                    }
                });

开始录像

File file = new File(requireActivity().getExternalFilesDir("").getAbsoluteFile(), "camerax.mp4");
        if (file.exists()) {
            file.delete();
        }
        //创建视频文件的数据,比如创建文件
        VideoCapture.OutputFileOptions fileOptions = new VideoCapture.OutputFileOptions
                .Builder(file).build();
        if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        //开始拍照,拍照之前需要进行权限确认
        mVideoCapture.startRecording(fileOptions, ContextCompat.getMainExecutor(requireActivity())
                , new VideoCapture.OnVideoSavedCallback() {
                    //保存录像
                    @Override
                    public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {
                        Toast.makeText(requireActivity(), "视频已保存"
                                + outputFileResults.getSavedUri().getPath(), Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
                        Log.e("TAG", "onError: ");
                        //视频不足一秒会走到这里来,但是视频依然生成了,所以得删掉
                        file.delete();
                    }
                });

停止录像

//判断视频的Capture是否为空
if (mVideoCapture != null){
            //停止录像
            mVideoCapture.stopRecording();
        }

完整代码地址

https://gitee.com/thereisnoif/media-recorder-demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

立花泷える宫水三叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值