近期在做物联网设备的开发,上位机是安卓一体机,有需要连接多个外置USB相机的录像和拍照的需要,记录一下,希望可以帮到有缘人。
首先你的安卓设备需要支持UVC协议 ( 一种为USB视频捕获设备定义的协议标准 )
如果需要使用多个摄像头的话,我这边的方案是使用 HUB 将多个USB摄像头连接起来,也就是这个。
连接多个摄像头后,如何区分各个摄像头呢,每一个USB device 有一个pid或者vid 参数,也就是产品id和版本id 吧。
android.hardware.usb.UsbDevice
如图所示:
这个PID可以找摄像头厂家定制,或者找厂家给你烧录工具和烧录文件,自己去烧录,这样使用的时候按照PID进行安装。
现在开始部署吧
首先我使用的是这个库
https://github.com/jiangdongguo/AndroidUSBCamera
build.gradle
allprojects {
repositories {
...
maven { url 'http://raw.github.com/saki4510t/libcommon/master/repository/' }
maven { url 'https://jitpack.io' }
}
}
saki4510t 构建的时候 如果出现 这个错误的话
Could not resolve com.serenegiant:common:2.12.4
可以改成 liuchaoya 这个试试
maven { url 'https://gitee.com/liuchaoya/libcommon/raw/master/repository/' }
接下来是 dependencies
implementation 'com.github.jiangdongguo:AndroidUSBCamera:2.3.1'
使用
我这里写一个基于该库,使用uvc相机来录制视频的demo吧。
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".UVCCameraActivity">
<com.serenegiant.usb.widget.UVCCameraTextureView
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="center"/>
<EditText
android:id="@+id/cameraNumber"
android:text="1"
android:inputType="number"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:onClick="StartAndStop"
android:text="录制 / 停止"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Activity
import android.hardware.usb.UsbDevice;
import android.os.Bundle;
import android.os.Environment;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.fenfeneco.recycle.util.AndroidDeviceSDK;
import com.fenfeneco.recycle.util.UvcUtil;
import com.jiangdg.usbcamera.UVCCameraHelper;
import com.jiangdg.usbcamera.utils.FileUtils;
import com.serenegiant.usb.common.AbstractUVCCameraHandler;
import com.serenegiant.usb.encoder.RecordParams;
import com.serenegiant.usb.widget.CameraViewInterface;
import com.serenegiant.usb.widget.UVCCameraTextureView;
import java.io.File;
public class UVCCameraActivity extends AppCompatActivity{
public static boolean isRequest;
public static boolean isPreview;
private UVCCameraHelper mCameraHelper;
private UVCCameraTextureView uvcCameraTextureView;
private final static String TAG = "UVC相机";
private EditText cameraNumber;
int nowCamera = 4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_uvc_camera);
AndroidDeviceSDK.hideStatus(this,false);
cameraNumber = findViewById(R.id.cameraNumber);
cameraNumber.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if(!TextUtils.isEmpty(s)){
int number = Integer.parseInt(s.toString());
// 切换摄像头
mCameraHelper.closeCamera();
requestCamera(number);
}
}
});
uvcCameraTextureView = findViewById(R.id.textureView);
CameraViewInterface mUVCCameraView = (CameraViewInterface) uvcCameraTextureView;
// 设置 UVC 预览界面回调
mUVCCameraView.setCallback(new CameraViewInterface.Callback() {
@Override
public void onSurfaceCreated(CameraViewInterface view, Surface surface) {
Log.i(TAG,"画面创建");
}
@Override
public void onSurfaceChanged(CameraViewInterface view, Surface surface, int width, int height) {
Log.i(TAG,"画面改变");
}
@Override
public void onSurfaceDestroy(CameraViewInterface view, Surface surface) {
Log.i(TAG,"画面销毁");
}
});
mCameraHelper = UVCCameraHelper.getInstance();
mCameraHelper.setDefaultPreviewSize(640,480);
// 设置默认帧格式,默认格式为 UVCCameraHelper.Frame_FORMAT_MPEG
// 如果 Frame_FORMAT_MPEG 模式不能使用,可以尝试 YUYV
// mCameraHelper.setDefaultFrameFormat(UVCCameraHelper.FRAME_FORMAT_YUYV);
mCameraHelper.initUSBMonitor(this, mUVCCameraView, new UVCCameraHelper.OnMyDevConnectListener() {
@Override
public void onAttachDev(UsbDevice device) {
// 插拔的时候会触发
Log.i(TAG,"插拔扫描到的设备 (ID) :" + device.getProductId());
}
@Override
public void onDettachDev(UsbDevice device) {
// 关闭相机
if (isRequest) {
isRequest = false;
mCameraHelper.closeCamera();
// USB 相机被拔出
Log.i(TAG,"被拔出");
}
}
@Override
public void onConnectDev(UsbDevice device, boolean isConnected) {
Log.i(TAG,"连接成功,结果:" + isConnected);
if (!isConnected) {
// 连接失败,检测连接参数
isPreview = false;
} else {
isPreview = true;
// 连接成功
// 等待 UVC 初始化成功
}
}
@Override
public void onDisConnectDev(UsbDevice device) {
// 设备被断开
Log.i(TAG,"设备断开");
}
});
mCameraHelper.registerUSB();
// 寻找第一个可用的USB相机
requestCamera(nowCamera);
}
private void requestCamera(int index){
// 寻找第一个可用的USB相机
for(int i = 0 ; i < mCameraHelper.getUsbDeviceList().size();i++){
Log.i(TAG,"扫描到摄像头" + mCameraHelper.getUsbDeviceList().get(i).getProductId());
if(mCameraHelper.getUsbDeviceList().get(i).getProductId() == UvcUtil.doorNumberToPid(index)){
Log.i(TAG,"找到了第一个相机");
mCameraHelper.requestPermission(i);
return;
}
}
}
// 开始或停止
public void StartAndStop(View view){
// 如果没有开始录制
if (!mCameraHelper.isPushing()) {
String videoPath = Environment.getExternalStorageDirectory() + File.separator + System.currentTimeMillis() + UVCCameraHelper.SUFFIX_MP4;
// 录制的一些参数
RecordParams params = new RecordParams();
params.setRecordPath(videoPath);
params.setRecordDuration(0);
params.setVoiceClose(true);
// 开始录制
mCameraHelper.startPusher(params, new AbstractUVCCameraHandler.OnEncodeResultListener() {
@Override
public void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) {
// type = 1,h264 video stream
// 视频类型
if (type == 1) {
FileUtils.putFileStream(data, offset, length);
}
// type = 0,aac audio stream
// 音乐类型
if(type == 0) {
// FileUtils.createfile(FileUtils.ROOT_PATH + "test666.h264");
}
}
@Override
public void onRecordResult(String videoPath) {
if(TextUtils.isEmpty(videoPath)) {
return;
}
Toast.makeText(UVCCameraActivity.this,"录制结束,视频地址" + videoPath,Toast.LENGTH_LONG ).show();
}
});
// 如果只想推送视频流
// mCameraHelper.startPusher(listener);
Toast.makeText(this, "开始录制", Toast.LENGTH_SHORT).show();
} else {
FileUtils.releaseFile();
mCameraHelper.stopPusher();
Toast.makeText(this, "停止录制", Toast.LENGTH_SHORT).show();
}
}
}
缺少 UvcUtil 这个类大家不用管,只是根据我自身业务写的一个序号和pid转换类。