• (原创)基于ZedBoard的Webcam设计(四):MJPG编码和AVI封装


    在前几篇博客中,我们用ZED平台完成了对USB摄像头的视频采集,Qt的视频显示。在此基础上,我们更希望能把视频存储成视频文件,像DV机一样。MJPG是一种比较简单且易于实现的视频视频编码格式,而AVI封装则是非常流行的视频封装。本篇将介绍MJPG的编码格式和视频的AVI封装,并在ZedBoard上实现对视频的压缩和存储。

    更多更新请关注我的博客:@超群天晴 http://www.cnblogs.com/surpassal/

    相关阅读 :

    (原创)基于ZedBoard的Webcam设计(一):USB摄像头(V4L2接口)的图片采集 

    (原创)基于ZedBoard的Webcam设计(二):USB摄像头图片采集+QT显示 

    (原创)基于ZedBoard的Webcam设计(三):视频的采集和动态显示 

    (原创)基于ZedBoard的Webcam设计(四):MJPG编码和AVI封装 

    (原创)基于ZedBoard的Webcam设计(五):x264编码在zedboard上的实现(软编码)

    硬件平台:Digilent ZedBoard

    开发环境:Windows XP 32 bit + Wmare 8.0 + Ubuntu 10.04 +Qt+ arm-linux-xilinx-gnueabi交叉编译环境 

    Zedboard linux: Digilent OOB Design  

    一、JPEG和MJPG编码介绍

     1、JPEG编码

    我个人简单的理解是,JPEG即是Joint Photographic Experts Group(联合图像专家组)的缩写,更是一种图像压缩编码算法。JPEG编码算法过程简单可以归结于下:其中DCT变换和量化是有损的,而熵编码(一般是哈夫曼编码)是无损的。量化和编码都可以通过量化表和编码表查询得到。

    2、MJPG编码

    Motion JPEG是一种基于静态图像JPEG 压缩标准的动态图像压缩标准,压缩时将连续图像的每一个帧视为一幅静止图像进行压缩,从而可以生成序列化运动图像。压缩时不对帧间的时间冗余进行压缩,也就是说没有MJPEG或者H264那样的帧间编码,阵内预测编码也没有,所以较于实现,只是压缩效率较低。一个MJPG帧序列可以看出是N帧JPEG编码后的数据流连接而成。

    二、基于MJPG编码的AVI视频封装介绍

    AVI是一种RIFF(Resource Interchange File Format)文件格式,多用于音视频捕捉、编辑、回放等应用程序。AVI包含三个部分:文件头、数据块和索引块。其中文件头包括文件的通用信息,定义数据格式,所用的压缩算法等参数。数据块包含实际数据流,即图像和声音序列数据。这是文件的主体,也是决定文件容量的主要部分。视频文件的大小等于该文件的数据率乘以该视频播放的时间长度。索引块包括数据块列表和它们在文件中的位置,以提供文件内数据随机存取能力。AVI文件的总体结构:

    三、ZED上JPEG编码实现

    整个编码过程比较繁琐,这里只做简单介绍。后续如果有时间,专门开辟一篇博客介绍JPEG编码过程。

    1、主编码

     1 void mjpg::jpeg_encode(unsigned char **yuv_buffer_pointer)
     2 {
     3     unsigned int remnant;
     4     yuv_p = *yuv_buffer_pointer;
     5 
     6         bitstring fillbits; //filling bitstring for the bit alignment of the EOI marker
     7 
     8         //fp_jpeg_stream=fopen("000.jpg","wb");
     9         jpgsize = 0;
    10         // 505 bytes
    11         writeword(0xFFD8); // SOI 2
    12         write_APP0info();//JIFF
    13         // write_comment("Cris made this JPEG with his own encoder");
    14         write_DQTinfo();//= 0xFFDB
    15         write_SOF0info();//FFC0
    16         write_DHTinfo();
    17         write_SOSinfo();
    18 
    19         //jpgsize = 505;
    20 
    21         // init global variables
    22         bytenew = 0; // current byte
    23         bytepos = 7; // bit position in this byte
    24         main_encoder();
    25 
    26         // Do the bit alignment of the EOI marker
    27         if (bytepos >= 0)
    28         {
    29                 fillbits.length = bytepos + 1;
    30                 fillbits.value = (1<<(bytepos+1)) - 1;
    31                 writebits(fillbits);
    32         }
    33         writeword(0xFFD9); // EOI
    34 
    35         //remnant = (~(jpgsize&0x00000003))&0x00000003;// important
    36         remnant = (4-(jpgsize&0x00000003))&0x00000003;// important
    37         jpgsize = jpgsize + remnant;
    38         movisize = movisize + jpgsize;
    39         while(remnant > 0)
    40         {
    41             fputc(0,avi_jpeg_stream);
    42             remnant--;
    43         }
    44 }

    其中remnant是一个计算一帧图像大小的余数,因为后续AVI封装要求每帧图像大小都是4的整数倍。

    2、获取8x8阵列数据

    每次处理都是以8x8大小的数据矩阵进行处理的。由于USB摄像头采集到的图像数据是YUV422打包格式,而JPEG编码中比较多的是使用YUV411,所以优先考虑将其转换。其中yuv_p是原始YUV422图像数据指针,YDU1~YDU4是四个连续存储Y分量大小为64字节的数组,CbDU和CrDU分别为Cb和Cr分量。为了提高计算效率,乘法均由移位完成

     1 void mjpg::load_data_units_from_YUV_buffer(WORD xpos, WORD ypos)
     2 {
     3         BYTE x, y;
     4         BYTE pos = 0;
     5         DWORD location;
     6        // SBYTE Cr_temp,Cb_temp;
     7 
     8         //location = ypos * 640+ xpos;
     9         location = (ypos<<7) + (ypos<<9) +  xpos;
    10         for (y=0; y<8; y++)
    11         {
    12             for (x=0; x<8; x++)
    13             {
    14                 YDU1[pos] = *(yuv_p+((location)<<1)) -128;
    15                 YDU2[pos] = *(yuv_p+((location+8)<<1)) -128;
    16                 YDU3[pos] = *(yuv_p+((location+5120)<<1)) -128;//location = (ypos+8) * 640+ xpos+8;
    17                 YDU4[pos] = *(yuv_p+((location+5128)<<1)) -128;//location = (ypos+8) * 640+ xpos+8;
    18                 pos++;
    19                 location++;
    20             }
    21             location += 632;//640 - 8;
    22         }
    23 
    24         pos = 0;
    25         //location = ypos * 640+ xpos;
    26         location = (ypos<<7) + (ypos<<9) +  xpos;
    27         for (y=0; y<8; y++)
    28         {
    29             for (x=0; x<8; x++)
    30             {
    31                 CbDU[pos] = *(yuv_p+(location)*2+1)-128;
    32                 CrDU[pos] = *(yuv_p+(location+1)*2+1)-128;
    33                 pos++;
    34                 location++;
    35                 location++;
    36             }
    37             location += 1264;//640*2 - 16;
    38         }
    39 }

    3、对每个8x8数据阵列进行JPEG处理

     1 void mjpg::main_encoder()
     2 {
     3         SWORD DCY = 0, DCCb = 0, DCCr = 0; //DC coefficients used for differential encoding
     4         WORD xpos, ypos;
     5 
     6         for (ypos=0; ypos<IMG_HEIGTH; ypos+=16)
     7         {
     8                 for (xpos=0; xpos<(IMG_WIDTH); xpos+=16)
     9                 {
    10                         load_data_units_from_YUV_buffer(xpos, ypos);
    11                         process_DU(YDU1, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
    12                         process_DU(YDU2, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
    13                         process_DU(YDU3, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
    14                         process_DU(YDU4, fdtbl_Y, &DCY, YDC_HT, YAC_HT);
    15                         process_DU(CbDU, fdtbl_Cb, &DCCb, CbDC_HT, CbAC_HT);
    16                         process_DU(CrDU, fdtbl_Cb, &DCCr, CbDC_HT, CbAC_HT);
    17                 }
    18         }
    19 }

    四、ZED上MJPG的编码实现以及AVI封装

     写avi文件第一步是写hdrl头信息,可是hdrl头信息需要确定文件的总帧数和文件大小,而在采集过程中这些都是不确定的(因为不知道什么时候采集结束),为此采用了一个“偷懒”方法:先写一个虚假的hdrl,然后每次对一帧图像进行JPEG编码后,将图像的数据量mjpgfile->movisize记录下来,并将数据帧数framecnt记录下来。停止采集后先结束avi文件的写入,再重新打开,然后对文件头进行修改;或者通过fseek寻找的头文件位置,同样修改hdrl信息。两种方法我都试过,感觉效率都差不多。

    为了方便采集,添加按键来触发改变需要的状态,定义state为3个状态:

    state--含义
    0------
    idle,等待采集 1------正在采集
    2------结束采集

    state为0时,标明需要准备写一个新的avi文件;state为1时,标明现在正在采集图像数据,并对每一帧进行jpeg编码;state为2时,标明采集已经结束,fseek往回修改头文件。新的paintEvent函数:

     1 void Widget::paintEvent(QPaintEvent *)
     2 {
     3     rs = vd->get_frame(&yuv_buffer_pointer,&len);
     4 
     5     if(last_state==2 && state == 0)
     6     {
     7         //write hdrl
     8         hdrl.avih.width =640;// (width);
     9         hdrl.avih.height = 480;//(height);
    10         hdrl.strl.strf.width = 640;//(width);
    11         hdrl.strl.strf.height = 480;//(height);
    12         hdrl.strl.strf.image_sz = 640*480*3;//(width * height * 3);
    13 
    14 
    15         sizeofhdrl=sizeof(hdrl);
    16 
    17         mjpgfile->avi_jpeg_stream = fopen(avifilename, "wb");
    18 
    19         fputc('R', mjpgfile->avi_jpeg_stream);
    20         fputc('I', mjpgfile->avi_jpeg_stream);
    21         fputc('F', mjpgfile->avi_jpeg_stream);
    22         fputc('F', mjpgfile->avi_jpeg_stream);
    23         print_quartet(0/*riff_sz*/);//riff file size
    24         fputc('A', mjpgfile->avi_jpeg_stream);
    25         fputc('V', mjpgfile->avi_jpeg_stream);
    26         fputc('I', mjpgfile->avi_jpeg_stream);
    27         fputc(' ', mjpgfile->avi_jpeg_stream);
    28 
    29         fwrite(&hdrl, sizeofhdrl, 1, mjpgfile->avi_jpeg_stream);// write head
    30 
    31         fputc('L', mjpgfile->avi_jpeg_stream);
    32         fputc('I', mjpgfile->avi_jpeg_stream);
    33         fputc('S', mjpgfile->avi_jpeg_stream);
    34         fputc('T', mjpgfile->avi_jpeg_stream);
    35 
    36         print_quartet(0/*jpg_sz + 8*TOTALFRAMES + 4*/);// size again
    37         fputc('m', mjpgfile->avi_jpeg_stream);
    38         fputc('o', mjpgfile->avi_jpeg_stream);
    39         fputc('v', mjpgfile->avi_jpeg_stream);
    40         fputc('i', mjpgfile->avi_jpeg_stream);
    41 
    42         avifilename[5]++;
    43     }
    44 
    45     if(state==1)
    46     {
    47         framecnt++;
    48         fputc('0', mjpgfile->avi_jpeg_stream);
    49         fputc('0', mjpgfile->avi_jpeg_stream);
    50         fputc('d', mjpgfile->avi_jpeg_stream);
    51         fputc('c', mjpgfile->avi_jpeg_stream);
    52         print_quartet(0);
    53 
    54         mjpgfile->jpeg_encode(&yuv_buffer_pointer);
    55 
    56         //printf("%ld\n",mjpgfile->jpgsize);
    57 
    58         fseek(mjpgfile->avi_jpeg_stream,-4-(long)mjpgfile->jpgsize,SEEK_CUR);
    59         print_quartet(mjpgfile->jpgsize);
    60 
    61         fseek(mjpgfile->avi_jpeg_stream,6,SEEK_CUR);
    62         fwrite("AVI1",4, 1, mjpgfile->avi_jpeg_stream);
    63 
    64         fseek(mjpgfile->avi_jpeg_stream,mjpgfile->jpgsize-10,SEEK_CUR);
    65 
    66 
    67     }
    68     if(last_state==1 && state==2)
    69     {
    70 
    71         fseek(mjpgfile->avi_jpeg_stream,4,SEEK_SET);
    72         print_quartet(mjpgfile->movisize+sizeofhdrl);//riff file size
    73         fseek(mjpgfile->avi_jpeg_stream,4,SEEK_CUR);
    74 
    75         //overwrite hdrl
    76         hdrl.avih.us_per_frame = 1000000/12;//(per_usec);
    77         hdrl.avih.max_bytes_per_sec = mjpgfile->movisize*12/framecnt;
    78         hdrl.avih.tot_frames = framecnt;
    79         hdrl.strl.list_odml.frames =framecnt;// (TOTALFRAMES);
    80         hdrl.strl.strh.scale = 1;//
    81         hdrl.strl.strh.length =10;//
    82         hdrl.strl.strh.rate = 12;
    83 
    84         fwrite(&hdrl, sizeofhdrl, 1, mjpgfile->avi_jpeg_stream);// write head
    85         fseek(mjpgfile->avi_jpeg_stream,4,SEEK_CUR);
    86 
    87         print_quartet(mjpgfile->movisize);// size again
    88         fclose(mjpgfile->avi_jpeg_stream);
    89     }
    90     last_state=state;
    91 
    92     convert_yuv_to_rgb_buffer();
    93 
    94     frame->loadFromData(rgb_buffer,640 * 480 * 3);
    95     ui->label->setPixmap(QPixmap::fromImage(*frame,Qt::AutoColor));
    96 
    97 
    98     rs = vd->unget_frame();
    99 }

    最开始定义了视频名字的数组,char avifilename[11] = {'r','c','q','0','0','0','.','a','v','i','\0'};

    在42行:avifilename[5]++;

    表示让名字由"rcq000.avi"依次计数增加。

    五、测试效果

     

    ============================================

    可执行程序:zed_MJPG_camera.rar

  • 相关阅读:
    CF 1292A 迷宫
    JSOI Salesman 树形Dp
    HDU-1051 一个DP问题
    洛谷 P2656 采蘑菇 树形DP+缩点+坑点
    JSOI 2016 病毒感染 辅助Dp问题
    Codeforces D. Color the Fence(贪心)
    Codeforces H. Maximal GCD(贪心)
    Codeforces J. A Simple Task(多棵线段树)
    Codeforces C. A Simple Task(状态压缩dp)
    子集生成的三种算法
  • 原文地址:https://www.cnblogs.com/surpassal/p/zed_webcam_4.html
Copyright © 2020-2023  润新知