Android之UVC调试
背景
前段时间在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
参考
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 DD'Notes!
评论