如何采集UVC Camera数据
早在2015年,我们在做Android平台RTMP直播推送模块的时候,就有用到UVC摄像头采集,比如移动单兵或采集执法场景,除了Android系统自带的摄像头外,设备端还需要支持UVC外接摄像头。先说什么是UVC吧?实际上,UVC全称为USB Video Class,即:USB视频类,是一种为USB视频捕获设备定义的协议标准。是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已成为USB org标准之一。
Android平台实现UVC采集,无需重复造轮子,可以参考市面上比较成熟的采集方案,比如libuvccamera之类,非常成熟完善。以下是利用libuvccamera获取 NV12数据的步骤:
我们是先把libuvccamera相关库进行ndk编译,然后把相关java文件或资源打包成aar,然后到demo工程加载:
初始化和打开摄像头
public void initCameraHelper() {
Toast.makeText(this, "initCameraHelper", Toast.LENGTH_SHORT).show();
if (mCameraHelper == null) {
mCameraHelper = new CameraHelper();
mCameraHelper.setStateCallback(mStateListener);
}
}
设置视频格式预览和获取数据帧
private final ICameraHelper.StateCallback mStateListener = new ICameraHelper.StateCallback() {
@Override
public void onAttach(UsbDevice device) {
selectDevice(device);
}
@Override
public void onDeviceOpen(UsbDevice device, boolean isFirstOpen) {
mCameraHelper.openCamera();
}
@Override
public void onCameraOpen(UsbDevice device) {
mCameraHelper.startPreview();
mCameraHelper.addSurface(mCameraViewMain.getHolder().getSurface(), false);
}
@Override
public void onCameraClose(UsbDevice device) {
if (mCameraHelper != null) {
mCameraHelper.removeSurface(mCameraViewMain.getHolder().getSurface());
}
clearFPS();
}
@Override
public void onDeviceClose(UsbDevice device) {
}
@Override
public void onDetach(UsbDevice device) {
}
@Override
public void onCancel(UsbDevice device) {
}
};
如果需要对接RTMP推送、GB28181设备接入模块或轻量级RTSP服务,只要在数据回调的地方,把数据投递出去即可;
mCameraHelper.setFrameCallback(frame -> {
...
int w = size.width, h = size.height;
int y_stride = size.width, uv_stride = size.width;
int y_offset = 0, uv_offset = size.width * size.height;
int is_vertical_flip = 0, is_horizontal_flip = 0;
int rotation_degree = 0;
int scale_w = 0, scale_h = 0, scale_filter_mode = 0;
for (LibPublisherWrapper i : publisher_array_)
{
i.PostLayerImageNV21ByteBuffer(0, 0, 0,
frame, y_offset, y_stride, frame, uv_offset, uv_stride, w, h,
is_vertical_flip, is_horizontal_flip, scale_w, scale_h, scale_filter_mode, rotation_degree);
}
}, UVCCamera.PIXEL_FORMAT_NV21);
如何对接GB28181模块
对大牛直播SDK来说,只要数据源有了,任督二脉也就打通了,数据可以实时录制到MP4文件,可以推送到RTMP,可以对接到轻量级RTSP服务,当然也可以对接到GB28181设备接入模块。
这里,我们对接大牛直播SDK的SmartGBD GB28181设备接入模块为例,Android终端除支持常规的音视频数据接入外,还可以支持移动设备位置(MobilePosition)订阅和通知、语音广播和语音对讲、云台控制回调和预置位查询,支持对接数据类型如下:
- 编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型);
- 编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);
- 拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。
UVC摄像头对接,无非就是回调NV12、NV21或YUV420SP数据,通过调用相关的数据投递接口,实现UVC数据的投递即可。
UVC摄像头插入,APP链接的时候,会有权限提醒:
启动GB28181,国标平台侧发起实时回传请求后,UVC设备侧采集到数据,编码打包并实时传输到平台。
功能支持
- [视频格式]H.264/H.265(Android H.265硬编码);
- [音频格式]G.711 A律、AAC;
- [音量调节]Android平台采集端支持实时音量调节;
- [H.264硬编码]支持H.264特定机型硬编码;
- [H.265硬编码]支持H.265特定机型硬编码;
- [软硬编码参数配置]支持gop间隔、帧率、bit-rate设置;
- [软编码参数配置]支持软编码profile、软编码速度、可变码率设置;
- 支持纯视频、音视频PS打包传输;
- 支持RTP OVER UDP和RTP OVER TCP被动模式;
- 支持信令通道网络传输协议TCP/UDP设置;
- 支持注册、注销,支持注册刷新及注册有效期设置;
- 支持设备目录查询应答;
- 支持心跳机制,支持心跳间隔、心跳检测次数设置;
- 支持移动设备位置(MobilePosition)订阅和通知;
- 适用国家标准:GB/T 28181—2016;
- 支持语音广播;
- 支持语音对讲;
- 支持图像抓拍;
- 支持历史视音频文件检索;
- 支持历史视音频文件下载;
- 支持历史视音频文件回放;
- 支持云台控制和预置位查询;
- [实时水印]支持动态文字水印、png水印;
- [镜像]Android平台支持前置摄像头实时镜像功能;
- [实时静音]支持实时静音/取消静音;
- [实时快照]支持实时快照;
- [降噪]支持环境音、手机干扰等引起的噪音降噪处理、自动增益、VAD检测;
- [外部编码前视频数据对接]支持YUV数据对接;
- [外部编码前音频数据对接]支持PCM对接;
- [外部编码后视频数据对接]支持外部H.264数据对接;
- [外部编码后音频数据对接]外部AAC数据对接;
- [扩展录像功能]支持和录像SDK组合使用,录像相关功能。
系统要求
- SDK支持Android 5.1及以上版本;
- 支持的CPU架构:armv7, arm64, x86, x86_64。
准备工作
- 确保SmartPublisherJniV2.java放到com.daniulive.smartpublisher包名下(可在其他包名下调用);
- 如需集成语音广播、语音对讲功能,确保SmartPlayerJniV2.java放到com.daniulive.smartplayer包名下(可在其他包名下调用);
- smartavengine.jar和smartgbsipagent.jar加入到工程;
- 拷贝libSmartPublisher.so和libSmartPlayer.so(如需语音广播或语音对讲)到工程;
- AndroidManifast.xml添加相关权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" ></uses-permission>
<uses-permission android:name="android.permission.INTERNET" ></uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
- Load相关so:
static {
System.loadLibrary("SmartPublisher");
System.loadLibrary("SmartPlayer");
}
- build.gradle配置32/64位库:
splits {
abi {
enable true
reset()
// Specifies a list of ABIs that Gradle should create APKs for
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' //select ABIs to build APKs for
// Specify that we do not want to also generate a universal APK that includes all ABIs
universalApk true
}
}
- 如需集成到自己系统测试,请用大牛直播SDK的app name,授权版按照授权app name正常使用即可;
- 如何改app-name,strings.xml做以下修改:
<string name="app_name">SmartPublisherSDKDemo</string>
接口详解
以Android平台Camera2对接为例,信令部分需要实现如下标红接口:
public class MainActivity extends Activity implements ViewTreeObserver.OnGlobalLayoutListener, Camera2Listener,
GBSIPAgentListener, GBSIPAgentPlayListener, GBSIPAgentAudioBroadcastListener,
GBSIPAgentDeviceControlListener, GBSIPAgentQueryCommandListener,
GBSIPAgentTalkListener,
GBSIPAgentQueryRecordInfoListener{
}
媒体数据处理接口,可参照SmartPublisherJniV2.java,如需语音广播或语音对讲,可参照SmartPlayerJniV2.java。
信令处理
GBSIPAgentListener主要系GB28181注册、心跳、DevicePosition等,如注册成功、注册超时、注册网络传输层错误、心跳异常、设备位置请求处理:
public interface GBSIPAgentListener
{
/*注册成功
* @param dateString: 服务器日期,用来校准设备端时间,用户自行决定是否校准设备时间
*/
void ntsRegisterOK(String dateString);
/*
*注册超时
*/
void ntsRegisterTimeout();
/*
*注册网络传输层异常
*/
void ntsRegisterTransportError(String errorInfo);
/*
*心跳达到异常次数
*/
void ntsOnHeartBeatException(int exceptionCount, String lastExceptionInfo);
/*
* 设备位置请求, 这个主要用在移动设备位置订阅上
* @param interval 请求间隔, 单位是毫秒
*/
void ntsOnDevicePositionRequest(String deviceId, int interval);
}
GBSIPAgentPlayListener主要系GB28181的Invite、Ack、Bye等处理:
public interface GBSIPAgentPlayListener {
/*
*收到s=Play的实时视音频点播
*/
void ntsOnInvitePlay(String deviceId, SessionDescription sessionDescription);
/*
*发送play invite response 异常
*/
void ntsOnPlayInviteResponseException(String deviceId, int statusCode, String errorInfo);
/*
* 收到CANCEL play INVITE请求
*/
void ntsOnCancelPlay(String deviceId);
/*
* 收到Ack
*/
void ntsOnAckPlay(String deviceId);
/*
* 收到Bye
*/
void ntsOnByePlay(String deviceId);
/*
* 不是在收到BYE Message情况下, 终止Play
*/
void ntsOnTerminatePlay(String deviceId);
/*
* Play会话对应的对话终止, 一般不会出发这个回调,目前只有在响应了200K, 但在64*T1时间后还没收到ACK,才可能会出发
收到这个, 请做相关清理处理
*/
void ntsOnPlayDialogTerminated(String deviceId);
}
GBSIPAgentAudioBroadcastListener主要系GB28181语音广播处理相关,如有语音广播相关需求,可参照demo实例实现:
public interface GBSIPAgentAudioBroadcastListener {
/*
*收到语音广播通知
*/
void ntsOnNotifyBroadcastCommand(String fromUserName, String fromUserNameAtDomain, String sn, String sourceID, String targetID);
/*
*需要准备接受语音广播的SDP内容
*/
void ntsOnAudioBroadcast(String commandFromUserName, String commandFromUserNameAtDomain, String sourceID, String targetID);
/*
*音频广播, 发送Invite请求异常
*/
void ntsOnInviteAudioBroadcastException(String sourceID, String targetID, String errorInfo);
/*
*音频广播, 等待Invite响应超时
*/
void ntsOnInviteAudioBroadcastTimeout(String sourceID, String targetID);
/*
*音频广播, 收到Invite消息最终响应
*/
void ntsOnInviteAudioBroadcastResponse(String sourceID, String targetID, int statusCode, SessionDescription sessionDescription);
/*
* 音频广播, 收到BYE Message
*/
void ntsOnByeAudioBroadcast(String sourceID, String targetID);
/*
* 不是在收到BYE Message情况下, 终止音频广播
*/
void ntsOnTerminateAudioBroadcast(String sourceID, String targetID);
}
GBSIPAgentDeviceControlListener主要系GB28181设备控制相关,比如远程启动、云台控制:
public interface GBSIPAgentDeviceControlListener {
/*
* 收到远程启动控制命令
*/
void ntsOnDeviceControlTeleBootCommand(String deviceId, String teleBootValue);
/*
* 云台控制
*/
void ntsOnDeviceControlPTZCmd(String deviceId, String typeValue);
}
GBSIPAgentQueryCommandListener主要系GB28181查询命令,如预置位查询:
public interface GBSIPAgentQueryCommandListener {
/*
* 设备预置位查询
*/
void ntsOnDevicePresetQueryCommand(String fromUserName, String fromUserNameAtDomain, String sn, String deviceId);
}
总结
如果需要对接Android平台下的UVC摄像头,对我们来说,其实没有多少工作量,主要是稳定高效的拿到原始的YUV或NV21|NV12数据,我们来做软硬编码,打包即可,数据源拿到后,不管是录像还是对接RTMP、RTSP服务或GB28181都非常方便,以上是大概的流程,感兴趣的开发者,可以单独跟我沟通探讨。