【安卓相关】安卓外置USB相机开发 ( UVC )

近期在做物联网设备的开发,上位机是安卓一体机,有需要连接多个外置USB相机的录像和拍照的需要,记录一下,希望可以帮到有缘人。

首先你的安卓设备需要支持UVC协议 ( 一种为USB视频捕获设备定义的协议标准 )

如果需要使用多个摄像头的话,我这边的方案是使用 HUB 将多个USB摄像头连接起来,也就是这个。


连接多个摄像头后,如何区分各个摄像头呢,每一个USB device 有一个pid或者vid 参数,也就是产品id和版本id 吧。

android.hardware.usb.UsbDevice
如图所示:

image.png

这个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转换类。

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

推荐阅读更多精彩内容