转自:http://blog.csdn.net/eternity9255/article/details/53069037

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[-]

    前言
底层配置
打开配置
添加权限
Debug
几个比较有用的调试命令
上层应用
操作流程
具体代码实现
解码mjpeg格式
jni层 - 插入huffman表
jave层 - 解码并显示
总结 . 前言 前段时间调试了一个uvc摄像头,这里做下记录。硬件平台为mt6735,软件平台为Android 5.0
. 底层配置 UVC全称是usb video class,一种usb视频规范。所有遵循uvc协议的摄像头都不需要安装额外的驱动,只需要一个通用驱动即可。Linux内核已经集成了uvc驱动,代码路径是kernel-3.10/drivers/media/usb/uvc/
2.1 打开配置 linux内核需要打开以下配置来支持uvc设备 CONFIG_MEDIA_SUPPORT=y
CONFIG_MEDIA_CAMERA_SUPPORT=y
CONFIG_VIDEO_DEV=y
CONFIG_VIDEO_V4L2=y
CONFIG_VIDEOBUF2_CORE=y
CONFIG_VIDEOBUF2_MEMOPS=y
CONFIG_VIDEOBUF2_VMALLOC=y
CONFIG_MEDIA_USB_SUPPORT=y
CONFIG_USB_VIDEO_CLASS=y MTK平台还需要额外打开otg配置 CONFIG_USB_MTK_OTG=y
CONFIG_USB_MTK_HDRC=y
CONFIG_USB_MTK_HDRC_HCD=y 插入摄像头,如果生成了/dev/video0设备节点,则证明uvc摄像头已经加载成功了。成功生成驱动节点后还需要为它添加权限
2.2 添加权限 在uevent.rc中加入 /dev/video0 root root 在system_app.te中加入 allow system_app video_device:chr_file { read write open getattr }; 2.3 Debug 如果没有出现/dev/video0节点,需要先判断是否枚举成功。在shell终端cat相关的节点查询 cat /sys/kernel/debug/usb/devices 如果该摄像头枚举成功,则能找到对应的设备信息 T: Bus= Lev= Prnt= Port= Cnt= Dev#= Spd= MxCh=
D: Ver=2.00 Cls=(>ifc) Sub= Prot= MxPS= #Cfgs=
P: Vendor=18EC ProdID= Rev=0.00
S: Manufacturer=ARKMICRO
S: Product=USB PC CAMERA 如果枚举成功则需要判断当前的usb摄像头是不是遵循uvc协议的摄像头。将usb摄像头插到PC上(ubuntu操作系统),通过”lsusb”命令查找是否有视频类接口信息 lsusb -d 18ec: -v | grep "14 Video" 如果该摄像头遵循UVC协议,则会输出以下类似信息 bFunctionClass Video
bInterfaceClass Video
bInterfaceClass Video
bInterfaceClass Video 其中18ec:3399是摄像头的vid和pid,而14 video代表uvc规范
2.4 几个比较有用的调试命令 打开/关闭linux uvc driver log echo 0xffff > /sys/module/uvcvideo/parameters/trace //打开
echo > /sys/module/uvcvideo/parameters/trace //关闭 获取详细的usb设备描述符 lsusb -d 18ec: –v . 上层应用 v4l2 - Video for Linux ,是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。同时是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。 MTK标准的Camera并没有采用v4l2框架,所以需要在jni层实现基本的v4l2视频采集流程。
3.1 操作流程 在v4l2编程中,一般使用ioctl函数来对设备进行操作: extern int ioctl (int __fd, unsigned long int __request, …) __THROW; __fd:设备的ID,例如用open函数打开/dev/video0后返回的cameraFd;
__request:具体的命令标志符。
在进行V4L2开发中,一般会用到以下的命令标志符:
VIDIOC_REQBUFS:分配内存
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP:查询驱动功能
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
VIDIOC_S_FMT:设置当前驱动的视频格式
VIDIOC_G_FMT:读取当前驱动的视频格式
VIDIOC_TRY_FMT:验证当前驱动的视频格式
VIDIOC_CROPCAP:查询驱动的修剪能力
VIDIOC_S_CROP:设置视频信号的边框
VIDIOC_G_CROP:读取视频信号的边框
VIDIOC_QBUF:把数据放回缓存队列
VIDIOC_DQBUF:把数据从缓存中读取出来
VIDIOC_STREAMON:开始视频采集
VIDIOC_STREAMOFF:结束视频采集
VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。
这些IO调用,有些是必须的,有些是可选择的。 在网上有开源的应用simplewebcam,它已经实现了基本的v4l2视频采集流程。大概看下它是怎么做的 操作流程 v4l2
3.2 具体代码实现 () 打开设备驱动节点 int opendevice(int i)
{
struct stat st; sprintf(dev_name,"/dev/video%d",i); if (- == stat (dev_name, &st)) {
LOGE("Cannot identify '%s': %d, %s", dev_name, errno, strerror (errno));
return ERROR_LOCAL;
} if (!S_ISCHR (st.st_mode)) {
LOGE("%s is no device", dev_name);
return ERROR_LOCAL;
} fd = open (dev_name, O_RDWR); if (- == fd) {
LOGE("Cannot open '%s': %d, %s", dev_name, errno, strerror (errno));
return ERROR_LOCAL;
}
return SUCCESS_LOCAL;
} () 查询驱动功能 int initdevice(void)
{
struct v4l2_capability cap;
struct v4l2_format fmt;
unsigned int min; if (- == xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
if (EINVAL == errno) {
LOGE("%s is no V4L2 device", dev_name);
return ERROR_LOCAL;
} else {
return errnoexit ("VIDIOC_QUERYCAP");
}
} if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
LOGE("%s is no video capture device", dev_name);
return ERROR_LOCAL;
} if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
LOGE("%s does not support streaming i/o", dev_name);
return ERROR_LOCAL;
} ...... } () 设置视频格式 int initdevice(void)
{
struct v4l2_capability cap;
struct v4l2_format fmt; ...... CLEAR (fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = IMG_WIDTH;
fmt.fmt.pix.height = IMG_HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; if (- == xioctl (fd, VIDIOC_S_FMT, &fmt))
return errnoexit ("VIDIOC_S_FMT"); ......
} () 申请帧缓存并映射到用户空间 int initmmap(void)
{
struct v4l2_requestbuffers req; CLEAR (req);
req.count = ;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; if (- == xioctl (fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno) {
LOGE("%s does not support memory mapping", dev_name);
return ERROR_LOCAL;
} else {
return errnoexit ("VIDIOC_REQBUFS");
}
} if (req.count < ) {
LOGE("Insufficient buffer memory on %s", dev_name);
return ERROR_LOCAL;
} buffers = calloc (req.count, sizeof (*buffers)); if (!buffers) {
LOGE("Out of memory");
return ERROR_LOCAL;
} for (n_buffers = ; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf; CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers; if (- == xioctl (fd, VIDIOC_QUERYBUF, &buf))
return errnoexit ("VIDIOC_QUERYBUF"); buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap (NULL ,
buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, buf.m.offset); if (MAP_FAILED == buffers[n_buffers].start)
return errnoexit ("mmap");
} return SUCCESS_LOCAL;
} () 将帧缓存加入缓存队列并启动视频采集 int startcapturing(void)
{
unsigned int i;
struct v4l2_buffer buf;
enum v4l2_buf_type type; for (i = ; i < n_buffers; ++i) {
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i; if (- == xioctl (fd, VIDIOC_QBUF, &buf))
return errnoexit ("VIDIOC_QBUF");
} type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (- == xioctl (fd, VIDIOC_STREAMON, &type))
return errnoexit ("VIDIOC_STREAMON"); return SUCCESS_LOCAL;
} () 从缓存队列中取出一帧 int readframeonce(void)
{
for (;;) {
fd_set fds;
struct timeval tv;
int r; FD_ZERO (&fds);
FD_SET (fd, &fds); tv.tv_sec = ;
tv.tv_usec = ; r = select (fd + , &fds, NULL, NULL, &tv); if (- == r) {
if (EINTR == errno)
continue; return errnoexit ("select");
} if ( == r) {
LOGE("select timeout");
return ERROR_LOCAL; } if (readframe ()==)
break; } return realImageSize; } int readframe(void)
{
struct v4l2_buffer buf;
unsigned int i; CLEAR (buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP; if (- == xioctl (fd, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
return ;
case EIO:
default:
return errnoexit ("VIDIOC_DQBUF");
}
} assert (buf.index < n_buffers); convert2JPEG(buffers[buf.index].start, buf.bytesused); if (- == xioctl (fd, VIDIOC_QBUF, &buf))
return errnoexit ("VIDIOC_QBUF"); return ;
} . 解码mjpeg格式 我所使用的usb摄像头是mjpeg格式,而从网上下载的simplewebcam应用只支持yuyv格式,所以需要重写解码模块。
4.1 jni层 - 插入huffman表 安卓自带的libjpeg解码库只能解码jpeg格式。而mjpeg格式需要在v4l2读出的帧中找到SOF0(Start Of Frame ),插入huffman表后就可以用libjpeg库解码成rgb。 static int convert2JPEG(const void *p, int size)
{
char *mjpgBuf = NULL; if (pImageBuf == NULL) {
return errnoexit("pImageBuf isn't initialized in JNI");
} /* Clear pImageBuf and realImageSize */
memset(pImageBuf, , (IMG_WIDTH*IMG_HEIGHT)*);
realImageSize = ; /* insert dht data to p, and then save them to pImageBuf */
realImageSize = insert_huffman(p, size, pImageBuf); return SUCCESS_LOCAL;
} static int insert_huffman(const void *in_buf, int buf_size, void *out_buf)
{
int pos = ;
int size_start = ;
char *pcur = (char *)in_buf;
char *pdeb = (char *)in_buf;
char *plimit = (char *)in_buf + buf_size;
char *jpeg_buf = (char *)out_buf; /* find the SOF0(Start Of Frame 0) of JPEG */
while ( (((pcur[] << ) | pcur[]) != 0xffc0) && (pcur < plimit) ){
pcur++;
} LOGD("pcur: 0x%x, plimit: 0x%x", pcur, plimit); /* SOF0 of JPEG exist */
if (pcur < plimit){
if (jpeg_buf != NULL)
{
/* insert huffman table after SOF0 */
size_start = pcur - pdeb;
memcpy(jpeg_buf, in_buf, size_start);
pos += size_start;
memcpy(jpeg_buf + pos, dht_data, sizeof(dht_data));
pos += sizeof(dht_data);
memcpy(jpeg_buf + pos, pcur, buf_size - size_start);
pos += buf_size - size_start;
return pos;
}
} else{
LOGE("SOF0 does not exist");
}
return ;
} const static unsigned char dht_data[] = {
0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x03,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05,
0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04,
0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22,
0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15,
0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95,
0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5,
0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
0xfa, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
}; 第28-31行,找到SOF0所在的位置,并让pcur指向它
第39-47行,在SOF0所在的位置之后插入huffman表,也就是dht_data数组。可被libjpeg解码的图像最终保存在pImageBuf中
4.2 jave层 - 解码并显示 jni层把图像保存在pImageBuf,这个buffer对应Java层的mImageBuffer。Jave层获取到图像之后调用BitmapFactory.decodeByteArray进行解码,并通过Canvas显示图像 @Override
public void run() {
while (true && cameraExists) { ...... imageSize = processCamera();
if(imageSize == - || imageSize == )
continue; bmp = BitmapFactory.decodeByteArray(mImageBuffer.array(), mImageBuffer.arrayOffset(), imageSize);
if(bmp == null)
continue; Canvas canvas = getHolder().lockCanvas();
if (canvas != null)
{
// draw camera bmp on canvas
canvas.drawBitmap(bmp,null,rect,null);
getHolder().unlockCanvasAndPost(canvas);
}
}
} . 总结 底层配置,只需要使能otg功能并把uvc相关的配置宏打开,插入设备后生成了/dev/videoX设备节点则说明usb摄像头枚举并初始化成功了 上层应用,采用网上的开源应用simplewebcam,这个应用只支持yuyv格式,所以需要重写解码模块。需要在数据帧中手动插入huffman表之后,才能用android的libjpeg库来解码mjpeg格式 另外,在调试过程中出现了”uvcvideo: Non-zero status (-) in video completion handler”这样的log,那是因为mt6735平台的usb host controller对iso端点的支持不太好,经常出现丢包现象,这个问题需要打上mtk提供的patch才能解决问题

Android USB Camera(1) : 调试记录【转】的更多相关文章

  1. [未完] Linux 4.4 USB —— spiflash模拟usb大容量存储设备 调试记录 Gadget Mass Stroage

    linux 4.4 USB Gadget Mass Stroage 硬件平台: licheepi nano衍生 调试记录 驱动信息 │ This driver is a replacement for ...

  2. S3c6410 平台 Android系统的Wi-Fi调试记录

    硬件平台:S3c6410 操作系统:Android 网卡芯片:GH381(SDIO接口 sdio8688) 1.SDIO驱动 因为是SDIO接口,所以请先保证mmc驱动(代码在“kernel\driv ...

  3. usb鼠标制作调试记录

    2010-07-26 20:07:00 制作调试过程 1,串口通信硬件设计.焊接了串口通信电路实验.由于我的usb转串口线是不能配max232的.而是要配一个反向器.于是自己焊接了74ls00.并且把 ...

  4. Android Usb Camera HAL框架

  5. android4.0 USB Camera实例(三)UVC

    前面我写了两篇文章说明了zc301的实现 详细请看 http://blog.csdn.net/hclydao/article/details/21235919 以下顺便把通用的USB也写上 前面的ZC ...

  6. [安卓][转]Android eclipse中程序调试

    一:断点调试 用eclipse开发android程序的时,跟VS一样是可以断点单步调试的.步骤如下.1 设置断点:在编码窗体的左边框上用鼠标双击,或者右键点击菜单,选择 Toggle Breakpoi ...

  7. Android eclipse中程序调试

    一:断点调试 用eclipse开发android程序的时,跟VS一样是可以断点单步调试的.步骤如下.1 设置断点:在编码窗体的左边框上用鼠标双击,或者右键点击菜单,选择 Toggle Breakpoi ...

  8. I.MX6 Android USB Touch eGTouchA.ini文件存放

    /******************************************************************** * I.MX6 Android USB Touch eGTo ...

  9. 使用ADB无线连接Android真机进行调试

    使用ADB无线连接Android真机进行调试   其实这已经是一个很古老的知识了,记录一下备忘. 准备工作 手机和电脑需要在同一个局域网内 电脑上已经安装好ADB工具,可以是Mac或者Windows ...

随机推荐

  1. python 自动化-"Elements not visible"

    一,今天试着跑一个多乘客下单的python脚本, 总是遇到  Elements not visible 或者  not clickable的错误 解决方法: 1. 首先观察脚本运行时, 报错的那个元素 ...

  2. Swiper 常用功能及配置清单

    内容来源于Swiper中文在线(http://www.swiper.com.cn/),由于Swiper功能强大,这里只将常用的功能列出来,方便开发. 这里统一使用Swiper最新版 4.0做为演示! ...

  3. MySQL训练营03

    [任务四] #任务时间# 请于4月6日22:00前完成,在[打卡表格]处打卡.逾期尚未打卡的会被清退. 4.1 MySQL 实战 #学习内容# 数据导入导出 将之前创建的任意一张MySQL表导出,且是 ...

  4. Win10下Pytorch的安装和使用[斗之力三段]

    简介: 看到paper的代码是用Pytorch实现的,试图理解代码,但是看不懂,只能先学一些基础教程来帮助理解.笔记本电脑配置较低,所以安装一个没有CUDA的版本就可以了.安装完之后,就可以跟着教程边 ...

  5. Python 3 学习笔记之——基础语法

    1. a, b = a, a + b 先计算右边表达式,然后再同时赋值给左边. 2. 条件控制和循环语句 条件控制 if condition_1: statement_block_1 elif con ...

  6. visionpro halcon 哪个好

    visionpro halcon 哪个好 很多朋友会问到visionpro和halcon这两款机器视觉软件,到底学哪个好呢,今天众寻网就给大家讲一讲: 首先比较下两者的优缺点: halcon: 提供的 ...

  7. Python3 初识Python

    一 Python简介 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC ...

  8. No node available for block: blk

    刚才利用hadoop和mahout运行kmean是算法,一开始利用了10个节点,一个master,9个slave,运行了7分钟,我为了看速度的变化,就改用伪分布的形式,但是一开始运行就报错了: 17/ ...

  9. CodeForces Round #521 (Div.3) D. Cutting Out

    http://codeforces.com/contest/1077/problem/D You are given an array ss consisting of nn integers. Yo ...

  10. java基础知识-冒泡排序

    //冒泡排序,从数组前面向后循环比较 public static void sort1(int[] aa){ int size=aa.length; int temp; //循环数组 for(int ...