关于android:Android音视频-CameraAudioRecord采集数据

前情提要

在上一篇文章中,放了一张音视频学习路线图,前面我会尽量依照这个路线图进行博客更新,首先咱们来谈一谈输出,在Android上的输出源无非是本地已有的音视频文件或者通过音视频设施(相机 录音)采集的原生数据。本章咱们来剖析如何在Android上通过Camera以及录音设施采集数据。

Camera

在Android上的视频采集设施无疑就是Camera了,在Android SDK API21之前的版本只能应用Camera1 ,在 API 21之后Camera1曾经被标记为Deprecated ,Google 举荐应用Camera2,上面咱们来别离看一下

Camera1

咱们先来看一下Camera1体系的局部类图

Camera类是Camera1体系的外围类,该类还有好多外部类,关上Camera、设置参数、设置预览等都是应用该类的API,上面咱们来看应用Camera API关上零碎照相机的流程。


有了下面的流程图之后,咱们上代码

//启动Camera
private fun setUpCamera() {
        //【1】 获取Camera Id
        val id = getCurrentCameraId()
        try {
            //【2】 依据Camera Id 获取Camera 实例
            cameraInstance = getCameraInstance(id)
        } catch (e: IllegalAccessError) {
            Log.e(TAG, "Camera not found")
            return
        }
        //【3】 调用Camera API 设置Camera参数
        val parameters = cameraInstance!!.parameters

        if (parameters.supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
        }
        cameraInstance!!.parameters = parameters
        //【4】  调用Camera API 设置预览Surface
        surfaceHolder?.let { cameraInstance!!.setPreviewDisplay(it) }
          //【5】 调用Camera API设置预览回调
        cameraInstance!!.setPreviewCallback { data, camera ->
            if (data == null || camera == null) {
                return@setPreviewCallback
            }
            val size = camera.parameters.previewSize
            onPreviewFrame?.invoke(data, size.width, size.height)
        }
        //【6】 调用Camera API开启预览
        cameraInstance!!.startPreview()
    }

下面代码中的【3】【4】【5】【6】都是调用Camera类的API来实现,咱们来看【1】【2】的具体步骤

/**
*获取Camera Id
*/
 private fun getCurrentCameraId(): Int {
        val cameraInfo = Camera.CameraInfo()
        //遍历所有的Camera id,比拟CameraInfo facing 
        for (id in 0 until Camera.getNumberOfCameras()) {
            Camera.getCameraInfo(id, cameraInfo)
            if (cameraInfo.facing == cameraFacing) {
                return id
            }
        }
        return 0
    }

/**
*获取Camera 实例
*/
 private fun getCameraInstance(id: Int): Camera {
      return try {
          //调用Camera的open函数获取Camera的实例
          Camera.open(id)
      } catch (e: Exception) {
          throw IllegalAccessError("Camera not found")
      }
  }

/**
*开释Camera
*/
  private fun releaseCamera() {
      cameraInstance!!.setPreviewCallback(null)
      cameraInstance!!.release()
      cameraInstance = null
  }

通过下面的流程之后,Camera的预览会显示在传入的Surface上,并且在Camera进行前会始终回调函数onPreviewFrame(byte[] data,Camera camera),其中byte[] data中存储的就是实时的YUV图像数据,对于YUV图像数据咱们在Android音视频开篇的时候就曾经说过了,咱们晓得YUV也有很多格局,byte[] data的格局是YUV格局中的NV21

Camera2

在Andorid SDK API 21之后呢,Google就举荐应用Camera2体系来治理设施,Camera2还是与Camera1有很大的不同的。
一样的,咱们先来看一下Camera2体系的局部类图

Camera2要比Camera1简单的多,CameraManager CameraCaptureSession是Camera2体系的外围类,CameraManager 用来治理摄像头的关上和敞开 Camera2 引入了CameraCaptureSession来治理拍摄会话。

咱们上面来看一下更具体的流程图


同样的上代码:

      private fun setUpCamera() {
        //【1】获取Camera Id     
        val cameraId = getCameraId(cameraFacing) ?: return
        try {
            //【2】关上Camera,传入的 CameraDeviceCallback()是摄像机设施状态回调
            cameraManager.openCamera(cameraId, CameraDeviceCallback(), null)
        } catch (e: CameraAccessException) {
            Log.e(TAG, "Opening camera (ID: $cameraId) failed.")
        }
    }
    //设施状态回调
    private inner class CameraDeviceCallback : CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
            cameraInstance = camera
            //【3】开启拍摄会话
            startCaptureSession()
        }

        override fun onDisconnected(camera: CameraDevice) {
            camera.close()
            cameraInstance = null
        }

        override fun onError(camera: CameraDevice, error: Int) {
            camera.close()
            cameraInstance = null
        }
    }

PS
ImageRender能够间接拜访出现在Surface上得图像数据,ImageRender的工作原理是创立实例并设置回调,这个回调会在ImageRender所关联的Surface上的图像可用时调用

 //【3】开启拍摄会话
    private fun startCaptureSession() {
        val size = chooseOptimalSize()
        //创立ImageRender并设置回调
        imageReader =
                ImageReader.newInstance(size.width, size.height, ImageFormat.YUV_420_888, 2).apply {
                    setOnImageAvailableListener({ reader ->
                        val image = reader?.acquireNextImage() ?: return@setOnImageAvailableListener
                        onPreviewFrame?.invoke(image.generateNV21Data(), image.width, image.height)
                        image.close()
                    }, null)
                }

        try {
            if (surfaceHolder == null) {
                //设置ImageRender的surface给cameraInstance,以便前面预览的时候数据出现到ImageRender的surface,从而触发ImageRender的回调
                cameraInstance?.createCaptureSession(
                        listOf(imageReader!!.surface),
                        //【4】CaptureStateCallback是CameraCaptureSession的外部类,是摄像机会话状态的回调
                        CaptureStateCallback(),
                        null
                )
            } else {
                cameraInstance?.createCaptureSession(
                        listOf(imageReader!!.surface,
                                surfaceHolder!!.surface),
                        CaptureStateCallback(),
                        null
                )
            }

        } catch (e: CameraAccessException) {
            Log.e(TAG, "Failed to start camera session")
        }
    }

    //摄像机会话状态的回调
    private inner class CaptureStateCallback : CameraCaptureSession.StateCallback() {
        override fun onConfigureFailed(session: CameraCaptureSession) {
            Log.e(TAG, "Failed to configure capture session.")
        }
        //摄像机配置实现
        override fun onConfigured(session: CameraCaptureSession) {
            cameraInstance ?: return
            captureSession = session
            //设置预览CaptureRequest.Builder
            val builder = cameraInstance!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            builder.addTarget(imageReader!!.surface)
            surfaceHolder?.let {
                builder.addTarget(it.surface)
            }

            try {
                //开启会话
                session.setRepeatingRequest(builder.build(), null, null)
            } catch (e: CameraAccessException) {
                Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e)
            } catch (e: IllegalStateException) {
                Log.e(TAG, "Failed to start camera preview.", e)
            }
        }
    }
    /**
    *【1】 获取Camera Id
    */
    private fun getCameraId(facing: Int): String? {
        return cameraManager.cameraIdList.find { id ->
            cameraManager.getCameraCharacteristics(id).get(CameraCharacteristics.LENS_FACING) == facing
        }
    }

咱们剖析了下面的Camera采集数据,残缺的代码请看文末的Github 地址

AudioRecord

下面剖析完了视频,咱们接着来看音频,录音API咱们应用AudioRecord,录音的流程绝对于视频而言要简略许多,一样的,咱们先来看一下简略类图

就一个类,API也简单明了,咱们来看一下流程

上面上代码

     public void startRecord() {
     //开启录音
        mAudioRecord.startRecording();
        mIsRecording = true;
        //开启新线程轮询
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                byte[] buffer = new byte[DEFAULT_BUFFER_SIZE_IN_BYTES];
                while (mIsRecording) {
                    int len = mAudioRecord.read(buffer, 0, DEFAULT_BUFFER_SIZE_IN_BYTES);
                    if (len > 0) {
                        byte[] data = new byte[len];
                        System.arraycopy(buffer, 0, data, 0, len);
                        //解决data
                    }
                }
            }
        });

    }


    public void stopRecord() {
        mIsRecording = false;
        mAACMediaCodecEncoder.stopEncoder();
        mAudioRecord.stop();
    }

AudioRecord生成的 byte[] data即PCM音频数据

小结

本章咱们对音视频的原生输出API进行了具体的介绍,这个也是咱们前面博客的根底,有了YUV和PCM数据之后,就能够编码了,下一篇咱们再来剖析MediaCodec,用MediaCodec对原生音视频数据进行硬编码生成Mp4.

Camera
AudioRecord

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理