ZBar源码分析——image.c | 2021SC@SDUSC

2021SC@SDUSC

一、Image 模块

ZBar的工作流程在以往的博客中做过一些介绍,在前几次的博客分析中也对video input部分和image scanner部分的一些代码进行了分析。在分析的过程中,我们发现,ZBar的Image模块起着不可或缺的作用,所有的分析和解码工作都是建立在图像的基础上完成的。

从工作流程图中我们也能看出,视频输入后,需要转换成图像的形式进行后续分析,并不是之间对视频进行处理。 在之前的博客中,也对Image模块的部分代码进行过简单介绍和分析,这篇博客将从image.c展开,对Image模块进行探索。

二、代码分析

首先对zbar_image_t的结构(ZBar图像的数据结构)进行说明。这在前面的博客中提及过。

 
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; /*解码结果集*/

检索、扫描并裁剪矩形

void zbar_image_set_crop (zbar_image_t *img,
                          unsigned x,
                          unsigned y,
                          unsigned w,
                          unsigned h)
{
    unsigned img_w = img->width;
    if(x > img_w) x = img_w;
    if(x + w > img_w) w = img_w - x;
    img->crop_x = x;
    img->crop_w = w;

    unsigned img_h = img->height;
    if(y > img_h) y = img_h;
    if(y + h > img_h) h = img_h - y;
    img->crop_y = y;
    img->crop_h = h;
}

在前面的代码分析中提到,BZar在对图像进行扫描、解码等操作时,都是基于矩形进行的。这个函数将识别得到的图像中条码附近的矩形区域进行了处理,将不包含条码的区域裁剪掉,得到需要处理的区域。

图像内存释放

inline void zbar_image_free_data (zbar_image_t *img)
{
    if(!img)
        return;
    if(img->src) {
        zbar_image_t *newimg;
        /* replace video image w/new copy */
        assert(img->refcnt); /* FIXME needs lock */
        newimg = zbar_image_create();
        memcpy(newimg, img, sizeof(zbar_image_t));
        /* recycle video image */
        newimg->cleanup(newimg);
        /* detach old image from src */
        img->cleanup = NULL;
        img->src = NULL;
        img->srcidx = -1;
    }
    else if(img->cleanup && img->data) {
        if(img->cleanup != zbar_image_free_data) {
            /* using function address to detect this case is a bad idea;
             * windows link libraries add an extra layer of indirection...
             * this works around that problem (bug #2796277)
             */
            zbar_image_cleanup_handler_t *cleanup = img->cleanup;
            img->cleanup = zbar_image_free_data;
            cleanup(img);
        }
        else
            free((void*)img->data);
    }
    img->data = NULL;
}

接下来分析的函数负责释放图像内存。首先注意到,这个函数是内联函数。

内联函数

 在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。

为了解决这个问题,特别的引入了inline修饰符,表示为内联函数。

栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间

ZBar项目中特地将释放图像内存的函数声明为内联函数,说明该函数在ZBar项目的运行过程中将被频繁调用,换言之,ZBar的运行过程中将频繁对图像进行创建和释放。这也在某种程度上体现了Image模块对ZBar项目的重要性。

回到函数本身,在释放图像内存时,首先判断当前图像是否处在一个视频文件的逐帧迭代中(由src属性确定),如果是,则更换带有新副本的视频图像,将下一帧图像需要的资源进行内存拷贝,并对当前图像的索引进行重置。如果当前图像并不处在一个视频文件的逐帧迭代中,或是以及完成迭代,则检查其中关联的数据是否被释放,如果未被释放,则需要先释放这些数据,否则会产生空指针异常。这些检查工作完成后,方可释放图像内存。

值得一提的是,上述函数中提及了memcpy()函数:

函数原型为:void *memcpy(void *destin, void *source, unsigned n);

参数:

  • destin-- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。

  • source-- 指向要复制的数据源,类型强制转换为 void* 指针。

  • n-- 要被复制的字节数。

该函数返回一个指向目标存储区destin的指针。

它实现了从源source所指的内存地址的起始位置开始拷贝n个字节到目标destin所指的内存地址的起始位置中。

图像写入文件

typedef struct zimg_hdr_s {
    uint32_t magic, format;
    uint16_t width, height;
    uint32_t size;
} zimg_hdr_t;

int zbar_image_write (const zbar_image_t *img,
                      const char *filebase)
{
    int len = strlen(filebase) + 16;
    char *filename = malloc(len);
    int n = 0, rc = 0;
    FILE *f;
    zimg_hdr_t hdr;
    strcpy(filename, filebase);
    if((img->format & 0xff) >= ' ')
        n = snprintf(filename, len, "%s.%.4s.zimg",
                     filebase, (char*)&img->format);
    else
        n = snprintf(filename, len, "%s.%08" PRIx32 ".zimg",
                     filebase, img->format);
    assert(n < len - 1);
    filename[len - 1] = '\0';

    zprintf(1, "dumping %.4s(%08" PRIx32 ") image to %s\n",
            (char*)&img->format, img->format, filename);

    f = fopen(filename, "w");
    if(!f) {
#ifdef HAVE_ERRNO_H
        rc = errno;
        zprintf(1, "ERROR opening %s: %s\n", filename, strerror(rc));
#else
        rc = 1;
#endif
        goto error;
    }

    hdr.magic = 0x676d697a;
    hdr.format = img->format;
    hdr.width = img->width;
    hdr.height = img->height;
    hdr.size = img->datalen;

    if(fwrite(&hdr, sizeof(hdr), 1, f) != 1 ||
       fwrite(img->data, 1, img->datalen, f) != img->datalen) {
#ifdef HAVE_ERRNO_H
        rc = errno;
        zprintf(1, "ERROR writing %s: %s\n", filename, strerror(rc));
#else
        rc = 1;
#endif
        fclose(f);
        goto error;
    }

    rc = fclose(f);

error:
    free(filename);
    return(rc);
}

对ZBar工作流程进行简单分析可以得知,ZBar对图像进行获取后,会在内存中以临时文件的形式进行保存,便于后续扫描器和解码器的使用。

在实现该函数时,首先声明了图像的渲染格式,即 zimg_hdr_s,所有的图像都会以该数据结构规整后的格式存入。代码中将渲染格式中的magic属性规定为0x676d697a,这也许是图像的统一色调。

该函数开始时,对图像文件创建一份副本,副本的文件大小=原文件大小+16。这是为了防止在后续的处理过程中,对图像文件进行了增量而导致申请的内存不够出现数据丢失的问题。

该函数的后续处理较为简单,通过几个分支对不同格式的图像进行分类保存。

三、总结

本次博客对ZBar扫描器的Image模块的几个关键函数进行了分析,也是对Video模块的一些补充说明。如有不足,敬请指正。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mai゛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值