[TOC]
一、Video模块
我们知道,扫描时提供给ZBar的不都是静态的图片,也有可能是动态的视频。例如我们日常生活中调用的微信扫码,所提供的也是视频video,这时便需要Zbar对视频进行分析,动态扫码。
Video模块是ZBar实现对读入视频进行扫描分析的功能模块。核心代码由video.h和video.c组成,video.h包括对一些关键变量的声明和结构体的定义,同时也是其他部件对Video模块的调用接口(头文件),而具体功能代码则是在video.c中实现。
二、代码分析
上篇博客对ZBar关于图像和视频之间的转换和处理部分的代码进行了简要分析,这篇博客将继上篇博客往下分析。
ZBar对视频流的创建和销毁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| `
1. zbar_video_t *zbar_video_create () 2. { 3. zbar_video_t *vdo = calloc(1, sizeof(zbar_video_t)); 4. int i; 5. if(!vdo) 6. return(NULL); 7. err_init(&vdo->err, ZBAR_MOD_VIDEO); 8. vdo->fd = -1;
10. (void)_zbar_mutex_init(&vdo->qlock);
12. /* pre-allocate images */ 13. vdo->num_images = ZBAR_VIDEO_IMAGES_MAX; 14. vdo->images = calloc(ZBAR_VIDEO_IMAGES_MAX, sizeof(zbar_image_t*)); 15. if(!vdo->images) { 16. zbar_video_destroy(vdo); 17. return(NULL); 18. }
20. for(i = 0; i < ZBAR_VIDEO_IMAGES_MAX; i++) { 21. zbar_image_t *img = vdo->images[i] = zbar_image_create(); 22. if(!img) { 23. zbar_video_destroy(vdo); 24. return(NULL); 25. } 26. img->refcnt = 0; 27. img->cleanup = _zbar_video_recycle_image; 28. img->srcidx = i; 29. img->src = vdo; 30. }
32. return(vdo); 33. }
`

|
首先为视频对象zbar_video_t *vdo申请内存,大小为zbar_video_所需内存。
若申请成功,则将相机状态置为打开(属性fd决定),若未申请成功,则返回NULL。
打开相机后,则代表着相机资源已被占用,其他视频对象不可使用相机资源。
这一功能由函数_zbar_mutex_init(&vdo->qlock)来实现,该函数在上篇博客中简单提到过,它实现了视频对象的上锁,即当前状态下不可产生新的视频对象(调用该函数),以免产生资源互斥。
接下来对图像进行预分配。
Zbar采用的处理方式是设定一个图像数量的最大值,然后将视频的图像数量置为该值并申请内存。
接下来根据这个最大值对视频对象进行遍历,对视频中包含的所有扫描得到的图像进行保存和创建,申请内存。并对每一张图像的进行计数、索引,以便调用和分析。
注意到,如果某一张图像出现异常、损坏,ZBar则直接销毁整个视频对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| `
1. void zbar_video_destroy (zbar_video_t *vdo) 2. { 3. if(vdo->intf != VIDEO_INVALID) 4. zbar_video_open(vdo, NULL); 5. if(vdo->images) { 6. int i; 7. for(i = 0; i < ZBAR_VIDEO_IMAGES_MAX; i++) 8. if(vdo->images[i]) 9. _zbar_image_free(vdo->images[i]); 10. free(vdo->images); 11. } 12. while(vdo->shadow_image) { 13. zbar_image_t *img = vdo->shadow_image; 14. vdo->shadow_image = img->next; 15. free((void*)img->data); 16. img->data = NULL; 17. free(img); 18. } 19. if(vdo->buf) 20. free(vdo->buf); 21. if(vdo->formats) 22. free(vdo->formats); 23. err_cleanup(&vdo->err); 24. _zbar_mutex_destroy(&vdo->qlock);
26. #ifdef HAVE_LIBJPEG 27. if(vdo->jpeg_img) { 28. zbar_image_destroy(vdo->jpeg_img); 29. vdo->jpeg_img = NULL; 30. } 31. if(vdo->jpeg) { 32. _zbar_jpeg_decomp_destroy(vdo->jpeg); 33. vdo->jpeg = NULL; 34. } 35. #endif 36. free(vdo); 37. }
`

|
当ZBar需要销毁一个视频对象时,同样不能直接调用free函数释放内存,需要对将该视频对象占用的资源(包括占用的图像数组等)进行销毁后,才能释放视频对象。
这个函数的代码较为浅显,做的工作就是遍历视频对象调用过的图像和阴影部分图像,将其占用内存逐一释放后,最后free(vdo)释放视频对象。
获取相机设备状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 1. int zbar_video_get_fd (const zbar_video_t *vdo) 2. { 3. if(vdo->intf == VIDEO_INVALID) 4. return(err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, 5. "video device not opened")); 6. if(vdo->intf != VIDEO_V4L2) 7. return(err_capture(vdo, SEV_WARNING, ZBAR_ERR_UNSUPPORTED, __func__, 8. "video driver does not support polling")); 9. return(vdo->fd); 10. }
|
该函数对相机状态的两种异常情况进行了异常处理:视频设备未打开和视频驱动程序不支持轮询。
视频初始化(预处理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| `
1. int zbar_video_init (zbar_video_t *vdo, 2. unsigned long fmt) 3. { 4. #ifdef HAVE_LIBJPEG 5. const zbar_format_def_t *vidfmt; 6. #endif 7. if(vdo->initialized) 8. /* FIXME re-init different format? */ 9. return(err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, 10. "already initialized, re-init unimplemented"));
12. if(vdo->init(vdo, fmt)) 13. return(-1); 14. vdo->format = fmt; 15. if(video_init_images(vdo)) 16. return(-1); 17. #ifdef HAVE_LIBJPEG 18. vidfmt = _zbar_format_lookup(fmt); 19. if(vidfmt && vidfmt->group == ZBAR_FMT_JPEG) { 20. zbar_image_t *img; 21. /* prepare for decoding */ 22. if(!vdo->jpeg) 23. vdo->jpeg = _zbar_jpeg_decomp_create(); 24. if(vdo->jpeg_img) 25. zbar_image_destroy(vdo->jpeg_img);
27. /* create intermediate image for decoder to use*/ 28. img = vdo->jpeg_img = zbar_image_create(); 29. img->format = fourcc('Y','8','0','0'); 30. zbar_image_set_size(img, vdo->width, vdo->height); 31. img->datalen = vdo->width * vdo->height; 32. } 33. #endif 34. vdo->initialized = 1; 35. return(0); 36. }
`

|
在对视频进行初始化时,ZBar首先判断了该视频是否被初始化过,如果被初始化过,则判定一种异常情况,需要对该视频格式进行重新修正。这一点在我们写代码的过程中容易被忽略,值得学习。
接下来则是对视频进行预处理(创建解码器所需要的视频/图像格式),用于下一步的解码操作。
这里ZBar设定了编码格式为Y800。
对于单色的图像,Y800格式仅包含一个8位Y平面。它的FourCC编码中包含重复的为Y8和灰色对应的编码。这部分将在解码部分的代码中进行分析。
视频流的图像迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| `
1. zbar_image_t *zbar_video_next_image (zbar_video_t *vdo) 2. { 3. unsigned frame; 4. zbar_image_t *img;
6. if(video_lock(vdo)) 7. return(NULL); 8. if(!vdo->active) { 9. video_unlock(vdo); 10. return(NULL); 11. }
13. frame = vdo->frame++; 14. img = vdo->dq(vdo); 15. if(img) { 16. img->seq = frame; 17. if(vdo->num_images < 2) { 18. /* return a *copy* of the video image and immediately recycle 19. * the driver's buffer to avoid deadlocking the resources 20. */ 21. zbar_image_t *tmp = img; 22. video_lock(vdo); 23. img = vdo->shadow_image; 24. vdo->shadow_image = (img) ? img->next : NULL; 25. video_unlock(vdo);
27. if(!img) { 28. img = zbar_image_create(); 29. assert(img); 30. img->refcnt = 0; 31. img->src = vdo; 32. /* recycle the shadow images */
34. img->format = vdo->format; 35. zbar_image_set_size(img, vdo->width, vdo->height); 36. img->datalen = vdo->datalen; 37. img->data = malloc(vdo->datalen); 38. } 39. img->cleanup = _zbar_video_recycle_shadow; 40. img->seq = frame; 41. memcpy((void*)img->data, tmp->data, img->datalen); 42. _zbar_video_recycle_image(tmp); 43. } 44. else 45. img->cleanup = _zbar_video_recycle_image; 46. _zbar_image_refcnt(img, 1); 47. } 48. return(img); 49. }
`

|
这部分功能实现的核心在于迭代过程中需要返回视频图像的副本并立即回收驱动程序的缓冲区,以避免资源死锁。
这部分代码与操作系统中的信号量机制类似,对于每一张图像的循环,必须保证缓冲区的有序使用,这就需要对缓冲区进行上锁和解锁。
对于缓冲区的资源,图像之间需要进行递交,在递交过程中,需要借助到tmp暂时图像,在递交结束之后,需要对tmp进行回收,否则会产生相当大的资源浪费。
三、总结
本次博客对ZBar扫描器的Video模块的几个关键函数进行了分析。如有不足,敬请指正。