2008年7月22日星期二

驱动编程调优相关辅助工具-图形图像类

驱动编程调优相关辅助工具-图形图像类

作者:刘旭晖 Raymond转载请注明出处

Emailcolorant@163.com

BLOGhttp://blog.csdn.net/colorant/

主页:http://sites.google.com/site/rgbbones/

1综述

说到驱动编程和调优的辅助工具,我想说的不是像source insight Visual Studio这类的代码阅读开发工具,也不是像GDB Oprofile Strace之类的代码跟踪调试工具。


我所说的辅助工具,更多的是指针对各种各样类型的硬件,以硬件开发调优和测试为目的,提供数据来源,数据仿真,数据抓包,提供测试用例,进行数据转换,格式转换等等各类辅助用途的工具。


这其中多数是一些现成的工具,也有些是为了方便,自己编写的一些小程序或应用。


也许有些工具,你从来不曾用过,也同样能完成自己的工作,或者出于麻烦,不愿意去学习了解相关的知识。但是要成为一个专业的驱动开发者,我始终认为,只有认真学习相关硬件以及所涉及的子系统和应用领域,深入掌握其相关背景知识,才能真正开发出高效,有用的驱动,才能真正发挥硬件的所有潜在能力,从硬件能力软件应用的背景等角度去系统的思考驱动的框架结构。而不仅仅是简单的把硬件Run起来,做些修修补补的工作。


这篇说的是图形图像类的工具,通常会在LCDCameraTVOUT等驱动开发过程中使用到。有些工具需要你对一些相关知识有些了解。应该说,了解一些图像处理,以及光学等方面的原理,对于驱动的开发调试常常会起到事半功倍的指导性作用。至少能让你自己显得更专业些 8


2颜色校准类:

2.1LCD校准工具

校准你的PC显示器,应该是严肃的对待图像处理相关的工作第一步需要做的事吧。因为通常显示器都会有一定的偏色。不论你所做的驱动工作是调整LCD的颜色,对比度,gamma曲线,以期获得理想的显示效果,还是调整摄像头的相关参数,希望得到真实的成像效果。你都不可避免的,或者使用PC的显示器作为参照,对比显示效果,或者用PC检查摄像头的成像结果。要做到客观科学,就要求你的PC显示器本身所呈现的图像效果是标准的,真实的,这也就要求你对显示器进行校准。

最好的工具之一当然是Spyder,俗称蜘蛛。这是一套硬件工具,在使用时,由一个感光组件吸附在显示屏幕上,运行配套的软件,采集屏幕发出的色彩亮度信息,生成校准数据,以颜色配置文件的形式对屏幕进行校准。蜘蛛有不同的规格,价格从数百到数千不等,我只用过最便宜的Express版本。

此外,如果实在搞不到蜘蛛,也可以使用Photoshop中的色彩校正功能,按照步骤,进行校正,这种方法存在较大的主观因素的影响。

2.2颜色采样软件

很多场合,我们会想知道图像上某一点的具体颜色值是多少,例如分析camera拍摄的色板图像等。使用Photoshop当然是可以的,不过,毕竟有些大材小用。可以找到很多颜色采样的小软件,例如下面这个Color Cop

不过需要注意一点的是,有些图像编辑显示软件在显示图像时,会根据图像内嵌的ICC文件等在显示时对图像色彩进行校正,所以用屏幕捕捉工具捕获的颜色值,可能会和图像编辑软件取得的色彩值不同。

2.3色度分析计

调试LCD驱动时,与PCLCD进行比对,毕竟是一种主观的间接的方法,如果想要精确的分析LCD的色彩,亮度指标,可以使用色度亮度分析计。类似下面这样的东西:

它的主要功能就是测量发光体的亮度值和色度信息。使用时,可以让LCD显示例如红、绿、蓝、黑、白等纯色图像,用它采集LCD的亮度和色彩值。然后和理论标准值进行比对,进而调整。

这玩意用过几次,确实不错,不过价格不便宜。。。

2.4标准灯箱

如果你做过Camera驱动,或者你对摄影有一些兴趣,那你应该听过白平衡这样一个概念。简单的来说,物体显示的颜色,和它所处的环境光源是有密切联系的。标准灯箱,就是用来模拟几种常见的光源环境。不同的灯箱有不同的用途,我所用过的灯箱可以模拟例如标准日光,日光灯光等光源。

标准灯箱的应用领域很广了,对于我来说,主要是用来调试Camera驱动的各种白平衡设置,以及标准光源下Camera的各种色彩相关的寄存器的设置。

3颜色转换类

颜色转化,严格地说是色彩空间的变换,其包含的内容是很广泛的,也是很复杂的一门科学。常见的转换包括例如从RGB色彩空间转换为打印领域常用的CMYK色彩空间等。

3.1YUV2RGB

对于驱动开发来说,最常遇到的应该是RGBYUV色彩空间的转换了。在图形显示,Camera数据采集等等场合都可能会遇到RGBYUV的转换问题。

实际上YUV本身也存在很多不同的标准,所以RGBYUV的转换公式会有好多种,在数字领域,常见的应该还是RGBYCrCb的转换。

没有找到很简单的工具直接计算色彩值,所以我用Excel表格制作了一个转换工具,填入RGBYUV值,就能得到相对应的色彩值。

上传到CSDN了:计算YUV转RGB的EXCEL表格

4图像转换类

4.1格式转换

图像格式的转换,如果是各种图形格式之间的转换,那么有很多工具可以实现,最简单常用的不外乎就是Acdsee了。

除了这类图形格式的转换,在驱动开发中,最常用的应该还是将一幅图像转换成二进制的RAW数据,用于直接在LCD上显示,这类工具我最常用的就是Image2Lcd了。

Image2Lcd能够将图像转换成LCD显示所需的点阵数据,可以设置输出数据的色深和颜色分量的排列顺序。也可以将数据输出成C语言数组的形式。

5图像分析类

5.1色板

标准色板,我认为是调试Camera类的驱动不可缺少的工具,常用的是这种24色的标准色板

进口的这种色板价格可不便宜,大概要100-200美金吧。没有找到合适的国产替代品。这24个色块是很有代表性的颜色,下面6个是灰阶,上来6个是三原色和三补色。其它还有一些肤色啊CMYK啊之类的颜色。

5.2灰板

灰板主要用来调整白平衡和进行曝光测试。相对而言比较便宜,几十块钱就能搞定了。很多摄影爱好者还随身携带一块呢。


5.3色板数据分析工具

有个很牛的软件叫

Imatest,通常很多数码媒体评测相机或镜头时都会使用这个软件。它的功能很多了,包括测试MTF曲线,镜头光场变形等。

对于我来说,在Camera驱动中,可以用它来分析拍摄得到的色板数据,进而调整摄像头的gamma曲线,对比度设置,颜色空间变换设置等参数。下面是一个分析色板底部6个灰阶块的亮度信息,计算Gamma值的示例:

5.4其它分析工具

其它还有一些辅助图表,工具等是用来测试镜头的分辨率,形变等,这个在小的摄像头上基本上就用不上了,因为即使测了,也没法做什么改变 8 )这个是由硬件本身决定的。

6其它

6.1Gamma曲线计算工具

很多LCDCamera Sensor都会有gamma曲线的寄存器设置,不过,通常不同的厂商,gamma曲线的寄存器的设置方式都不一样,有的是分段取点,有的是分段取斜率。

不过,最基本的,第一步总是需要计算,对应于多少的gamma值,曲线上的数值应该是多少。这个我也没有找到现成的工具,还是用excel表格制作一个简单的计算工具如下,对应曲线上的数值输出为十进制和十六进制:


上传到CSDN了:Gamma曲线计算工具

6.2DisplayX

这是一个很小的显示器测试软件,主要用来测试颜色,清晰度,聚焦,延迟等等。本来和驱动开发没什么关系,不过,它的测试项目很有代表性,自己写LCD驱动的测试程序时可以参考它的测试项目编写测试用例。


2008年7月13日星期日

移植和使用内核函数跟踪系统KFT

移植和使用内核函数跟踪系统KFT

作者:刘旭晖 Raymond转载请注明出处

Emailcolorant@163.com

BLOGhttp://blog.csdn.net/colorant/


以前在2.4内核中使用过KFI来跟踪内核的函数调用,分析性能和帮助理解源码,感觉在某些情况下,还是很好用的,最近在新的项目中,需要在短时间内阅读,维护和修改大量的驱动代码。所以又想到了它,因为当前使用的内核没有集成KFI的后续项目KFT的代码,所以做了一些移植和使用的脚本编程工作,记录在这里。

关于KFT的基本原理,以前写过一篇基于KFI分析的文档(用KFIGraphviz跟踪优化内核代码),现在看来原理上基本是一致的,贴在这里:http://blog.csdn.net/colorant/archive/2008/07/09/2627493.aspx

对内核函数跟踪机制不了解的可以先看那篇文章,这里对原理不再做分析。

本人的能力和时间有限,可能下文中有些理解、分析不一定准确,欢迎联系指正。


1相关说明


1.1网站资源

KFT的官方网址:http://elinux.org/Kernel_Function_Trace

Graphviz的官方网址 http://www.graphviz.org/

一篇分析函数跟踪机制和使用Graphviz进行分析的文章,Visualize function calls with Graphviz : http://www-128.ibm.com/developerworks/linux/library/l-graphvis/?S_TACT=105AGX52&S_CMP=cn-a-l


KFT2.6.21内核的patch http://elinux.org/upload/3/33/Kft-all-in-one-2.6.21.patch


1.2工作环境

由于是跟踪内核,所以KFTkernel的关联性应该还是比较密切的,KFT的主页上有2.6.8 2.6.112.6.12 及我所使用的2.6.21等几个版本的patch

至于我的环境:

  • 硬件平台:基于ARM的嵌入式板子

  • 软件环境:Linux 2.6.21 ,自制文件系统


2移植

理论上,我的内核版本和patch所针对的版本是精确匹配的,所以本来希望patch打上以后就能用。很可惜,打完patch以后,内核配置选上KFT以后,内核build不通过:


kernel/built-in.o: In function `__cyg_profile_func_exit':

utsname_sysctl.c:(.text+0x2a68c): undefined reference to `cmpxchg'

kernel/built-in.o: In function `__cyg_profile_func_enter':

utsname_sysctl.c:(.text+0x2ad74): undefined reference to `cmpxchg'

make: *** [.tmp_vmlinux1] Error 1


仔细看了一下,cmpxchg的目的应该是做一次原子性的比较和交换的动作,ARM本身不支持这样的指令。所以,在我的内核中,使用软件关中断的方式,实现了一个类似的函数,仿造那部分代码在kft.c中添加了一个cmpxchg函数如下:


static inline int __noinstrument cmpxchg(int *v, int old, int new)

{

int ret;

unsigned long flags;


raw_local_irq_save(flags);

ret = *v;

if (likely(ret == old))

*v = new;

raw_local_irq_restore(flags);


return ret;

}


然后,kernel可以Build通过了,只是,不幸的是,下载后的kernel无法启动了,Uncompressing Linux............................................................................................... done, booting the kernel. 到这一步就停止了,

大致猜想,原因是某部分的内核代码不能使用gcc-finstrument-functions参数来编译,这一点可以在KFTpatch中的很多代码上可以看到,使用了__noinstrument 禁止跟踪,这些修改,有些是为了防止KFT本身的函数循环调用,有些是因为某些函数必须禁止跟踪,还有些是未知的原因,可能导致内核崩溃。


没有合适的硬件调试器方便跟踪kernel启动的早期阶段(还没有打印输出),暂时没有精力去跟踪到底那部分出了问题,我只能假设基于ARM平台,在我使用的内核代码中,还有一些部分必须禁止跟踪,而KFTpatch中没有处理这一部分。


所以,暂时放弃完美的解决这一问题的企图,曲线救国吧,修改总的Makefile 把编译选项-finstrument-functions 去掉。这样可以使得KFT核心模块本身被编译,但是,所有的Kernel代码不调用相关函数,毫无疑问这样是可以正常把kernel跑起来的。


然后,修改底层驱动的Makefile,仅对部分模块使用-finstrument-functions参数进行编译。这样同样可以实现对这些模块涉及到的函数进行跟踪的目的。基本能满足我的要求,缺点是,由于不是所有函数都跟踪,可能出现跟踪路径不完整。(在发生进程切换的场合,似乎有时候还会导致函数调用关系的错误关联,这和KFT的跟踪机制有关,不能怪它,谁叫我没能完整的跟踪所有函数,这个问题怎么解决有点头大,好在这种错误情况一眼就能看出来,大不了重跟踪一次)


3使用


标准的基本使用方法,可以参考前面列的文章,除此之外,我所面临的问题是需要能够支持对以模块的形式插入内核的驱动的跟踪。

这里涉及到几个问题:

3.1如何triggerstop

因为模块在未插入内核之前,没有办法得到所需的函数地址,(Buildin的函数是通过 addr2sym 查询System.map来转化得到),所以我所能想到的办法不外乎:

如果确实需要设定由哪个函数进行触发,那么插入一次模块,查询模块的符号表(下一节描述),手工将trigger函数改为对应的地址,重启系统,以完全相同的步骤插入模块,希望模块加载进来以后,保持函数地址和上次一致。

如果不关心由哪个函数触发(例如我想了解一下模块插入以后,相关的驱动初始化流程),那么可以在插入模块前,加载一个基于timetrigger,(只是为了存在一个trigger,没有trigger KFT没法启动)然后,用echo “start” > /proc/kft 进行强制触发。我的trigger


new

begin

trigger start time 5000

end


Stop也类似了,手工得到函数地址,或者,强制停止。

3.2如何获得log分析所需的地址和符号表映射

符号映射表通常由System.map得到,我的使用场合下,需要动态的得到模块插入以后总的符号表。

为此,我通过cat /proc/kallsyms > /tmp/kallsyms.bin 来得到运行时的完整符号表。

这个途径得到的符号表包括所有已经插入的模块的符号,但是,和System.map比较存在一些问题:


  • 符号表没有完整按照地址排序

  • 符号表有额外的带$特殊字符的符号存在

  • 模块部分的符号表有4列输出,比System.map多了一列模块名的显示


这几个区别会导致后面的地址转换成符号的查询算法出现问题,不能正常工作,所以,写了一个简单的脚本来处理得到的符号表,使其满足所需的格式,脚本如下:


#! /bin/sh

name=$1

nameout=$name".map"

awk '{print $1,$2,$3}' $name sed /"$"[a-z]/d sort > $nameout


基本上就是3步:只输出前3列,去除$a等符号,排序。


3.3处理__INIT函数

__init修饰符的函数,在system.map/proc/kallsyms中都没有生成符号表,如果要以相关的函数作为触发,我所能想到的只能是写一个dummy函数来触发了。例如下面这个函数:


static int __init u2d_init(void)

{

return platform_driver_register(&u2d_driver);

}

修改成下面的代码:

void dummy_u2d_init(void)

{

printk("just for kft trigger");

return;

}

static int __init u2d_init(void)

{

dummy_u2d_init();

return platform_driver_register(&u2d_driver);

}

这样,以dummy_u2d_init作为触发函数,即可。

不知道是否有更好的解决办法。


3.4绘制函数调用图

得到log,按照kft的标准用法,使用kd等工具已经可以对数据进行分析,不过我的目的之一依然是希望得到函数调用图,所以参照以前的做法,修改了转换脚本以及mtjones 所写的graphviz所需Dot文件的生成程序。

上传到了google doc

http://docs.google.com/Doc?id=dxf836w_10vzx9v6dx

3.4.1使用:

使用上述脚本,程序,完成画图所需的工作的大致流程如下:


../sym2map.sh kallsyms.bin

../mykftres.py kft.log kallsyms.bin.map > kft.call

../mypvtrace/pvtrace kft.call

dot -Tjpg graph.dot -o kft.jpg

../addr2sym < kft.log -m kallsyms.bin.map > kft.lst

../kd -c -r kft.lst > kft.ctree


下图是实际跟踪usb gadge filestorage的模块的初始化过程得到的函数调用图中的一小部分:


4遗留问题

  • __init 宏修饰的函数没法从/proc/kallsym中获得,这些函数就没法查找定位了

  • 部分 static函数,如果只被一个函数调用,那么该static函数可能会被优化成内联函数,无法跟踪。

  • 完全使能-finstrument-functions编译选项导致内核无法启动

2008年7月10日星期四

LTT (Linux Trace Toolkits) 简介

LTT (Linux Trace Toolkits) 简介

1概念

1.1功能

LTT是一个用于跟踪系统详细运行状态和流程的工具,它可以跟踪记录系统中的特定事件。这些事件包括:


  • 系统调用的进入和退出

  • 陷阱/中断(Trap / Irq)的进入和退出

  • 进程调度事件

  • 内核定时器

  • 进程管理相关事件:创建 ,唤醒,信号处理等等

  • 文件系统相关事件:Open / Read / Write / Seek / Ioctl 等等

  • 内存管理相关事件:内存分配/释放等

  • 其他事件:IPC / Socket/ 网络 等等


此外 Ltt还提供了自定义和记录需要跟踪的事件类型的函数接口。

1.2结构

LTT主要由4个部分组成:

  • 内核代码补丁:LTT目前还没有纳入正式的官方内核,在内核中的部分代码需要打Patch来获得,主要是修改了上述各类事件的相关代码,添加了Trace的调用代码

  • 内核模块:Trace功能的主要实现部分,记录内核事件,并和用户空间的守护进程进行交互

  • 用户空间的守护进程:从LTT的内核模块中获取事件相关数据,并写入文件。

  • 数据分析应用程序:读取守护进程生成的数据文件,加以分析,并以更加可读的方式(图表等)显示出来。



除此之外,LTT还具备自定义和跟踪用户空间事件的能力。


2使用

所有的安转使用相关的内容,都可以在LTT的官方站点:http://www.opersys.com/ltt/ 找到。仔细阅读里面的文档即可。

(不过,其稳定版本在我这编译起来居然有Error,修改过一些代码后,编译通过,有很多warning,或许是我的编译环境有问题,最终试用的时候图省事,没有去过多研究,使用了MontavistaPackage中现成编译好的版本 8


3体会


LTT的确是一个很有用的工具,灵活的配合其他工具的使用,如stracetime等等,可以作为分析优化系统的performance的一个可行的途径。


值得一提的是,LTT可以通过设置参数,在特定的时间段,筛选所需要记录的事件类型,还可以按进程ID等选择所跟踪的特定对象,通过合理的配置参数,可以使得其对系统所造成的额外负担减少到一个合理的程度,对系统性能的分析基本不会造成明显的干扰因素。


另外LTT提供的自定义和记录需要跟踪的事件类型的函数接口,也使它有了一些拓展能力,可以用来Debug自己编写的内核模块代码。


LTT跟踪用户空间事件的实现,是通过其内核模块中几个特定的IOCTL接口,由用户空间的一个LIB库将其包装成函数,使得其使用方法与其在内核模块中自定义和记录事件类型的函数接口相同。应该说这是一个很巧妙的实现办法,不过对其性能个人还是有些怀疑,因为这样一来,对用户空间事件的纪录就需要经由 用户空间-〉内核空间-〉用户空间 走一趟来实现。


USB Video Class及其实现

1 Video Class基础概念

Usb协议中,除了通用的软硬件电气接口规范等,还包含了各种各样的Class协议,用来为不同的功能定义各自的标准接口和具体的总线上的数据交互格式和内容。这些Class协议的数量非常多,最常见的比如支持U盘功能的Mass Storage Class,以及通用的数据交换协议:CDC class。此外还包括Audio Class, Print Class等等。

理论上说,即使没有这些Class,通过专用驱动也能够实现各种各样的应用功能。但是,正如Mass Storage Class的使用,使得各个厂商生产的U盘都能通过操作系统自带的统一的驱动程序来使用,对U盘的普及使用起了极大的推动作用,制定其它这些Class也是为了同样的目的。

Video Class 协议的目的是给USB接口的视频设备提供一个统一的数据交换规范。最初版本是在20039月才添加到USB Class规范中的,1.1的版本更是在2005年才发布。相比之下,Mass Storage Class 早在1998年就发布了。支持Video Class协议的多媒体芯片也是在2005年才陆续发布。所以USB 视频设备目前的现状是,在设备一端,多数依旧还采用原先的各种包含通用USB功能的多媒体处理芯片,主机端需要安装专用的驱动程序,基本上各个产品之间不具备兼容性。甚至对于操作系统而言,也只有在XPSP2以后,才包含了对通用的Video class协议的支持。所以即使是某些多媒体设备(比如Logitech最新的几款摄像头)包含了对Video Class的支持,在Win2000等操作系统上依然需要安装驱动程序。不过,应该说使用Video Class无疑会是一个趋势,在相应的多媒体芯片陆续投入市场后,支持Video Class的多媒体设备应该会在一两年内会迅速普及开来。

除了在硬件上通过相应的多媒体芯片支持Video Class的设备以外,对于包含了操作系统的智能手机,当然也可以在手机端通过驱动程序来实现对Video Class的支持,就好像原先支持任何一种专用的USB驱动一样。只不过数据交换的格式不是自己随意制订的,而是按照Video Class的规范来实现的。

由于目前支持Video Class的设备还很少,所以在Linux上还没有开源的Video Class的主机端驱动,设备端的Video Class驱动就更没有见到开源的代码了。本文在介绍USB Video Class架构的基础上,主要是探讨Linux操作系统下设备端Video Class驱动的实现。不过在其它平台下的实现思路应该也是类似的。



2USB Video Class 协议结构

2.1设备拓扑结构

在拓扑结构上Video Class 将视频设备抽象为几个主要的硬件功能模块:

  • 输入端点 Input Terminal

  • 输出端点 Output Terminal

  • camera端点 Camera Terminal

  • 选择单元 Selector Unit

  • 处理单元 Processing Unit

  • 拓展单元 Extension Unit


下图是一幅摘自USB_Video_Example 1.1.pdf www.usb.org)的拓扑结构示例图:

  1. USB Video Camera Topology Example

sensor和另一个复合视频设备得到的数据流由IT CT输入,经SU选择送PU处理,再由OT绑定到指定的USB端点。最后由USB端点与主机交互将数据发送到host端。在实际设备中,可能没有其中的某些功能模块,也可能其中的几个模块都是由同一硬件来完成的。

2.2协议层次结构

上图中,左半部的框架组成了Video Class中的控制接口界面,右半部的框架组成了视频流传输接口界面。这两部分购成了Video Class的主要协议框架。

2.2.1Descriptor Layout

Class相关的信息,当然是主机端通过向设备端获取描述符(Descriptor)来得到的, 下图摘自USB_Video_Class_1.1.pdf , 给出了一个Video Class协议描述符应用示例的Layout

  1. Video Camera Descriptor Layout Example

可以看到,Descriptor Layout中,在标准描述符里,除了Device Descriptor, Configuration Descriptor, Interface Descriptor, Endpoint DescriptorString Descriptor以外,还有一个USB2.0 协议中后期才新加的IAD Interface Association Descriptor,用来描述多个相关Interface之间的关系,在Video Class中,IAD用来描述VideoControl InterfaceVideoStreaming Interface之间的关系。

图中深色的部分就是Video Class 协议相关的专用描述符(Class Specific Descriptor)了。主要就是对硬件图像采集和处理模块的物理拓扑结构和功能的描述,以及对视频传输格式(包括编码格式,码率等等视频图像相关参数)的描述。

通过从设备处获得这些描述符,主机可以得知视频设备端的结构及其所支持的功能。而控制这些功能模块,对数据源和数据流进行配置,则需要通过Request来完成。

2.3Request

Request是由主机向设备端发起的功能请求,包括所有USB设备都需要支持的Standard Device Requests 和与Class相关的Class Specific Requests

2.3.1Standard Device Requests

下图列出了USB Spec中规定的标准Request

  1. Standard Device Requests

这其中,有一部分Request是由USB控制芯片在硬件一级就直接完成的应答工作,比如SET_ADDRESS,有些则需要由软件来做进一步的处理,比如Set_Configuration。软硬件的这种任务的划分还与具体的硬件芯片相关。因为这部分是标准协议相关,本文就不详述。

2.3.2Class Specific Requests

Class Specific Requests的数据结构Layout与标准Request是一样的,只是内容不同而已。VideoClassClass Specific Requests主要根据Interface分为两类,其下又根据具体功能模块做进一步的划分:

  • VideoControl Requests

  • Camera Terminal Control Requests

  • Selector Unit Control Requests

  • Processing Unit Control Requests

  • Extension Unit Control Requests

  • VideoStreaming Requests

  • Interface Control Requests


这其中,Interface Control Requests因为是用来在主机和设备之间协商数据交互格式和分辨率,流量等信息的,所以一般来说是必须实现的。

Camera Terminal Control Requests Processing Unit Control Requests中的内容,则是目前常用的即时通讯软件如MSN / QQ 等在其视频控制界面上集成的控制参数。

其中,Camera Terminal Control Requests包含了对曝光时间,曝光模式,对焦,变焦,平移等sensor获取数据阶段时的参数控制。

Processing Unit Control Requests中则包含了亮度,增益,色调,对比度,白平衡等等sensor在获取到图像数据后,图像处理阶段的相关参数。

  1. Win2000MSN的视频控制界面之一

不过实际上,以上两者的划分在硬件层次并不是绝对的。很多参数的控制在sensor硬件级别上是同一层次的。不过,将这些功能抽象成这两类,正如在硬件的拓扑结构上将功能模块抽象出来一样,有利于通用化的程序设计。


3USB Video Class Linux设备端的实现

3.1驱动架构

3.1.1平台及软件基础

本文讨论的是USB Video ClassLinux操作系统上的设备端实现。具体是在Omap平台上,基于USB Gadget的驱动架构来实现的。

USB Gadget驱动分为两层,底层是处理与USB控制芯片硬件相关的内容,对上层屏蔽了大部分硬件相关的设置,并处理了一部分标准RequestEP0相关的标准操作流程。上层则是Class相关的部分,官方的Gadget驱动中,已经包含了File StorageU盘功能),RNDISUSB网卡功能)等的支持。考虑到Video Class的数据交换过程与File Storage有很多相似的地方,所以本文在Video Class的实现中,在大的框架上仿照了File Storage驱动的架构。

3.1.2基本框架和数据流程

在本文实现的Video Class驱动中,整体的框架基本上分为两大部分。

一部分是负责处理模块的初始化过程,并负责处理Usb总线上的DescriptorRequests的交互过程。包括USB总线上的控制和查询包的接收,解释,分配和应答。

另一部分,是在初始化过程中启动的一个独立的内核线程。负责具体的控制指令的执行和图像数据的获取及传输工作。这其中的许多操作都有可能引起睡眠,或者需要对文件进行操作,因此必须有一个线程做为依托。

模块的流程基本上是这样的:

init函数中向Gadget底层驱动注册VideoClass数据结构。所有的描述符都定义为全局结构变量,在模块初始化过程中,进一步完成对描述符的填充过程,启动独立的内核线程,并注册EP0complete回调函数。

在启动的内核线程中打开并初始化camera设备。将camera设置为默认的参数配置。读取图像数据并将数据填充到BUF里而后提交RequestVideoStream Interface里的BULK IN端点中。而后睡眠等待由于数据传送完毕被唤醒或有(异常)Exception发生被唤醒。

如果是数据传送完毕,则继续读取,填充并发送图像数据,如果有异常发生,则转而处理异常。

另一方面,哪些情况会引发异常呢?主要是驱动程序与BUS总线交互信息的这部分模块中,如果发生主机端重新设置Configuration,改变USB设备配置,或者发生总线插拔等引起总线状态变化的时候,会产生一个相应的异常,等待内核线程被唤醒并处理这些异常。

此外,在处理Requests的时候,有时候需要与camera驱动模块交互控制信息,同样需要操作文件句柄,目前的做法是在ep0 request的回调函数(context)中启动一个bottom half task,在Bottom half中完成相应的控制。

从总体结构上说,这样做很难看,理想的话应该在上述独立的内核线程中统一处理与Camera模块相关的操作,但是目前的架构,要唤醒该线程,只有两个途径,一是数据传输完毕或被取消,二是有总线状态变化相关的异常发生。如果硬加一个异常用来处理Requests似乎也很难看,而且对架构需要有较大的调整。所以目前只好简化的采用了前面所说的方案。

然后关于什么时候打开设备,开始往USB总线上放置数据,目前的处理方式也不是非常理想。目前是在模块初始化后立即获取第一帧图像而后等待主机端读取,实际上,主机端可能并不马上安排图像数据的传输。但是如果在主机端需要的时候才去读取数据的话,因为sensor获取数据需要一段曝光时间再加上压缩数据所需的时间,不可能立刻响应,所以在时间上肯定不能满足开始一段的数据传输请求。也需要继续仔细分析在何时启动camera模块最为合适。

3.2Camera驱动和V4L2子系统的配合

linux内核中,200212月起发布了V4L2 (Video For Linux Two ) 0.1版本的规范,V4l2试图为所有和视频相关的设备都提供统一的接口,这其中当然也就包括了Camera设备。

USB Video Class这一部分内容恰恰与视频设备也是密切相关的,所以在某些平台产品的实现中,甚至是在VideoClass中直接包含了Camera的驱动程序。这样做对于单一产品来说,可以大大简化驱动的层次结构,应该说是处理Camera的最直接简洁的办法。

但是,在本文的实现中,考虑到Linux内核中合理的模块划分的原则,也是为了符合Gadget驱动的其他Class实现的一贯风格,所以还是尽量使用V4L2的接口来控制Camera模块。这样做也有利于代码的移植。减小不同功能模块之间的耦合性。

理想的方式自然是所有的与Camera相关的操作都通过V4L2接口来实现,不过目前的实现中还是有些例外,引发例外的主要因素是效率问题。

由于在USB总线上传输图像,受到总线速度的限制,特别是在非USB2.0接口的芯片中,所以势必要采用JPEGMPEG编码对数据进行压缩。V4L2子系统的框架中,包含了对编码器的支持,但是,也许是笔者对V4L2子系统的学习还不够深入,其对编码器的支持应该是对硬件编码芯片的支持,即使如此,也没有见到相关的代码,而且,在实现中使用的手机主板上也没有硬件的编解码芯片,所以在实现中Camera驱动通过V4L2子系统对应用层提供的是原始的未经编码的数据流,由应用程序调用IJG Independent JPEG Group JPEG库函数来实现jpeg的编解码工作。

所以如果通过V4L2read接口来获取数据,势必只能得到原始的图像数据,而且在V4L2的实现中,通过Read方式获取的数据,需要通过多次内存拷贝才能到达调用者处。所以也会很大程度的影响图像处理的速度。

要对图像进行压缩,要不在用户空间调用IJG库,要不在内核中再实现一个JPEG压缩算法。按前者来实现的话,涉及到Video Class如何去启动一个用户程序来读取Camera数据并在压缩后再传送给内核,也不是完全没法实现,但是无疑是一个非常糟糕的实现办法。

后者的话,涉及到这个JPEG压缩算法应该在什么地方实现,以及由谁来调用的问题(Video Class 还是 V4L2)。考虑到在存在硬件编码芯片的情况下,该芯片的管理和使用应该会纳入V4L2子系统中,所以考虑到兼容性,目前实现的方式是将JPEG压缩算法作为一个独立的模块插入内核,由V4L2子系统调用相关函数对图像数据进行压缩,然后再在Camera驱动中Export一个额外的函数接口,USB Video Class通过该函数接口启动Camera图像的读取和压缩过程,以区别标准的V4L2子系统的数据处理流程。压缩后的图像数据直接写入通过指针参数传递进来的内存地址空间中,尽可能的减少内存拷贝的次数,加速图像的传递。

这样做带来的问题就是需要在V4L2架构中添加额外的接口,增加了USB Video ClassV4L2子系统之间的耦合性,不能完全将这两个模块隔离开来。应该还有更好的解决方案。


3.3JPEG编码相关

Jpeg的编解码,在Linux操作系统中,基本上采用的都是IJGwww.ijg.org)的JPEG库函数Libjpeg,这是一个相当可靠稳定和高效的开源项目,支持JPEG标准(不包括JPEG2000)的绝大多数编码方式。如非无奈,确实没有必要另外再写一个编码程序。但是由于需要在内核中使用,所以只好自己再编一个了。

JPEG编码相关的代码除了IJG的源代码以外,在网上还可以搜索到若干,但是无疑IJG的代码是最完善的。其它我能搜到的代码,多多少少都有一些BUG,而且也只是实现了JPEG标准的最基本的功能(当然,对于Video Class的应用来说已经是足够了)。最重要的是,多数是用浮点数运算来实现的,撇开速度不说,在本文的实现中OMAP平台的CPU也不支持浮点数运算。。。所以,本文实现中,最终是参考了网上搜到的某个算法的流程(主要是IJG的架构太复杂,一来没有时间精力和能力进行完整的分析,二来也不适合在内核中使用如此复杂的架构),在快速离散余弦变化的整数算法上仿照了IJG库的算法两者综合起来完成的。最终的代码还有很多需要改进的地方,不过,对于VideoClass来说,应该勉强够用了。这其中的具体问题,打算在另外单独的文档中再说明。

3.4操作系统相关

说操作系统相关,可能说大了一些,这里主要涉及的内容是在本文的实现中,在WIN2000WINXP平台的MSN测试中,遇到的一些问题。

由于VideoClass的协议只是规定了数据传输的格式和内容,对具体实现中的一些细节并没有作硬性的规定,所以导致有些细节可能存在不兼容的实现方式。(当然,我想主要还是本文的实现,由于能力有限,没有充分考虑到各种情况下的容错性,如果驱动做得好应该可以避免出现问题)。所以在WIN2000WINXPMSN测试中,遇到了一些平台相关的问题,有些功能在2000下能正常工作在XP下存在Bug,有些却相反。有些已经解决,有些只是猜测了可能的原因,罗列如下:

3.4.1视频窗口关闭再打开后,没有图像

开始是在XPMSN上发现有这样的问题,2000下没有,分析BUS数据可以看到,XP在关闭视频窗口的时候,会执行一个Abort Pipe的操作,这个操作应该会中断BULK传输,但是在设备端,Gadget底层驱动接收不到这个事件(也有可能是Gadget底层驱动的BUG),所以在VideoClass中无从得知这个传输已经被取消了,这样睡眠在等待数据传送完毕或失败上的线程也就无法被唤醒,自然也就不会继续发送数据。造成主机端再度打开视频窗口时接收不到图像数据。而在2000下的MSN中,关闭视频窗口的动作系统不会发送这个Abort Pipe事件,所以也就没有问题。

考虑到每次打开视频窗口的时候,主机端都会设置Streaming Interface的图像分辨率,码率等参数。而这之后主机端才会读取图像数据,所以后来解决的办法是在主机端设置Streaming Interface的时候,将之前已经放入BULK IN传输节点的数据 Dequeue出来,这样会造成这个传输的失败,从而唤醒睡眠的线程。但是如果仅仅这样做,XP能够正常工作了,2000又显示不了图像了。分析认为由于部分数据丢失,所以造成第一帧图像的数据是不完整的,无法正常解压缩显示,但是XP下的MSN有较好的容错性,能够丢弃这一帧图像,继续读取之后的数据,而2000下的MSN容错能力较差,无法再正常解读后面的图像数据。所以最终的解决办法是在发现传输失败后,将当前这一帧的图像数据从头开始重新发送,这样在XP2000下就都能正常工作了。

不知道这种解决方案是否仅仅是一种治标的方案,有待以后继续研究。

3.4.2default setting无法恢复默认设置

MSN的控制界面上除了前面图示的控制界面,高级按钮点击进去还有一套类似的但更详细的控制界面,两者界面上都有恢复默认设置的按钮,在2000下高级设置里的恢复默认设置无法完全正常工作,而在XP下,则是外面的控制界面上的恢复默认设置无法完全正常工作,界面上是恢复了默认值,但是图像显然没有跟着改变。不知道是否是MSN本身的BUG,因为在发生这种现象的时候,USB总线上确实没有相关的Control Request从主机端发送出来。

3.4.3某些分辨率下图像无法正常显示

Win2000中如果提供160*120分辨率的图像,图像非常容易就停止刷新了,而BUS上实际数据还是在发送的。而在160*112(两者都是16的整倍数)的分辨率的情况下,就几乎不会发生这种情况。如果说这有可能还是JPEG的压缩算法有点问题,那另外一种情况就一定是XP 2000的区别了:如果设备这端通过描述符和Streaming Interface申明只能支持160*120 或者 160*112 的分辨率,2000可以接受这种分辨率,而XP根本就不能接受,总线上的控制传输就停止了,在界面上则显示检测不到CameraCamera正在被其它设备打开占用,只有在进一步提供更高的320*240的分辨率的情况下,XP才会承认Camera的存在!其它问题倒不大,就是在本文的实现平台上,受软件编码JPEG速度的限制,320*240的分辨率下,视频的帧频会低一些,影响图像的流畅性。

3.5其它

3.5.1特殊效果的控制

应该说,VideoClassControl Request基本上涵盖了V4L2标准界面提供的大部分控制参数,但是,还是有一部分没有涵盖,至于特定驱动专有的控制就更无法体现了,尤其是在MSN等应用程序的界面上,更不可能提供这些参数的控制了。但是,我们还是可以想办法trick过这个问题。

比如手机上常见的图像效果的设定,虽然不是特别有意义,但是既然是很常见的,为什么不能把它也做到Web Cam中呢?所以,如果一定要做,我们可以利用MSN控制界面上的原有的控制界面,借用其中一两个控制参数来实现图像效果的设定。

本文的实现中选择采用色调来控制图像效果,因为实际上这个参数是很不常用的,甚至只能在XP的高级设定中找到,对于99.9%的用户我相信都不会去改变这个参数。而它的字面含义与我们实现的功能也不算一点关系都没有,毕竟有很多效果实际上就是改变一下图像的颜色(当然还有一部分例外了)。

类似的可以用一些我们认为常用的设置替换既有的参数。这样做的缺点就是控制参数的字面含义与实际功能不太吻合,优点当然就是可以提供给用户更多更常用的图像设置。比如设置一个黑白,素描之类的图像的效果,玩玩抽象派视频聊天。

  1. 图像效果设置