开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天,点击查看活动详情
上一篇(Android:WebRtc实现多人音视频(中))中我们完成了基于WebRtc的多人音视频通讯,今天就来实现基于USB摄像头下的视频通信。
这里我们先提出两个问题:
- 使用USB摄像头需要解决设备如何识别它,并拿到它采集到的数据?
- 拿到数据后如何将数据接入到WebRtc中?
通过百度谷歌,发现了一个三方库AndroidUSBCamera,该库底层好像还是日本人写的。运行其demo能够正常跑起来,USB摄像头也能识别,至今该库都还在维护,所以使用起来也不是特别担心。
通过该库源码发现,它也提供了一个方法可以读取摄像头的实时数据:
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 天,点击查看活动详情