ZBar源码分析——video.c(二) | 2021SC@SDUSC

本文详细分析了ZBar库中Video模块的关键函数,包括视频对象的创建与销毁、获取相机设备状态、视频初始化以及视频流的图像迭代过程。在视频对象的创建中,涉及了资源的预分配和错误处理;销毁时,确保所有占用资源被正确释放。视频初始化着重于图像格式的设定,为解码做好准备;图像迭代则关注于在避免资源死锁的情况下返回视频帧的副本。
摘要由CSDN通过智能技术生成

2021SC@SDUSC

目录

一、Video模块

二、代码分析

ZBar对视频流的创建和销毁

获取相机设备状态

视频初始化(预处理)

视频流的图像迭代

三、总结


一、Video模块

我们知道,扫描时提供给ZBar的不都是静态的图片,也有可能是动态的视频。例如我们日常生活中调用的微信扫码,所提供的也是视频video,这时便需要Zbar对视频进行分析,动态扫码。

Video模块是ZBar实现对读入视频进行扫描分析的功能模块。核心代码由video.h和video.c组成,video.h包括对一些关键变量的声明和结构体的定义,同时也是其他部件对Video模块的调用接口(头文件),而具体功能代码则是在video.c中实现。


二、代码分析

上篇博客对ZBar关于图像和视频之间的转换和处理部分的代码进行了简要分析,这篇博客将继上篇博客往下分析。

ZBar对视频流的创建和销毁

zbar_video_t *zbar_video_create ()
{
    zbar_video_t *vdo = calloc(1, sizeof(zbar_video_t));
    int i;
    if(!vdo)
        return(NULL);
    err_init(&vdo->err, ZBAR_MOD_VIDEO);
    vdo->fd = -1;

    (void)_zbar_mutex_init(&vdo->qlock);

    /* pre-allocate images */
    vdo->num_images = ZBAR_VIDEO_IMAGES_MAX;
    vdo->images = calloc(ZBAR_VIDEO_IMAGES_MAX, sizeof(zbar_image_t*));
    if(!vdo->images) {
        zbar_video_destroy(vdo);
        return(NULL);
    }

    for(i = 0; i < ZBAR_VIDEO_IMAGES_MAX; i++) {
        zbar_image_t *img = vdo->images[i] = zbar_image_create();
        if(!img) {
            zbar_video_destroy(vdo);
            return(NULL);
        }
        img->refcnt = 0;
        img->cleanup = _zbar_video_recycle_image;
        img->srcidx = i;
        img->src = vdo;
    }

    return(vdo);
}

首先为视频对象zbar_video_t *vdo申请内存,大小为zbar_video_所需内存。

若申请成功,则将相机状态置为打开(属性fd决定),若未申请成功,则返回NULL。

打开相机后,则代表着相机资源已被占用,其他视频对象不可使用相机资源。

这一功能由函数_zbar_mutex_init(&vdo->qlock)来实现,该函数在上篇博客中简单提到过,它实现了视频对象的上锁,即当前状态下不可产生新的视频对象(调用该函数),以免产生资源互斥。

接下来对图像进行预分配。

Zbar采用的处理方式是设定一个图像数量的最大值,然后将视频的图像数量置为该值并申请内存。

接下来根据这个最大值对视频对象进行遍历,对视频中包含的所有扫描得到的图像进行保存和创建,申请内存。并对每一张图像的进行计数、索引,以便调用和分析。

注意到,如果某一张图像出现异常、损坏,ZBar则直接销毁整个视频对象。

void zbar_video_destroy (zbar_video_t *vdo)
{
    if(vdo->intf != VIDEO_INVALID)
        zbar_video_open(vdo, NULL);
    if(vdo->images) {
        int i;
        for(i = 0; i < ZBAR_VIDEO_IMAGES_MAX; i++)
            if(vdo->images[i])
                _zbar_image_free(vdo->images[i]);
        free(vdo->images);
    }
    while(vdo->shadow_image) {
        zbar_image_t *img = vdo->shadow_image;
        vdo->shadow_image = img->next;
        free((void*)img->data);
        img->data = NULL;
        free(img);
    }
    if(vdo->buf)
        free(vdo->buf);
    if(vdo->formats)
        free(vdo->formats);
    err_cleanup(&vdo->err);
    _zbar_mutex_destroy(&vdo->qlock);

#ifdef HAVE_LIBJPEG
    if(vdo->jpeg_img) {
        zbar_image_destroy(vdo->jpeg_img);
        vdo->jpeg_img = NULL;
    }
    if(vdo->jpeg) {
        _zbar_jpeg_decomp_destroy(vdo->jpeg);
        vdo->jpeg = NULL;
    }
#endif
    free(vdo);
}

当ZBar需要销毁一个视频对象时,同样不能直接调用free函数释放内存,需要对将该视频对象占用的资源(包括占用的图像数组等)进行销毁后,才能释放视频对象。

这个函数的代码较为浅显,做的工作就是遍历视频对象调用过的图像和阴影部分图像,将其占用内存逐一释放后,最后free(vdo)释放视频对象。

获取相机设备状态

int zbar_video_get_fd (const zbar_video_t *vdo)
{
    if(vdo->intf == VIDEO_INVALID)
        return(err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
                           "video device not opened"));
    if(vdo->intf != VIDEO_V4L2)
        return(err_capture(vdo, SEV_WARNING, ZBAR_ERR_UNSUPPORTED, __func__,
                           "video driver does not support polling"));
    return(vdo->fd);
}

该函数对相机状态的两种异常情况进行了异常处理:视频设备未打开和视频驱动程序不支持轮询。

视频初始化(预处理)

int zbar_video_init (zbar_video_t *vdo,
                     unsigned long fmt)
{
#ifdef HAVE_LIBJPEG
    const zbar_format_def_t *vidfmt;
#endif
    if(vdo->initialized)
        /* FIXME re-init different format? */
        return(err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
                           "already initialized, re-init unimplemented"));

    if(vdo->init(vdo, fmt))
        return(-1);
    vdo->format = fmt;
    if(video_init_images(vdo))
        return(-1);
#ifdef HAVE_LIBJPEG
    vidfmt = _zbar_format_lookup(fmt);
    if(vidfmt && vidfmt->group == ZBAR_FMT_JPEG) {
        zbar_image_t *img;
        /* prepare for decoding */
        if(!vdo->jpeg)
            vdo->jpeg = _zbar_jpeg_decomp_create();
        if(vdo->jpeg_img)
            zbar_image_destroy(vdo->jpeg_img);

        /* create intermediate image for decoder to use*/
        img = vdo->jpeg_img = zbar_image_create();
        img->format = fourcc('Y','8','0','0');
        zbar_image_set_size(img, vdo->width, vdo->height);
        img->datalen = vdo->width * vdo->height;
    }
#endif
    vdo->initialized = 1;
    return(0);
}

在对视频进行初始化时,ZBar首先判断了该视频是否被初始化过,如果被初始化过,则判定一种异常情况,需要对该视频格式进行重新修正。这一点在我们写代码的过程中容易被忽略,值得学习。

接下来则是对视频进行预处理(创建解码器所需要的视频/图像格式),用于下一步的解码操作。

这里ZBar设定了编码格式为Y800。

对于单色的图像,Y800格式仅包含一个8位Y平面。它的FourCC编码中包含重复的为Y8和灰色对应的编码。这部分将在解码部分的代码中进行分析。

视频流的图像迭代

zbar_image_t *zbar_video_next_image (zbar_video_t *vdo)
{
    unsigned frame;
    zbar_image_t *img;

    if(video_lock(vdo))
        return(NULL);
    if(!vdo->active) {
        video_unlock(vdo);
        return(NULL);
    }

    frame = vdo->frame++;
    img = vdo->dq(vdo);
    if(img) {
        img->seq = frame;
        if(vdo->num_images < 2) {
            /* return a *copy* of the video image and immediately recycle
             * the driver's buffer to avoid deadlocking the resources
             */
            zbar_image_t *tmp = img;
            video_lock(vdo);
            img = vdo->shadow_image;
            vdo->shadow_image = (img) ? img->next : NULL;
            video_unlock(vdo);
                
            if(!img) {
                img = zbar_image_create();
                assert(img);
                img->refcnt = 0;
                img->src = vdo;
                /* recycle the shadow images */

                img->format = vdo->format;
                zbar_image_set_size(img, vdo->width, vdo->height);
                img->datalen = vdo->datalen;
                img->data = malloc(vdo->datalen);
            }
            img->cleanup = _zbar_video_recycle_shadow;
            img->seq = frame;
            memcpy((void*)img->data, tmp->data, img->datalen);
            _zbar_video_recycle_image(tmp);
        }
        else
            img->cleanup = _zbar_video_recycle_image;
        _zbar_image_refcnt(img, 1);
    }
    return(img);
}

这部分功能实现的核心在于迭代过程中需要返回视频图像的副本并立即回收驱动程序的缓冲区,以避免资源死锁。

这部分代码与操作系统中的信号量机制类似,对于每一张图像的循环,必须保证缓冲区的有序使用,这就需要对缓冲区进行上锁和解锁。

对于缓冲区的资源,图像之间需要进行递交,在递交过程中,需要借助到tmp暂时图像,在递交结束之后,需要对tmp进行回收,否则会产生相当大的资源浪费。


三、总结

本次博客对ZBar扫描器的Video模块的几个关键函数进行了分析。如有不足,敬请指正。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mai゛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值