• 达芬奇TI DVSDK之视频数据流过程分析


    作者:openwince@gmail.com
    博客:http://www.cnblogs.com/tinz
     
     
    本文的copyright归openwince@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
    ========================================================
    本文讨论的是基于DVSDK3,硬件平台是ZMV6467。
     
    DVSDK TI为达芬奇平台的开发者提供了一套完善的视频软件开发套件。DVSDK功能非常强大,它将很多视频编解码的细节都隐藏起来了,使用者只需要
    关系应用就可以,但是DVSDK的强大又使得它变得异常的庞大,让使用者摸不着头脑,或者根本不知道怎样开始开发。接下来的TI DVSDK学习记录文章
    将会一步步分析DVSDK,将我学习使用DVSDK进行开发的过程中碰到的问题或者相关的经验一一写出,文章中不会涉及到如何构建编译环境,如何编译
    DVSDK的问题,如需了解这方面的问题可以参考TI官方文档或联系勤泽。文章如有错误敬请提出。
     
    以视频采集应用的开发过程,提出以下问题:
    1.视频怎样来,来自哪里?
    2.采集到视频数据要怎样处理?(显示/压缩/存放/发送)
    3.视频数据怎样直接显示出来?
    4.视频数据怎样压缩?
    5.视频数据怎样存放?
    6.视频数据如何发送?
     
     下面就开始一一来解决这些问题:
     
    先从硬件功能框图看一下视频的输入通道,红色框部分:

    draw by duzeming

     

     DM6467支持BT.656/BT.1120/RAW的数据输入,最高可以支持1080P30FPS,如果需要支持1080P60fps,可以使用1GHz版本的DM6467T。
    1)模拟高清分量输入,经过视频解码器后输出符合BT.1120的数据,然后再通过VPIF接口进入CPU。
    2)模拟标清信号输入,经过视频解码器后输出符合BT.656的数据,然后再通过VPIF接口进入CPU。
    3)数字摄像头(eg.cmos),直接通过VPIF接口进入CPU。这里要注意,因为DM6467只支持YUVPSemi4:2:0格式的数据显示和编码(H.264),所以数字摄像头输出的YCBCR或RGB数据再
    进入CPU后需要进行转换(颜色空间的问题,可以参考本博客前面的文章),这个转换可以通过ARM,也可以通过DSP来进行,勤泽的软件已经附带了一个DSP的转换核,
    可以直接转换cmos的YCBCR数据进行显示和编码。
     
    数据进入CPU后,接下来就是要获取数据了,这部分的只使用到ARM端。
    数据流向:
    [video decoder driver] ----> [vpif_caputre driver] ---->[V4L2 driver]--->[应用层]
    这里我们先不去关心video decoder driver 和  vpif_caputre driver,因为这两个不影响我们分析DVSDK。
     
    这里的V4L2 driver和我们平常使用的V4L2驱动没什么区别,就是提供了一些访问视频设备的接口,但是TI为了
    简化对V4L2的操作,方便用户更快的编写出视频应用程序,所以他们在V4L2的基础上又封装了一层叫做DMAI的东西。DMAI是什么呢?
    因为接下来会有更多的新名词冒出,所以这里我们先看一下DVSDK的一个简单的构成框图,这个框图在后面会不断扩大:
     

     
    从框图可以看出DMAI将视频编解码器(CE)、内存管理(CMEM)和V4L2的访问操作都统一到一套接口上了,这样视频编解码应用程序的开发
    者只需要关系数据的业务应用而不需要去理会逻辑层的东西。
     
    但是作为底层的开发人员,我们还是有必要去看看DMAI到底是怎样和CE/V4L2/CMEM进行交互,并且把它们封装起来的,然后到最后再看怎样使
    用DMAI进行应用程序的开发。
     
    1.DMAI与VL42的交互(DMAI版本:2_20_00_15)。
    在dmai_2_20_00_15/packages/ti/sdo/dmai/linux/dm6467目录下有一个Capture.c的文件,Capture.c包含了所有对V4L2的操作封装。现在来看一下
    文件中的一个函数,这个函数用于创建一个 视频捕获设备的操作句柄
    View Code
    复制代码
      1 /******************************************************************************
      2  * Capture_create
      3  ******************************************************************************/
      4 Capture_Handle Capture_create(BufTab_Handle hBufTab, Capture_Attrs *attrs)
      5 {
      6     struct v4l2_capability      cap;
      7     struct v4l2_cropcap         cropCap;
      8     struct v4l2_crop            crop;
      9     struct v4l2_format          fmt;
     10     enum v4l2_buf_type          type;
     11     Capture_Handle              hCapture;
     12     VideoStd_Type               videoStd;
     13     Int32                       width, height;
     14     Uint32                      pixelFormat;
     15 
     16     assert(attrs);
     17     Dmai_clear(fmt);
     18 
     19     /* Allocate space for state object */
     20     hCapture = calloc(1, sizeof(Capture_Object));
     21 
     22     if (hCapture == NULL) {
     23         Dmai_err0("Failed to allocate space for Capture Object
    ");
     24         return NULL;
     25     }
     26 
     27     /* User allocated buffers by default */
     28     hCapture->userAlloc = TRUE;
     29 
     30     /* Open video capture device */
     31     /* 打开V4L2视频输入设备 */
     32     hCapture->fd = open(attrs->captureDevice, O_RDWR, 0);
     33 
     34     if (hCapture->fd == -1) {
     35         Dmai_err2("Cannot open %s (%s)
    ", attrs->captureDevice,
     36                                            strerror(errno));
     37         cleanup(hCapture);
     38         return NULL;
     39     }
     40 
     41     /* See if an input is connected, and if so which standard */
     42     /* 检测V4L2设备当前是否有视频信号输入,输入信号的标准 */
     43     if (Capture_detectVideoStd(hCapture, &videoStd, attrs) < 0) {
     44         cleanup(hCapture);
     45         return NULL;
     46     }
     47 
     48     hCapture->videoStd = videoStd;
     49 
     50     if (VideoStd_getResolution(videoStd, &width, &height) < 0) {
     51         cleanup(hCapture);
     52         Dmai_err0("Failed to get resolution of capture video standard
    ");
     53         return NULL;
     54     }
     55 
     56     /* Query for capture device capabilities */
     57     /* 这里就是调用ioctl直接操作v4l2设备了,查询设备的特性*/
     58     if (ioctl(hCapture->fd, VIDIOC_QUERYCAP, &cap) == -1) {
     59         cleanup(hCapture);
     60         if (errno == EINVAL) {
     61             Dmai_err1("%s is no V4L2 device
    ", attrs->captureDevice);
     62             cleanup(hCapture);
     63             return NULL;
     64         }
     65         Dmai_err2("Failed VIDIOC_QUERYCAP on %s (%s)
    ", attrs->captureDevice,
     66                                                        strerror(errno));
     67         cleanup(hCapture);
     68         return NULL;
     69     }
     70 
     71     if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
     72         Dmai_err1("%s is not a video capture device
    ", attrs->captureDevice);
     73         cleanup(hCapture);
     74         return NULL;
     75     }
     76 
     77     if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
     78         Dmai_err1("%s does not support streaming i/o
    ", attrs->captureDevice);
     79         cleanup(hCapture);
     80         return NULL;
     81     }
     82 
     83     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     84     /* 获取V4L2设备的帧格式 */
     85     if (ioctl(hCapture->fd, VIDIOC_G_FMT, &fmt) == -1) {
     86         Dmai_err2("Failed VIDIOC_G_FMT on %s (%s)
    ", attrs->captureDevice,
     87                                                       strerror(errno));
     88         cleanup(hCapture);
     89         return NULL;
     90     }
     91 
     92     fmt.fmt.pix.width        = width;
     93     fmt.fmt.pix.height       = height; 
     94     fmt.type                 = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     95 
     96     switch(attrs->colorSpace) {
     97         case ColorSpace_UYVY: 
     98             fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
     99             break;
    100         case ColorSpace_YUV420PSEMI:
    101             fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
    102             break;
    103         case ColorSpace_YUV422PSEMI:
    104             fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV16;
    105             break;
    106         default:
    107             Dmai_err1("Unsupported color format %g
    ", attrs->colorSpace);            
    108             cleanup(hCapture);
    109             return NULL;
    110     }
    111 
    112     if ((videoStd == VideoStd_BAYER_CIF) || (videoStd == VideoStd_BAYER_VGA) || 
    113         (videoStd == VideoStd_BAYER_1280)) {
    114         fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR8;
    115     } 
    116 
    117     fmt.fmt.pix.bytesperline = BufferGfx_calcLineLength(fmt.fmt.pix.width, 
    118                                     attrs->colorSpace);
    119     fmt.fmt.pix.sizeimage    = BufferGfx_calcSize(attrs->videoStd, attrs->colorSpace);
    120 
    121     //printf("DMAI: pix.bytesperline= %d, pix.sizeimage= %d
    ",fmt.fmt.pix.bytesperline,fmt.fmt.pix.sizeimage);
    122     pixelFormat = fmt.fmt.pix.pixelformat;
    123     
    124     if ((videoStd == VideoStd_CIF) || (videoStd == VideoStd_SIF_PAL) || 
    125         (videoStd == VideoStd_SIF_NTSC) || (videoStd == VideoStd_D1_PAL) ||
    126         (videoStd == VideoStd_D1_NTSC) || (videoStd == VideoStd_1080I_30) ||
    127         (videoStd == VideoStd_1080I_25)) {
    128         fmt.fmt.pix.field        = V4L2_FIELD_INTERLACED;
    129     } else {
    130         fmt.fmt.pix.field        = V4L2_FIELD_NONE;
    131     }
    132 
    133         /* 设置V4L2输入设备的帧格式 */
    134     if (ioctl(hCapture->fd, VIDIOC_S_FMT, &fmt) == -1) {
    135         printf("Failed VIDIOC_S_FMT on %s (%s)
    ", attrs->captureDevice,
    136                                                       strerror(errno));
    137         cleanup(hCapture);
    138         return NULL;
    139     }
    140 
    141     if ((fmt.fmt.pix.width != width)  || (fmt.fmt.pix.height != height)) {
    142         Dmai_err4("Failed to set resolution %d x %d (%d x %d)
    ", width,
    143                     height, fmt.fmt.pix.width, fmt.fmt.pix.height);
    144         cleanup(hCapture);
    145         return NULL;
    146     }
    147 
    148     if (pixelFormat != fmt.fmt.pix.pixelformat) {
    149         Dmai_err2("Pixel format 0x%x not supported. Received 0x%x
    ", 
    150             pixelFormat, fmt.fmt.pix.pixelformat);
    151         cleanup(hCapture);
    152         return NULL;        
    153     }
    154 
    155     Dmai_dbg3("Video input connected size %dx%d pitch %d
    ",
    156               fmt.fmt.pix.width, fmt.fmt.pix.height, fmt.fmt.pix.bytesperline);
    157 
    158     /* Query for video input cropping capability */
    159 
    160     if (attrs->cropWidth > 0 && attrs->cropHeight > 0) {
    161 
    162         cropCap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    163         if (ioctl(hCapture->fd, VIDIOC_CROPCAP, &cropCap) == -1) {
    164             Dmai_err2("VIDIOC_CROPCAP failed on %s (%s)
    ", attrs->captureDevice,
    165                                                             strerror(errno));
    166             cleanup(hCapture);
    167             return NULL;
    168         }
    169 
    170         if (attrs->cropX & 0x1) {
    171             Dmai_err1("Crop width (%ld) needs to be even
    ", attrs->cropX);
    172             cleanup(hCapture);
    173             return NULL;
    174         }
    175 
    176         crop.type     = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    177         crop.c.left   = attrs->cropX;
    178         crop.c.top    = attrs->cropY;
    179         crop.c.width  = attrs->cropWidth;
    180         crop.c.height = hCapture->topOffset ? attrs->cropHeight + 4 + 2 :
    181                                               attrs->cropHeight;
    182 
    183         Dmai_dbg4("Setting capture cropping at %dx%d size %dx%d
    ",
    184                   crop.c.left, crop.c.top, crop.c.width, crop.c.height);
    185 
    186         /* Crop the image depending on requested image size */
    187         if (ioctl(hCapture->fd, VIDIOC_S_CROP, &crop) == -1) {
    188             Dmai_err2("VIDIOC_S_CROP failed on %s (%s)
    ", attrs->captureDevice,
    189                                                            strerror(errno));
    190             cleanup(hCapture);
    191             return NULL;
    192         }
    193     }
    194 
    195     if (hBufTab == NULL) {
    196         hCapture->userAlloc = FALSE;
    197 
    198         /* The driver allocates the buffers */
    199         /* 调用CMEM创建要用到的视频缓冲区 */
    200         if (_Dmai_v4l2DriverAlloc(hCapture->fd,
    201                                   attrs->numBufs,
    202                                   V4L2_BUF_TYPE_VIDEO_CAPTURE,
    203                                   &hCapture->bufDescs,
    204                                   &hBufTab,
    205                                   hCapture->topOffset,
    206                                   attrs->colorSpace) < 0) {
    207             Dmai_err1("Failed to allocate capture driver buffers on %s
    ",
    208                       attrs->captureDevice);
    209             cleanup(hCapture);
    210             return NULL;
    211         }
    212     }
    213     else {
    214         /* Make the driver use the user supplied buffers */
    215         /* 如果调用者已经创建好缓冲区,那只需要加入相应的队列管理就可以 */
    216         if (_Dmai_v4l2UserAlloc(hCapture->fd,
    217                                 attrs->numBufs,
    218                                 V4L2_BUF_TYPE_VIDEO_CAPTURE,
    219                                 &hCapture->bufDescs,
    220                                 hBufTab,
    221                                 0, attrs->colorSpace) < 0) {
    222             Dmai_err1("Failed to intialize capture driver buffers on %s
    ",
    223                       attrs->captureDevice);
    224             cleanup(hCapture);
    225             return NULL;
    226         }
    227     }
    228 
    229     hCapture->hBufTab = hBufTab;
    230 
    231     /* Start the video streaming */
    232     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    233 
    234         /* 配置完后,启动V4L2设备 */
    235     if (ioctl(hCapture->fd, VIDIOC_STREAMON, &type) == -1) {
    236         Dmai_err2("VIDIOC_STREAMON failed on %s (%s)
    ", attrs->captureDevice,
    237                                                          strerror(errno));
    238         cleanup(hCapture);
    239         return NULL;
    240     }
    241 
    242     hCapture->started = TRUE;
    243 
    244     return hCapture;
    245 }
    复制代码

     从上面的代码可以看出,DMAI的函数将大量的对V4L2的控制操作都封装起来了,使用者只需要传入缓冲区列表和视频采集参数就可以创建一个视频采集设备。设备采集到

    数据后,会将数据填充到使用者提供的缓冲区,使用者只需要取出相应的缓冲区就可以得到视频数据。在上面的函数中,调用了一个_Dmai_v4l2DriverAlloc()函数,这个函数

    的作用是分配视频缓冲区,它最终是调用CMEM进行内存分配的,下面一起看看这个函数的具体实现。

     

    2.DMAI与CMEM的交互。

    查看dmai_2_20_00_15/packages/ti/sdo/dmai/linux/dm6467/_VideoBuf.c

    _Dmai_v4l2DriverAlloc()
    复制代码
      1 /******************************************************************************
      2  * _Dmai_v4l2DriverAlloc
      3  ******************************************************************************/
      4 Int _Dmai_v4l2DriverAlloc(Int fd, Int numBufs, enum v4l2_buf_type type,
      5                           struct _VideoBufDesc **bufDescsPtr,
      6                           BufTab_Handle *hBufTabPtr, Int topOffset, 
      7                           ColorSpace_Type colorSpace)
      8 {
      9     BufferGfx_Attrs             gfxAttrs = BufferGfx_Attrs_DEFAULT;
     10     struct v4l2_requestbuffers  req;
     11     struct v4l2_format          fmt;
     12     _VideoBufDesc              *bufDesc;
     13     Buffer_Handle               hBuf;
     14     Int                         bufIdx;
     15     Int8                       *virtPtr;
     16 
     17     Dmai_clear(fmt);
     18     fmt.type = type;
     19 
     20     if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
     21         Dmai_err1("VIDIOC_G_FMT failed (%s)
    ", strerror(errno));
     22         return Dmai_EFAIL;
     23     }
     24 
     25     Dmai_clear(req);
     26     req.count  = numBufs;
     27     req.type   = type;
     28     req.memory = V4L2_MEMORY_MMAP;
     29 
     30     /* Allocate buffers in the capture device driver */
     31     /* 申请建立V4L2的视频缓冲区管理队列*/
     32     if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
     33         Dmai_err1("VIDIOC_REQBUFS failed (%s)
    ", strerror(errno));
     34         return Dmai_ENOMEM;
     35     }
     36 
     37     if (req.count < numBufs || !req.count) {
     38         Dmai_err0("Insufficient device driver buffer memory
    ");
     39         return Dmai_ENOMEM;
     40     }
     41 
     42     /* Allocate space for buffer descriptors */
     43     *bufDescsPtr = calloc(numBufs, sizeof(_VideoBufDesc));
     44 
     45     if (*bufDescsPtr == NULL) {
     46         Dmai_err0("Failed to allocate space for buffer descriptors
    ");
     47         return Dmai_ENOMEM;
     48     }
     49 
     50     gfxAttrs.dim.width          = fmt.fmt.pix.width;
     51     gfxAttrs.dim.height         = fmt.fmt.pix.height;
     52     gfxAttrs.dim.lineLength     = fmt.fmt.pix.bytesperline;
     53     gfxAttrs.colorSpace         = colorSpace;
     54     gfxAttrs.bAttrs.reference   = TRUE;
     55 
     56         /* 调用CMEM建立缓冲区 */
     57     *hBufTabPtr = BufTab_create(numBufs, fmt.fmt.pix.sizeimage,
     58                                 BufferGfx_getBufferAttrs(&gfxAttrs));
     59 
     60     if (*hBufTabPtr == NULL) {
     61         return Dmai_ENOMEM;
     62     }
     63 
     64       /* 将建立好的缓冲区放到队列中并且配置好相关属性 */
     65     for (bufIdx = 0; bufIdx < numBufs; bufIdx++) {
     66         bufDesc = &(*bufDescsPtr)[bufIdx];
     67 
     68         /* Ask for information about the driver buffer */
     69         Dmai_clear(bufDesc->v4l2buf);
     70         bufDesc->v4l2buf.type   = type;
     71         bufDesc->v4l2buf.memory = V4L2_MEMORY_MMAP;
     72         bufDesc->v4l2buf.index  = bufIdx;
     73 
     74                 /* 查找队列中的缓冲区 */
     75         if (ioctl(fd, VIDIOC_QUERYBUF, &bufDesc->v4l2buf) == -1) {
     76             Dmai_err1("Failed VIDIOC_QUERYBUF (%s)
    ", strerror(errno));
     77             return Dmai_EFAIL;
     78         }
     79 
     80                 /* 修改缓冲区的属性 */
     81                 
     82         /* Map the driver buffer to user space */
     83         virtPtr = mmap(NULL,
     84                        bufDesc->v4l2buf.length,
     85                        PROT_READ | PROT_WRITE,
     86                        MAP_SHARED,
     87                        fd,
     88                        bufDesc->v4l2buf.m.offset) + topOffset;
     89 
     90         if (virtPtr == MAP_FAILED) {
     91             Dmai_err1("Failed to mmap buffer (%s)
    ", strerror(errno));
     92             return Dmai_EFAIL;
     93         }
     94 
     95         /* Initialize the Buffer with driver buffer information */
     96         hBuf = BufTab_getBuf(*hBufTabPtr, bufIdx);
     97 
     98         Buffer_setNumBytesUsed(hBuf, fmt.fmt.pix.bytesperline *
     99                                      fmt.fmt.pix.height);
    100         Buffer_setUseMask(hBuf, gfxAttrs.bAttrs.useMask);
    101         Buffer_setUserPtr(hBuf, virtPtr);
    102 
    103         /* Initialize buffer to black */
    104         _Dmai_blackFill(hBuf);
    105 
    106         Dmai_dbg3("Driver buffer %d mapped to %#x has physical address "
    107                   "%#lx
    ", bufIdx, (Int) virtPtr, Buffer_getPhysicalPtr(hBuf));
    108 
    109         bufDesc->hBuf = hBuf;
    110 
    111         /* Queue buffer in device driver */
    112         /* 将缓冲区放回队列中 */
    113         if (ioctl(fd, VIDIOC_QBUF, &bufDesc->v4l2buf) == -1) {
    114             Dmai_err1("VIODIOC_QBUF failed (%s)
    ", strerror(errno));
    115             return Dmai_EFAIL;
    116         }
    117     }
    118 
    119     return Dmai_EOK;
    120 }
    复制代码

    继续分析Buffer_create()函数

    查看dmai_2_20_00_15/packages/ti/sdo/dmai/Buffer.c。

    Buffer_create()
    复制代码
     1 /******************************************************************************
     2  * Buffer_create
     3  ******************************************************************************/
     4 Buffer_Handle Buffer_create(Int32 size, Buffer_Attrs *attrs)
     5 {
     6     Buffer_Handle hBuf;
     7     UInt32        objSize;
     8 
     9     if (attrs == NULL) {
    10         Dmai_err0("Must provide attrs
    ");
    11         return NULL;
    12     }
    13 
    14     if (attrs->type != Buffer_Type_BASIC &&
    15         attrs->type != Buffer_Type_GRAPHICS) {
    16 
    17         Dmai_err1("Unknown Buffer type (%d)
    ", attrs->type);
    18         return NULL;
    19     }
    20 
    21     objSize = attrs->type == Buffer_Type_GRAPHICS ? sizeof(_BufferGfx_Object) :
    22                                                     sizeof(_Buffer_Object);
    23 
    24     hBuf = (Buffer_Handle) calloc(1, objSize);
    25 
    26     if (hBuf == NULL) {
    27         Dmai_err0("Failed to allocate space for Buffer Object
    ");
    28         return NULL;
    29     }
    30 
    31     _Buffer_init(hBuf, size, attrs);
    32 
    33     if (!attrs->reference) {
    34     
    35             /* 这里就是调用了CMEM的接口进行缓冲区的创建 */
    36         hBuf->userPtr = (Int8*)Memory_alloc(size, &attrs->memParams);
    37 
    38         if (hBuf->userPtr == NULL) {
    39             printf("Failed to allocate memory.
    ");
    40             free(hBuf);
    41             return NULL;
    42         }
    43 
    44                 /* 获取缓冲区的物理地址 */
    45         hBuf->physPtr = Memory_getBufferPhysicalAddress(hBuf->userPtr,
    46                                                         size, NULL);
    47 
    48         Dmai_dbg3("Alloc Buffer of size %u at 0x%x (0x%x phys)
    ",
    49                   (Uns) size, (Uns) hBuf->userPtr, (Uns) hBuf->physPtr);
    50     }
    51 
    52     hBuf->reference = attrs->reference;
    53 
    54     return hBuf;
    55 }
    复制代码

    数据已经得到了,它们就放在通过CMEM创建的缓冲区里,那么验证或使用这些数据的一种最简单又有效的方式就是将它们直接

    显示出来。再来看一下DVSDK的框图:

     

    框图的两条红线表示数据的流向,需要将数据显示出来只需要两部,第一步创建V4L2的显示出输出设备,第二创建显示缓冲区并将采集到的数据放到缓冲区内。

    创建V4L2的显示设备和创建VL42的采集设备很相似,请查看dmai_2_20_00_15/packages/ti/sdo/dmai/linux/dm6467/Display_v4l2.c

    Display_v4l2_create()
    复制代码
      1 /******************************************************************************
      2  * Display_v4l2_create
      3  ******************************************************************************/
      4 Display_Handle Display_v4l2_create(BufTab_Handle hBufTab, Display_Attrs *attrs)
      5 {
      6     struct v4l2_format         fmt;
      7     enum v4l2_buf_type         type;
      8     Display_Handle             hDisplay;
      9 
     10     assert(attrs);
     11 
     12     Dmai_clear(fmt);
     13   
     14     /* delayStreamon not supported for this platform */
     15     if (attrs->delayStreamon == TRUE) {
     16         Dmai_err0("Support for delayed VIDIOC_STREAMON not implemented
    ");
     17         return NULL;
     18     }
     19 
     20     /* Allocate space for state object */
     21     hDisplay = calloc(1, sizeof(Display_Object));
     22 
     23     if (hDisplay == NULL) {
     24         Dmai_err0("Failed to allocate space for Display Object
    ");
     25         return NULL;
     26     }
     27 
     28     hDisplay->userAlloc = TRUE;
     29 
     30     /* Open video capture device */
     31     hDisplay->fd = open(attrs->displayDevice, O_RDWR, 0);
     32 
     33     if (hDisplay->fd == -1) {
     34         Dmai_err2("Cannot open %s (%s)
    ",
     35                   attrs->displayDevice, strerror(errno));
     36         cleanup(hDisplay);
     37         return NULL;
     38     }
     39 
     40     if(Display_detectVideoStd(hDisplay, attrs) != Dmai_EOK) {
     41         Dmai_err0("Display_detectVideoStd Failed
    ");
     42         cleanup(hDisplay);
     43         return NULL;
     44     }
     45     /* Determine the video image dimensions */
     46     fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
     47 
     48     if (ioctl(hDisplay->fd, VIDIOC_G_FMT, &fmt) == -1) {
     49         Dmai_err0("Failed to determine video display format
    ");
     50         cleanup(hDisplay);
     51         return NULL;
     52     }
     53 
     54     fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
     55     switch(attrs->colorSpace) {
     56         case ColorSpace_UYVY: 
     57             fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
     58             break;
     59         case ColorSpace_YUV420PSEMI:
     60             fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
     61             break;
     62         case ColorSpace_YUV422PSEMI:
     63             fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV16;
     64             break;    
     65         default:
     66             Dmai_err1("Unsupported color format %g
    ", attrs->colorSpace);            
     67             cleanup(hDisplay);
     68             return NULL;
     69     };
     70 
     71     if (hBufTab == NULL) {
     72         fmt.fmt.pix.bytesperline = Dmai_roundUp(BufferGfx_calcLineLength(fmt.fmt.pix.width, 
     73                                         attrs->colorSpace), 32);
     74         fmt.fmt.pix.sizeimage    = BufferGfx_calcSize(attrs->videoStd, attrs->colorSpace);
     75 #if 1  
     76       } else {
     77         /* This will help user to pass lineLength to display driver. */
     78         Buffer_Handle hBuf;
     79         BufferGfx_Dimensions dim;
     80 
     81         hBuf = BufTab_getBuf(hBufTab, 0);
     82         BufferGfx_getDimensions(hBuf, &dim);
     83         if((dim.height > fmt.fmt.pix.height) ||
     84            (dim.width > fmt.fmt.pix.width)) {
     85             Dmai_err2("User buffer size check failed %dx%d
    ", 
     86                             dim.height, dim.width);
     87             cleanup(hDisplay);
     88             return NULL;
     89         }
     90         fmt.fmt.pix.bytesperline = dim.lineLength;
     91         fmt.fmt.pix.sizeimage = Buffer_getSize(hBuf);
     92     }
     93 #endif
     94     Dmai_dbg4("Video output set to size %dx%d pitch %d imageSize %d
    ",
     95               fmt.fmt.pix.width, fmt.fmt.pix.height, 
     96               fmt.fmt.pix.bytesperline, fmt.fmt.pix.sizeimage);
     97 
     98     if ((attrs->videoStd == VideoStd_CIF) || (attrs->videoStd == VideoStd_SIF_PAL) || 
     99         (attrs->videoStd == VideoStd_SIF_NTSC) || (attrs->videoStd == VideoStd_D1_PAL) ||
    100         (attrs->videoStd == VideoStd_D1_NTSC) || (attrs->videoStd == VideoStd_1080I_30) ||
    101         (attrs->videoStd == VideoStd_1080I_25)) {
    102         fmt.fmt.pix.field        = V4L2_FIELD_INTERLACED;
    103     } else {
    104         fmt.fmt.pix.field        = V4L2_FIELD_NONE;
    105     }
    106 
    107     if (ioctl(hDisplay->fd, VIDIOC_S_FMT, &fmt) == -1) {
    108         Dmai_err2("Failed VIDIOC_S_FMT on %s (%s)
    ", attrs->displayDevice,
    109                                                       strerror(errno));
    110         cleanup(hDisplay);
    111         return NULL;
    112     }
    113 
    114     /* Should the device driver allocate the display buffers? */
    115     if (hBufTab == NULL) {
    116         hDisplay->userAlloc = FALSE;
    117 
    118         if (_Dmai_v4l2DriverAlloc(hDisplay->fd,
    119                                   attrs->numBufs,
    120                                   V4L2_BUF_TYPE_VIDEO_OUTPUT,
    121                                   &hDisplay->bufDescs,
    122                                   &hBufTab,
    123                                   0, attrs->colorSpace) < 0) {
    124             Dmai_err1("Failed to allocate display driver buffers on %s
    ",
    125                       attrs->displayDevice);
    126             cleanup(hDisplay);
    127             return NULL;
    128         }
    129     }
    130     else {
    131         hDisplay->userAlloc = TRUE;
    132 
    133         if (_Dmai_v4l2UserAlloc(hDisplay->fd,
    134                                 attrs->numBufs,
    135                                 V4L2_BUF_TYPE_VIDEO_OUTPUT,
    136                                 &hDisplay->bufDescs,
    137                                 hBufTab,
    138                                 0, attrs->colorSpace) < 0) {
    139             Dmai_err1("Failed to intialize display driver buffers on %s
    ",
    140                       attrs->displayDevice);
    141             cleanup(hDisplay);
    142             return NULL;
    143         }
    144     }
    145 
    146     /* Start the video streaming */
    147     type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
    148 
    149     if (ioctl(hDisplay->fd, VIDIOC_STREAMON, &type) == -1) {
    150         Dmai_err2("VIDIOC_STREAMON failed on %s (%s)
    ", attrs->displayDevice,
    151                                                          strerror(errno));
    152         cleanup(hDisplay);
    153         return NULL;
    154     }
    155 
    156     hDisplay->started = TRUE;
    157     hDisplay->hBufTab = hBufTab;
    158     hDisplay->displayStd = Display_Std_V4L2;
    159 
    160     return hDisplay;
    161 }
    复制代码

    创建显示设备不需要检查视频标准,因为输出的标准是有调用者决定的。

     

    显示设备创建好了,那怎样将采集到的数据显示出来呢?TI提供了一个叫encode的demo程序,大家可以在dvsdk_demos_3_10_00_16中找到。

    这个demo中很巧妙的将视频采集缓冲区和视频显示缓冲区管理起来,接下来一下分析一下。

     

    因为单个显示缓冲和采集缓冲区的大小是一样的,所以可以直接进行交互。

    下面截取demo中的代码片段,看一下这个缓冲区的交互的具体实现。

    capture and display buffer
    复制代码
      1     /* 采集及显示线程中的循环 */
      2     while (!gblGetQuit()) {
      3 
      4         /* 获取采集缓冲区 */
      5         if (Capture_get(hCapture, &hCapBuf) < 0) {
      6             ERR("Failed to get capture buffer
    ");
      7             cleanup(THREAD_FAILURE);
      8         }
      9 
     10         /* 获取显示缓冲区 */
     11         if (Display_get(hDisplay, &hDisBuf) < 0) {
     12             ERR("Failed to get display buffer
    ");
     13             cleanup(THREAD_FAILURE);
     14         }
     15 
     16                 /* 叠加字幕 */
     17         if (envp->osd) {
     18             /* Get the current transparency */
     19             trans = UI_getTransparency(envp->hUI);
     20 
     21             if (trans != oldTrans) {
     22                 /* Change the transparency in the palette */
     23                 for (i = 0; i < 4; i++) {
     24                     bConfigParams.palette[i][3] = trans;
     25                 }
     26 
     27                 /* Reconfigure the blending job if transparency has changed */
     28                 if (Blend_config(hBlend, NULL, hBmpBuf, hCapBuf, hCapBuf,
     29                                  &bConfigParams) < 0) {
     30                     ERR("Failed to configure blending job
    ");
     31                     cleanup(THREAD_FAILURE);
     32                 }
     33             }
     34 
     35             /*
     36              * Because the whole screen is shown even if -r is used,
     37              * reset the dimensions while Blending to make sure the OSD
     38              * always ends up in the same place. After blending, restore
     39              * the real dimensions.
     40              */
     41             BufferGfx_getDimensions(hCapBuf, &srcDim);
     42             BufferGfx_resetDimensions(hCapBuf);
     43 
     44             /*
     45              * Lock the screen making sure no changes are done to
     46              * the bitmap while we render it.
     47              */
     48             hBmpBuf = UI_lockScreen(envp->hUI);
     49 
     50             /* Execute the blending job to draw the OSD */
     51             /* 直接叠加在采集缓冲区的数据上 */
     52             if (Blend_execute(hBlend, hBmpBuf, hCapBuf, hCapBuf) < 0) {
     53                 ERR("Failed to execute blending job
    ");
     54                 cleanup(THREAD_FAILURE);
     55             }
     56 
     57             UI_unlockScreen(envp->hUI);
     58 
     59             BufferGfx_setDimensions(hCapBuf, &srcDim);
     60         }
     61 
     62         /* Color convert the captured buffer from 422Psemi to 420Psemi */
     63         /* 在进行H264编码前需要进行 422 到 420的颜色空间转换,hDstBuf是从视频编码缓冲区队列中得到的一个缓冲区 */
     64         if (Ccv_execute(hCcv, hCapBuf, hDstBuf) < 0) {
     65             ERR("Failed to execute color conversion job
    ");
     66             cleanup(THREAD_FAILURE);
     67         }
     68 
     69         /* Send color converted buffer to video thread for encoding */
     70         /* 转换后将这个缓冲区放回视频编码缓冲区队列中 */
     71         if (Fifo_put(envp->hOutFifo, hDstBuf) < 0) {
     72             ERR("Failed to send buffer to display thread
    ");
     73             cleanup(THREAD_FAILURE);
     74         }
     75 
     76         BufferGfx_resetDimensions(hCapBuf);
     77 
     78         /* Send the preview to the display device driver */
     79         /* 将采集缓冲区放到显示缓冲区队列中 */
     80         if (Display_put(hDisplay, hCapBuf) < 0) {
     81             ERR("Failed to put display buffer
    ");
     82             cleanup(THREAD_FAILURE);
     83         }
     84 
     85         BufferGfx_resetDimensions(hDisBuf);
     86 
     87         /* Return a buffer to the capture driver */
     88         /* 将显示缓冲区放到采集缓冲区队列中 */
     89         if (Capture_put(hCapture, hDisBuf) < 0) {
     90             ERR("Failed to put capture buffer
    ");
     91             cleanup(THREAD_FAILURE);
     92         }
     93 
     94         /* Incremement statistics for the user interface */
     95         /* 帧计数加1 */
     96         gblIncFrames();
     97 
     98         /* Get a buffer from the video thread */
     99         /* 从视频编码缓冲区队列中得到一个缓冲区,用于下一次颜色空间转换使用  */
    100         fifoRet = Fifo_get(envp->hInFifo, &hDstBuf);
    101 
    102         if (fifoRet < 0) {
    103             ERR("Failed to get buffer from video thread
    ");
    104             cleanup(THREAD_FAILURE);
    105         }
    106 
    107         /* Did the video thread flush the fifo? */
    108         if (fifoRet == Dmai_EFLUSH) {
    109             cleanup(THREAD_SUCCESS);
    110         }
    111     }
    复制代码

    到此为止,已经可以知道了数据是从哪里来到哪里去了,但是数据来了,肯定没那么容易就放它走,下一章将会讲到将采集到的数据如何编码并且保存。再加点预告,后面会讲到将编码后的数据通过live555发送出去,实现rtsp视频服务器。

  • 相关阅读:
    WLAN设备接入过程
    802.1帧格式、帧类型详解
    SSID、BSSID、BSS等区分
    802.11协议详解
    bit、Byte、Mbps、Mb/s区别
    WLAN认证技术
    freemarker的简单使用案例
    关于throw、throws、try--catch的问题
    String去重方法
    常用典型的sql语句
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3243671.html
Copyright © 2020-2023  润新知