Android webrtc使用USB摄像头

公司的Android项目要求支持外置摄像头,即要求支持USB摄像头;一脸懵逼的我从来没听过Android设备能支持USB摄像头的,只知道Android大机器能接外置的摄像头,但插口是接在Android机器上的前置和后置接口,也就是说可以通过Android自带的Camera类或Camera2类中API直接调用的;然而项目要的是在包含前置和后置摄像头之后,还要有USB摄像头,懵逼的我立马上网找这方面的资料,找到了UVCCamera和openCV,但下了openCV的demo之后并没有任何关于USB摄像头的玩意,转战UVCCamera;了解了UVCCamera之后,还要实现通过UVCCamera取流并给到webrtc

注:阅读本篇需要简单了解webrtc如何通过**Capturer类产生视频流并转化

一、UVCCamera下拉与编译

1、UVCCamra下拉

UVCCamera的github地址为:https://github.com/saki4510t/UVCCamera.git

2、UVCCamera编译和demo测试

首先,下载了UVCCamera并且通过AS打开之后,你一定非常的迫不及待的想要看看USB摄像头使用起来是怎么样的;但问题却来了

2.1、一直在sync不好

原因:网络问题

解决:将distributionUrl地址改成本机已有的版本,或者通过浏览器下载并放到系统android/.gradle文件夹中,具体文件夹地址可另外自行百度存放

 2.2、Process 'command '.......\Android\sdk\ndk-bundle/ndk-build.cmd'' finished with non-zero exit value 2

原因:ndk版本不兼容,这项目感觉有点老,作者又不更新,无奈自己能力也没那么强,也不会改,那么只能将ndk版本降低,降低到ndk r16b或者ndk r14b,下载地址为:https://developer.android.google.cn/ndk/downloads/older_releases;无奈这个官网地址上已无法下载太旧的ndk了,那么只能选择从AS中获取,好在AS最低有支持到ndk16的,要是以后AS也把ndk16给过滤掉,那我也暂时不知道怎么办了

下载好ndk之后,将UVCCamera的NDK版本改成16的这个:

2.3、无法出画面

若出现所有的test demo都能够打开,但无法出现画面,Logcat日志有:[2495*UVCCamera.cpp:172:connect]:could not open camera:err=-1。之类的,则将libuvccamera/src/main/jni/Application.mk中的

NDK_TOOLCHAIN_VERSION := 4.9

注释打开

2.4、运行调试过程当中出现[5050*UVCPreview.cpp:507:prepare_preview]:could not negotiate with camera:err=-51

可能是uvcCamera.setPreviewSize的预览格式不正确,更改预览格式

二、VideoCapturer的子类

在使用webrtc视频通话时,其中有一步是需要通过Camera1Capturer或Camera2Capturer来打开手机摄像头,并调用startCapturer方法开始录制并返回本地流数据,先来看下VideoCapturer和Camera1Capturer/Camera2Capturer的关系

可以看到Camera1Capturer和Camera2Capturer的曾祖父类是VideoCapturer类,该类是一个接口,定义了一些方法

可以看到,该类的方法依次为初始化、开始录像、暂停录像、改变录像规格(分辨率)、释放和返回是否截屏的方法,那么从这里可以知道,子类Camera1Capturer(以下都只以Camera1Capturer做讨论)也会继承这些方法,那么我们研究的重点在于初始化和开始录像,接着我们看CameraVideoCapturer类做了什么;CameraVideoCapturer这个类当中有两个过时的方法(不讨论),还有一个静态内部类CameraStatistics,直接翻译一下就是相机统计,通过源码得知,该类只是开了个线程,通过surfaceTextureHelper对象每两秒执行一次,判断相机是否可用,并且记录帧的数量。

2.1、CameraCapturer

看完CameraVideoCapturer之后,发现该类并没有对VideoCapturer接口中的方法进行实现,那么进而看子类CameraCapturer,首先是初始化方法initialze

该方法很简单,只是对上层传入的参数进行复制,但值得我们留意的是capturerObserver对象,根据webrtc视频通话流程,我们知道有个VideoSource对象,在创建VideoCapturer时候需要将videoSource.capturerObserver对象传进来;接着再看startCapture方法

可以看到startCapture方法当中调用了createSessionInternal方法,该方法又执行了createCameraSession方法,跟踪发现,createCameraSession是一个抽象的方法,而抽象方法被实现的地方正是Camera1Capturer类。

2.2、Camera1Session相机会话

该类主要实现对相机的开启和采集

通过源码得知,实例化Camera1Session之后就立马打开了相机,并且调用的是camera.startPreview方法,该方法是开启预览的方法,预览的返回接口则执行了listenForTextureFrames方法

到这里逐渐清晰,原来Camera开启预览,产生的帧数据是可以被采集到的,并且通过events.onFrameCaptured将帧数据传递出去;webrtc压根不是通过录像采集相机的流,而只是简单的开启预览就行;在此可能有人就会问,events是什么东西,别急,看Camera1Session的创建流程

而在前文我们看到的createSessionInternal方法中执行的createCameraSession方法传入的第二个参数,到这里我们可以知道,events应该是个接口或者是个抽象类,这样才能把采集到的数据传递到上层

而在CameraCapturer类当中,执行createCameraSession方法时传进的就是Events的一个实现对象,我们主要看onFrameCaptured实现

到此,我们知道最终的帧数据通过初始化initilaze方法传入的capturerObserver对象回调给videoSource对象,接着就是webrtc更底层的操作,最终得到流并传给对方或者本地显示。

三、UsbCapturer

分析完原有webrtc如何采集数据之后,我们可以发现关键点;第一、采集数据是直接通过startPreview就可以,那么UVCCamera也能对Usb摄像头进行打开预览关闭预览等操作;第二、采集到的数据最终通过videoSource.capturerObserver对象传出即可;

public class UsbCapturer implements VideoCapturer, USBMonitor.OnDeviceConnectListener, IFrameCallback { private static final String TAG = "UsbCapturer"; private USBMonitor monitor; private SurfaceViewRenderer svVideoRender; private CapturerObserver capturerObserver; private int mFps; private UVCCamera mCamera; private final Object mSync = new Object(); private boolean isRegister; private USBMonitor.UsbControlBlock ctrlBlock; public UsbCapturer(Context context, SurfaceViewRenderer svVideoRender) { this.svVideoRender = svVideoRender; monitor = new USBMonitor(context, this); } @Override public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context context, CapturerObserver capturerObserver) { this.capturerObserver = capturerObserver; } @Override public void startCapture(int width, int height, int fps) { this.mFps = fps; if (!isRegister) { isRegister = true; monitor.register(); } else if (ctrlBlock != null) { startPreview(); } } @Override public void stopCapture() { if (mCamera != null) { mCamera.destroy(); mCamera = null; } } @Override public void changeCaptureFormat(int i, int i1, int i2) { } @Override public void dispose() { monitor.unregister(); monitor.destroy(); monitor = null; } @Override public boolean isScreencast() { return false; } @Override public void onAttach(UsbDevice device) { LogKt.Loges(TAG, "onAttach:"); monitor.requestPermission(device); } @Override public void onDettach(UsbDevice device) { LogKt.Loges(TAG, "onDettach:"); if (mCamera != null) { mCamera.close(); } } @Override public void onConnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) { LogKt.Loges(TAG, "onConnect:"); UsbCapturer.this.ctrlBlock = ctrlBlock; startPreview(); } @Override public void onDisconnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock) { LogKt.Loges(TAG, "onDisconnect:"); if (mCamera != null) { mCamera.close(); } } @Override public void onCancel(UsbDevice device) { LogKt.Loges(TAG, "onCancel:"); } private ReentrantLock imageArrayLock = new ReentrantLock(); @Override public void onFrame(ByteBuffer frame) { if (frame != null) { imageArrayLock.lock(); byte[] imageArray = new byte[frame.remaining()]; frame.get(imageArray); //关键 long imageTime = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime()); VideoFrame.Buffer mNV21Buffer = new NV21Buffer(imageArray , UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT , null); VideoFrame mVideoFrame = new VideoFrame(mNV21Buffer, 0, imageTime); capturerObserver.onFrameCaptured(mVideoFrame); mVideoFrame.release(); imageArrayLock.unlock(); } } public USBMonitor getMonitor() { return this.monitor; } private void startPreview() { synchronized (mSync) { if (mCamera != null) { mCamera.destroy(); } } UVCCamera camera = new UVCCamera(); camera.setAutoFocus(true); camera.setAutoWhiteBlance(true); try { camera.open(ctrlBlock);// camera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.PIXEL_FORMAT_RAW); camera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, FpsType.FPS_15, mFps, UVCCamera.PIXEL_FORMAT_RAW, 1.0f); } catch (Exception e) { try {// camera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, UVCCamera.DEFAULT_PREVIEW_MODE); camera.setPreviewSize(UVCCamera.DEFAULT_PREVIEW_WIDTH, UVCCamera.DEFAULT_PREVIEW_HEIGHT, FpsType.FPS_15, mFps, UVCCamera.DEFAULT_PREVIEW_MODE, 1.0f); } catch (Exception e1) { camera.destroy(); camera = null; } } if (camera != null) { if (svVideoRender != null) { camera.setPreviewDisplay(svVideoRender.getHolder().getSurface()); } camera.setFrameCallback(UsbCapturer.this, UVCCamera.PIXEL_FORMAT_YUV420SP); camera.startPreview(); } synchronized (mSync) { mCamera = camera; } } public void setSvVideoRender(YQRTCSurfaceViewRenderer svVideoRender) { this.svVideoRender = svVideoRender; }}

注:使用UsbCapturer需要传入预览View。

后序:其实一开始我分析完上述的流程之后,并不知道该如何着手设计UsbCapturer,直到在github上看到这个之后https://github.com/sutogan4ik/android-webrtc-usb-camera,才一下子恍然大悟,非常感谢这位大佬,虽然他的代码在我项目中运行无效,但修改一下之后也就完美了,另外当中里面使用的方法 capturerObserver.onByteBufferFrameCaptured在新版的webrtc中已经不存在了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容