2021SC@SDUSC
目录
我们知道,扫描时提供给ZBar的不都是静态的图片,也有可能是动态的视频。例如我们日常生活中调用的微信扫码,所提供的也是视频video,这时便需要Zbar对视频进行分析,动态扫码。
Video模块是ZBar实现对读入视频进行扫描分析的功能模块。核心代码由video.h和video.c组成,video.h包括对一些关键变量的声明和结构体的定义,同时也是其他部件对Video模块的调用接口(头文件),而具体功能代码则是在video.c中实现。
在ZBar项目,每一个功能部件的实现通常都会有对这个部件的初始化、增加、释放内存等函数,这与Java中的new一个对象以及set(),get()函数有异曲同工之妙。
static void _zbar_video_recycle_image (zbar_image_t *img)
{
zbar_video_t *vdo = img->src;
assert(vdo);
assert(img->srcidx >= 0);
video_lock(vdo);
if(vdo->images[img->srcidx] != img)
vdo->images[img->srcidx] = img;
if(vdo->active)
vdo->nq(vdo, img);
else
video_unlock(vdo);
}
Video模块也不例外,首先分析_zbar_video_recycle_image (zbar_image_t *img)函数,顾名思义,这是Video模块对输入视频的图像进行逐帧循环的函数。
在分析这个函数之前,这里先对zbar_image_t的结构进行补充说明。
struct zbar_image_s {
uint32_t format; /* fourcc image format code */
unsigned width, height; /* image size */
const void *data; /* image sample data */
unsigned long datalen; /* allocated/mapped size of data */
unsigned crop_x, crop_y; /* crop rectangle */
unsigned crop_w, crop_h;
void *userdata; /* user specified data associated w/image */
/* cleanup handler */
zbar_image_cleanup_handler_t *cleanup;
refcnt_t refcnt; /* reference count */
zbar_video_t *src; /* originator */
int srcidx; /* index used by originator */
zbar_image_t *next; /* internal image lists */
unsigned seq; /* page/frame sequence number */
zbar_symbol_set_t *syms; /* decoded result set */
};
uint32_t格式;/*fourcc图像格式代码*/
注:FourCC全称Four-Character Codes,代表四字符代码 (four character code), 它是一个32位的标示符,其实就是typedef unsigned int FOURCC;是一种独立标示视频数据流格式的四字符代码。
视频播放软件通过查询 FourCC 代码并且寻找与 FourCC 代码相关联的视频解码器来播放特定的视频流。比如: DIV3 = DivX Low-Motion, DIV4 = DivX Fast-Motion, DIVX = DivX4, FFDS = FFDShow 等。比如wav、avi等RIFF文件的标签头标示,Quake 3的模型文件.md3中也大量存在等于“IDP3”的FOURCC。
unsigned width, height;/*图像大小*/
const void *data;/*图像样本数据*/
unsigned long datalen; /*已分配/映射的数据大小*/
unsigned crop_x, crop_y; /*裁剪矩形*/
unsigned crop_w, crop_h;
void*userdata;/*与图像关联的用户指定数据*/
refcnt_t refcnt;/*引用计数*/
zbar_video_t*src;/*生成器*/
int srcidx;/*生成器使用的索引*/
zbar_image_t *next;/*内部图像列表*/
unsigned seq;/*页/帧序列号*/
zbar_symbol_set_t*syms;/*解码结果集*/
以及对zbar_video_s传入视频结构体的结构进行补充。
struct zbar_video_s {
errinfo_t err; /* error reporting */
int fd; /* open camera device */
unsigned width, height; /* video frame size */
video_interface_t intf; /* input interface type */
video_iomode_t iomode; /* video data transfer mode */
unsigned initialized : 1; /* format selected and images mapped */
unsigned active : 1; /* current streaming state */
uint32_t format; /* selected fourcc */
unsigned palette; /* v4l1 format index corresponding to format */
uint32_t *formats; /* 0 terminated list of supported formats */
unsigned long datalen; /* size of image data for selected format */
unsigned long buflen; /* total size of image data buffer */
void *buf; /* image data buffer */
unsigned frame; /* frame count */
zbar_mutex_t qlock; /* lock image queue */
int num_images; /* number of allocated images */
zbar_image_t **images; /* indexed list of images */
zbar_image_t *nq_image; /* last image enqueued */
zbar_image_t *dq_image; /* first image to dequeue (when ordered) */
zbar_image_t *shadow_image; /* special case internal double buffering */
video_state_t *state; /* platform/interface specific state */
#ifdef HAVE_LIBJPEG
struct jpeg_decompress_struct *jpeg; /* JPEG decompressor */
zbar_image_t *jpeg_img; /* temporary image */
#endif
/* interface dependent methods */
int (*init)(zbar_video_t*, uint32_t);
int (*cleanup)(zbar_video_t*);
int (*start)(zbar_video_t*);
int (*stop)(zbar_video_t*);
int (*nq)(zbar_video_t*, zbar_image_t*);
zbar_image_t* (*dq)(zbar_video_t*);
};
在_zbar_video_recycle_image (zbar_image_t *img)函数中,还调用了assert断言函数。
简单地讲,断言就是对某种假设条件进行检查。在 C 语言中,断言被定义为宏的形式(assert(expression)),而不是函数,其原型定义在<assert.h>文件中。其中,assert 将通过检查表达式 expression 的值来决定是否需要终止执行程序。也就是说,如果表达式 expression 的值为假(即为 0),那么它将首先向标准错误流 stderr 打印一条出错信息,然后再通过调用 abort 函数终止程序运行;否则,assert 无任何作用。
默认情况下,assert 宏只有在 Debug 版本(内部调试版本)中才能够起作用,而在 Release 版本(发行版本)中将被忽略。
在对视频的逐帧图像进行循环时,我们可以看到,ZBar进行了一系列的判断工作来保证程序可靠性。首先在锁定每一帧图像时,需要判断当前视频对象是否存在以及判断当前锁定图像的索引,若不符合要求,则断言函数会终止程序运行。
锁定某一图像后,则对这个图像进行赋值和预处理,然后解锁。
下面是video.h中对图像进行锁定、解锁和预处理的函数。
/* video.next_image and video.recycle_image have to be thread safe
* wrt/other apis
*/
static inline int video_lock (zbar_video_t *vdo)
{
int rc = 0;
if((rc = _zbar_mutex_lock(&vdo->qlock))) {
err_capture(vdo, SEV_FATAL, ZBAR_ERR_LOCKING, __func__,
"unable to acquire lock");
vdo->err.errnum = rc;
return(-1);
}
return(0);
}
static inline int video_unlock (zbar_video_t *vdo)
{
int rc = 0;
if((rc = _zbar_mutex_unlock(&vdo->qlock))) {
err_capture(vdo, SEV_FATAL, ZBAR_ERR_LOCKING, __func__,
"unable to release lock");
vdo->err.errnum = rc;
return(-1);
}
return(0);
}
static inline int video_nq_image (zbar_video_t *vdo,
zbar_image_t *img)
{
/* maintains queued buffers in order */
img->next = NULL;
if(vdo->nq_image)
vdo->nq_image->next = img;
vdo->nq_image = img;
if(!vdo->dq_image)
vdo->dq_image = img;
return(video_unlock(vdo));
}
在对图像进行锁定和解锁时,都必须保证线程安全,即不允许出现互斥情况,即有一张图像以上同时进入临界区。简言之,就是同一个视频流中,不允许多张图像被锁定。
而 video_nq_image (zbar_video_t *vdo, zbar_image_t *img)则是负责对图像进行预处理,即进行循环的简单处理。它的工作主要包括,将上一张被锁定的图像所占用的资源交给下一张即将被锁定的图像。
static void _zbar_video_recycle_shadow (zbar_image_t *img)
{
zbar_video_t *vdo = img->src;
assert(vdo);
assert(img->srcidx == -1);
video_lock(vdo);
img->next = vdo->shadow_image;
vdo->shadow_image = img;
video_unlock(vdo);
}
在对视频的处理过程中,不仅需要对图像进行逐帧循环,对视频中的阴影部分也需要逐帧处理,用于筛选出有用的扫码区域。
对阴影部分的处理逻辑与_zbar_video_recycle_image (zbar_image_t *img)函数的逻辑大致相同,需要利用断言函数判断当前传入视频图像是否存在以及对图像的下标进行合理性判断,之后对阴影区域进行锁定、循环处理和解锁操作。
本次博客对ZBar扫描器的Video模块的几个关键函数进行了分析。如有不足,敬请指正。