背景

前段时间在Android机器上调试了UVC摄像头,最近又调试了HDMI转USB模块,它本质上也是虚拟成了一个UVC,和UVC的处理是一样的
相关东西记录下

UVC预览及与本地摄像头动态切换测试代码:

package com.example.myapplication;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PixelFormat;


import android.hardware.Camera;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MainActivity extends Activity{

    private Button btnCamera=null;
    private Button button1,button2,button3=null;
    private SurfaceView mySurfaceView =null;
    private Camera cam=null;
    private SurfaceHolder holder=null;
    private boolean previewRunning=false;
    private static final int ONE = 0;// 默认摄像头
    private static final int TWO = 1;
    private static final int THREE = 2;
    private static final int FOUR = 3;

    private USBReceiver mUsbReceiver;


    private int mBackKeyPressTimes = 0;
    private Handler mMainHandler;
    private int cameraCount;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //全屏显示预览
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_main);

        //Toast.makeText(MainActivity.this,"onCreate",Toast.LENGTH_SHORT).show();
 /*       if (Build.VERSION.SDK_INT >= 23) {
            int REQUEST_CODE_CONTACT = 101;
            String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.CAMERA};
            for (String str : permissions) {
                if (this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
                    this.requestPermissions(permissions, REQUEST_CODE_CONTACT);
                    return;
                }
            }
        }*/

        btnCamera= (Button) findViewById(R.id.btn_camera);
        button1= (Button) findViewById(R.id.button1);
        button2= (Button) findViewById(R.id.button2);
        button3= (Button) findViewById(R.id.button3);
        setOnClickListener();


        mMainHandler=new Handler();

        mySurfaceView= (SurfaceView) findViewById(R.id.mySurfaceView);
        holder=this.mySurfaceView.getHolder();
        holder.addCallback(new MySurfaceViewCallback());
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        holder.setFixedSize(800, 480);

        this.registerReceiver();
    }

    private UsbDevice getUsbCameraDevice(Context context) {
        UsbManager usbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE);
        for(UsbDevice usbDevice : usbManager.getDeviceList().values()) {

            Log.i("camera","\n"+usbDevice.toString());
            if (isUsbCamera(usbDevice)) {
                return usbDevice;
            }
        }
        return null;
    }
    private boolean isUsbCamera(UsbDevice usbDevice) {
        return usbDevice != null && 239 == usbDevice.getDeviceClass() && 2 == usbDevice.getDeviceSubclass();
    }

    private class USBReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 这里可以拿到插入的USB设备对象
            UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            switch (intent.getAction()) {
                case UsbManager.ACTION_USB_DEVICE_ATTACHED: // 插入USB设备
                    Log.i("camera","USB: 插入");
                    //if(getUsbCameraDevice(context) != null){
                    if (isUsbCamera(usbDevice)) {
                        new Handler().postDelayed(new Runnable(){
                            public void run() {
                                if(getCameraNumAndView() > 1) {
                                    try {
                                        changeCamera(TWO);
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }, 2000);
                    }
                    break;
                case UsbManager.ACTION_USB_DEVICE_DETACHED: // 拔出USB设备
                    Log.i("camera","USB: 拔出");
                    new Handler().postDelayed(new Runnable(){
                        public void run() {
                            if(getCameraNumAndView() > 0){
                                try {
                                    changeCamera(ONE);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }, 500);

                    break;
                default:
                    break;
            }
        }
    }

    private void registerReceiver() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        mUsbReceiver = new USBReceiver();
        registerReceiver(mUsbReceiver, filter);
    }

    private void unregisterUsbReceiver(){
        unregisterReceiver(mUsbReceiver);
    }

    private void setOnClickListener() {
        //绑定监听
        btnCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            if(cam!=null){
                cam.takePicture(null, null, jpegcall);
            }

            Toast.makeText(MainActivity.this,"btnCamera",Toast.LENGTH_SHORT).show();
            }
        });
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            try {
                changeCamera(ONE);
                Toast.makeText(MainActivity.this,"button1",Toast.LENGTH_SHORT).show();
            } catch (IOException e) {
                e.printStackTrace();
            }
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    changeCamera(TWO);
                    Toast.makeText(MainActivity.this,"button2",Toast.LENGTH_SHORT).show();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    changeCamera(THREE);
                    Toast.makeText(MainActivity.this,"button3",Toast.LENGTH_SHORT).show();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private int getCameraNumAndView() {
        //获取摄像头的数量 返回为int  根据摄像头数量显示与隐藏按钮数量
        int cameraCount = Camera.getNumberOfCameras();
        Log.i("camera","bcameraCount:"+cameraCount);
        switch (cameraCount) {
            case 0:
                //finish();
                Toast.makeText(this,"摄像头未找到",Toast.LENGTH_SHORT).show();
                break;
            case 1:
                button1.setVisibility(View.VISIBLE);
                button2.setVisibility(View.GONE);
                button3.setVisibility(View.GONE);
                break;
            case 2:
                button1.setVisibility(View.VISIBLE);
                button2.setVisibility(View.VISIBLE);
                button3.setVisibility(View.GONE);

                break;
            case 3:
                button1.setVisibility(View.VISIBLE);
                button2.setVisibility(View.VISIBLE);
                button3.setVisibility(View.VISIBLE);
                break;
        }

        return cameraCount;
    }

    //运行时判断当前摄像头为几个 当前写死了三个摄像头button,根据数量隐藏与显示按钮。
    //也可以动态添加button。
    @Override
    protected void onResume() {
        super.onResume();
        getCameraNumAndView();
    }

    //根据点击按钮 打开相应的摄像头
    private void changeCamera(int type) throws IOException{
        if(cam != null){
            if(previewRunning)
                cam.setPreviewCallback(null);
            cam.stopPreview();
            cam.lock();
            cam.release();
            cam = null;
        }
        if(type == ONE){
            cam = openCamera(ONE);
        }else if(type == TWO){
            cam = openCamera(TWO);
        }else if(type == THREE){
            cam = openCamera(THREE);
        }

        if(cam != null)
        {
            Camera.Parameters param=cam.getParameters();
            // param.setPreviewSize(display.getWidth(), display.getHeight());
            param.setPreviewFrameRate(20);//
            param.setPictureFormat(PixelFormat.JPEG);
            param.set("jpeg-quality", 100);
            param.setJpegQuality(100);
            //param.setPictureSize(1920,1080);
            param.setPreviewSize(1280,720);
            cam.setParameters(param);
            cam.setPreviewDisplay(holder);
            cam.startPreview();
        }

    }


    @SuppressLint("NewApi")
    private Camera openCamera(int type){
        if(type == ONE ){
            return Camera.open(0);
        }else if(type == TWO ){
            return Camera.open(1);


        }else if(type == THREE ){
            return Camera.open(2);
        }
        return null;
    }


    //预览时需要实现的接口,
    private class MySurfaceViewCallback implements SurfaceHolder.Callback {
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                   int height) {
        }

        //默认打开摄像头0
        @Override
        public void surfaceCreated(SurfaceHolder holder){
            try {
                cam = Camera.open(0); //取第一个摄像头
            }catch (Exception e){
                //e.printStackTrace();
                finish();
            }
            // WindowManager manager=(WindowManager)MainActivity.this.getSystemService(Context.WINDOW_SERVICE);
            //  Display display = manager.getDefaultDisplay();
            try{
                Camera.Parameters param=cam.getParameters();
                // param.setPreviewSize(display.getWidth(), display.getHeight());
                param.setPreviewFrameRate(5);//一秒5帧
                param.setPictureFormat(PixelFormat.JPEG);
                param.set("jpeg-quality", 80);
                cam.setParameters(param);
                cam.setPreviewDisplay(holder);
                cam.startPreview();//预览
            }catch(Exception e){
                //e.printStackTrace();
                finish();
                Toast.makeText(MainActivity.this,"未识别到摄像头",Toast.LENGTH_SHORT).show();
            }
            previewRunning=true;
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if(cam!=null){
                if(previewRunning){
                    cam.stopPreview();
                    previewRunning=false;
                }
                cam.release();//释放摄像头
            }
        }
    }

    private Camera.PictureCallback jpegcall=new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            // 保存图片操作
            Bitmap bmp= BitmapFactory.decodeByteArray(data, 0, data.length);
            String fileName=  "/storage/emulated/0/Pictures/"+new SimpleDateFormat
                    ("yyyyMMddHHmmss").format(new Date())+".jpg";

            File file=new File(fileName);
            if(!file.getParentFile().exists()){
                file.getParentFile().mkdir();//创建文件夹
            }
            try {
                BufferedOutputStream bos=new BufferedOutputStream
                        (new FileOutputStream(file));
                bmp.compress(Bitmap.CompressFormat.JPEG, 80, bos);//向缓冲区压缩图片
                bos.flush();
                bos.close();
                Toast.makeText(MainActivity.this, getResources().getString
                        (R.string.takephone)+fileName+getResources().getString
                        (R.string.intheFile),Toast.LENGTH_LONG).show();
            } catch (Exception e) {
                //e.printStackTrace();
                Toast.makeText(MainActivity.this, R.string.Picturesfailed+e.toString(),
                        Toast.LENGTH_LONG).show();
            }
            cam.stopPreview();
            cam.startPreview();
        }
    };

    //按两次退出app,中间间隔是2秒内
    @Override
    public void onBackPressed() {
        if (mBackKeyPressTimes == 0) {
            Toast.makeText(this, R.string.back_to_exit_msg, Toast.LENGTH_SHORT).show();
            mBackKeyPressTimes++;
            mMainHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mBackKeyPressTimes = 0;
                }
            }, 2000);
            return;
        }
        if (mBackKeyPressTimes >= 1)
            super.onBackPressed();
    }

}

扩展

  • USB接口传输速率
    USB2.0的理论传输速度为480 Mbps,即60 MB/s, 实际为30 MB/s左右
    USB3.0的理论传输速度为5Gbps,即500 MB/s, 实际为100 MB/s左右

  • (1080P)P的含义:
    progressive scan,逐行扫描的意思

  • YUV420采样之后的一帧数据大小
    每个Y/U/V的采样点用8bit来表示,即1Bytes.

    #720p:
    1280 * 720 * 8 *(1 + 1/4 + 1/4) / 8 
    = 1382400 Bytes
    = 1350 KB
    
    #1080p:
    1920 * 1080 * 8 *(1 + 1/4 + 1/4) / 8 
    = 3110400 Bytes
    =  3037.5 KB
  • 所以如果用USB2.0的UVC采集高分辨率、高帧率的原始视频数据(YUV)是不太现实的,USB是瓶颈,要么用USB3.0接口的,要么视频流进行压缩后再通过USB传过来,常见的比如MJPG、H264

参考