Android:WebRtc实现多人音视频(下)

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天,点击查看活动详情

上一篇(Android:WebRtc实现多人音视频(中))中我们完成了基于WebRtc的多人音视频通讯,今天就来实现基于USB摄像头下的视频通信。

这里我们先提出两个问题:

  • 使用USB摄像头需要解决设备如何识别它,并拿到它采集到的数据?
  • 拿到数据后如何将数据接入到WebRtc中?

通过百度谷歌,发现了一个三方库AndroidUSBCamera,该库底层好像还是日本人写的。运行其demo能够正常跑起来,USB摄像头也能识别,至今该库都还在维护,所以使用起来也不是特别担心。

1676511080388.jpg

通过该库源码发现,它也提供了一个方法可以读取摄像头的实时数据:

override fun onFrame(frame: ByteBuffer) {
   ......
}

数据来源问题解决了,接下来我们再看看WebRtc如何接入外部视频数据呢?

在之前的音视频配置中我们不难发现,WebRtc是使用的Camera1Capturer或Camera2Capturer打开的手机摄像头。

private fun createVideoCapture(): VideoCapturer? {
    return if (Camera2Enumerator.isSupported(mContext)) {
            createCameraCapture(Camera2Enumerator(mContext))
        } else {
            createCameraCapture(Camera1Enumerator(true))
        }
    }
}
//Camera1Enumerator和Camera2Enumerator是CameraEnumerator的实现类
public interface CameraEnumerator {
    String[] getDeviceNames();

    boolean isFrontFacing(String var1);

    boolean isBackFacing(String var1);

    List<CaptureFormat> getSupportedFormats(String var1);

    CameraVideoCapturer createCapturer(String var1, CameraEventsHandler var2);
}

 //Camera1Enumerator和Camera2Enumerator的实现方法
 public CameraVideoCapturer createCapturer(String deviceName, CameraEventsHandler eventsHandler) {
        return new Camera1Capturer(deviceName, eventsHandler, this.captureToTexture);
    }

而Camera1Capturer和Camera2Capturer的父类CameraCapturer有一个initialize初始化方法入参了一个CapturerObserver,它是一个接口,发现内部方法如下:

public interface CapturerObserver {
    void onCapturerStarted(boolean var1);

    void onCapturerStopped();

    void onFrameCaptured(VideoFrame var1);
}

很明显这三个回调分别是开始采集、停止采集、采集中的回调数据,这不就是我们需要的入口吗。Webrtc中onFrameCaptured是在底层处理,我们暂时不用管。现在只需要将USB数据采集并放入onFrameCaptured中就完成该功能。

这里我们自定义一个类将采集和写入的功能都放在里面:

class UsbCapturer(private var svVideoRender: SurfaceViewRenderer?) :
    VideoCapturer, IFrameCallback {
    //WebRtc视频数据源需要的接口
    private var capturerObserver: CapturerObserver? = null
    //视频帧
    private var mFps = 0
    //USB摄像头
    private var mCamera: UVCCamera? = null

    override fun initialize(
        surfaceTextureHelper: SurfaceTextureHelper,
        context: Context,
        capturerObserver: CapturerObserver
    ) {
        this.capturerObserver = capturerObserver
    }
    //预览数据回调
    override fun onFrame(frame: ByteBuffer) {
    }
    ```
    //开启USB摄像头预览
    fun startPreview(camera: UVCCamera?, ctrlBlock: UsbControlBlock?) {
    }
}

预览数据还需要进行转化才为CapturerObserver认识的数据才行,好在AndroidUSBCamera库中提供了对应的方法:

val imageArray = ByteArray(frame.remaining())
frame[imageArray] //关键
val imageTime = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime())
val mNV21Buffer: VideoFrame.Buffer = NV21Buffer(
    imageArray,
    UVCCamera.DEFAULT_PREVIEW_WIDTH,
    UVCCamera.DEFAULT_PREVIEW_HEIGHT,
    null
)
val mVideoFrame = VideoFrame(mNV21Buffer, 0, imageTime)
capturerObserver?.onFrameCaptured(mVideoFrame)

至于USB摄像头的初始化和数据采集demo中都有,这里不做赘述。

最后还需要在选择摄像头的地方加入USB摄像头的选择。因为项目需求是使用USB摄像头优先,所以我们需要判断是否连接了USB,这里使用的是判断USB连接设备数量进行判断:

private fun createVideoCapture(): VideoCapturer? {
    //优先使用usb摄像头  通过UsbManager获取usb设备数量进行判断
    return if ((mContext.getSystemService(SupportActivity.USB_SERVICE) as UsbManager).deviceList.size > 0) {
        localRenderer = SurfaceViewRenderer(mContext)
        UsbCapturer(localRenderer)
    } else {
        if (Camera2Enumerator.isSupported(mContext)) {
            createCameraCapture(Camera2Enumerator(mContext))
        } else {
            createCameraCapture(Camera1Enumerator(true))
        }
    }
}

另外USB摄像头预览的UsbControlBlock参数是USBMonitor初始化成功后的产物,而USBMonitor的初始化需要入参上下文对象,所以我们将这个过程在Activity。当USB摄像头连接成功,WebRtc初始化成功需要预览本地视频时则调用UsbCapturer.startPreview(),这样就达到了WebRtc使用USB摄像头进行音视频的全部功能。

总结

其实重点其一在USB摄像头采集数据,并将采集数据转化为WebRtc能够接受的数据类型后传入WebRtc对应接口。其二是阅读源码和分析能力,找到WebRtc是怎么获取手机摄像头数据,再根据对应接口自定义实现类,移花接木将我们需要的数据对接过去。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天,点击查看活动详情