17、MJPG编码和AVI封装
阅读原文时间:2023年07月16日阅读:1

一、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、主编码

void mjpg::jpeg_encode(unsigned char **yuv_buffer_pointer)
{
unsigned int remnant;
yuv_p = *yuv_buffer_pointer;

     bitstring fillbits; //filling bitstring for the bit alignment of the EOI marker

     //fp\_jpeg\_stream=fopen("000.jpg","wb");  
     jpgsize = ;  
     // 505 bytes  
     writeword(0xFFD8); // SOI 2  
     write\_APP0info();//JIFF  
     // write\_comment("Cris made this JPEG with his own encoder");  
     write\_DQTinfo();//= 0xFFDB  
     write\_SOF0info();//FFC0  
     write\_DHTinfo();  
     write\_SOSinfo();

     //jpgsize = 505;

     // init global variables  
     bytenew = ; // current byte  
     bytepos = ; // bit position in this byte  
     main\_encoder();

     // Do the bit alignment of the EOI marker  
     if (bytepos >= )  
     {  
             fillbits.length = bytepos + ;  
             fillbits.value = (<<(bytepos+)) - ;  
             writebits(fillbits);  
     }  
     writeword(0xFFD9); // EOI

     //remnant = (~(jpgsize&0x00000003))&0x00000003;// important  
     remnant = (-(jpgsize&0x00000003))&0x00000003;// important  
     jpgsize = jpgsize + remnant;  
     movisize = movisize + jpgsize;  
     while(remnant > )  
     {  
         fputc(,avi\_jpeg\_stream);  
         remnant--;  
     }  

}

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

2、获取8x8阵列数据

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

void mjpg::load_data_units_from_YUV_buffer(WORD xpos, WORD ypos)
{
BYTE x, y;
BYTE pos = ;
DWORD location;
// SBYTE Cr_temp,Cb_temp;

     //location = ypos \* 640+ xpos;  
     location = (ypos<<) + (ypos<<) +  xpos;  
     for (y=; y<; y++)  
     {  
         for (x=; x<; x++)  
         {  
             YDU1\[pos\] = \*(yuv\_p+((location)<<)) -;  
             YDU2\[pos\] = \*(yuv\_p+((location+)<<)) -;  
             YDU3\[pos\] = \*(yuv\_p+((location+)<<)) -;//location = (ypos+8) \* 640+ xpos+8;  
             YDU4\[pos\] = \*(yuv\_p+((location+)<<)) -;//location = (ypos+8) \* 640+ xpos+8;  
             pos++;  
             location++;  
         }  
         location += ;//640 - 8;  
     }

     pos = ;  
     //location = ypos \* 640+ xpos;  
     location = (ypos<<) + (ypos<<) +  xpos;  
     for (y=; y<; y++)  
     {  
         for (x=; x<; x++)  
         {  
             CbDU\[pos\] = \*(yuv\_p+(location)\*+)-;  
             CrDU\[pos\] = \*(yuv\_p+(location+)\*+)-;  
             pos++;  
             location++;  
             location++;  
         }  
         location += ;//640\*2 - 16;  
     }  

}

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

void mjpg::main_encoder()
{
SWORD DCY = , DCCb = , DCCr = ; //DC coefficients used for differential encoding
WORD xpos, ypos;

    for (ypos=; ypos<IMG\_HEIGTH; ypos+=)  
    {  
            for (xpos=; xpos<(IMG\_WIDTH); xpos+=)  
            {  
                    load\_data\_units\_from\_YUV\_buffer(xpos, ypos);  
                    process\_DU(YDU1, fdtbl\_Y, &DCY, YDC\_HT, YAC\_HT);  
                    process\_DU(YDU2, fdtbl\_Y, &DCY, YDC\_HT, YAC\_HT);  
                    process\_DU(YDU3, fdtbl\_Y, &DCY, YDC\_HT, YAC\_HT);  
                    process\_DU(YDU4, fdtbl\_Y, &DCY, YDC\_HT, YAC\_HT);  
                    process\_DU(CbDU, fdtbl\_Cb, &DCCb, CbDC\_HT, CbAC\_HT);  
                    process\_DU(CrDU, fdtbl\_Cb, &DCCr, CbDC\_HT, CbAC\_HT);  
            }  
    }  

}

四、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函数:

void Widget::paintEvent(QPaintEvent *)
{
rs = vd->get_frame(&yuv_buffer_pointer,&len);

 if(last\_state== && state == )  
 {  
     //write hdrl  
     hdrl.avih.width =;// (width);  
     hdrl.avih.height = ;//(height);  
     hdrl.strl.strf.width = ;//(width);  
     hdrl.strl.strf.height = ;//(height);  
     hdrl.strl.strf.image\_sz = \*\*;//(width \* height \* 3);

     sizeofhdrl=sizeof(hdrl);

     mjpgfile->avi\_jpeg\_stream = fopen(avifilename, "wb");

     fputc('R', mjpgfile->avi\_jpeg\_stream);  
     fputc('I', mjpgfile->avi\_jpeg\_stream);  
     fputc('F', mjpgfile->avi\_jpeg\_stream);  
     fputc('F', mjpgfile->avi\_jpeg\_stream);  
     print\_quartet(/\*riff\_sz\*/);//riff file size  
     fputc('A', mjpgfile->avi\_jpeg\_stream);  
     fputc('V', mjpgfile->avi\_jpeg\_stream);  
     fputc('I', mjpgfile->avi\_jpeg\_stream);  
     fputc(' ', mjpgfile->avi\_jpeg\_stream);

     fwrite(&hdrl, sizeofhdrl, , mjpgfile->avi\_jpeg\_stream);// write head

     fputc('L', mjpgfile->avi\_jpeg\_stream);  
     fputc('I', mjpgfile->avi\_jpeg\_stream);  
     fputc('S', mjpgfile->avi\_jpeg\_stream);  
     fputc('T', mjpgfile->avi\_jpeg\_stream);

     print\_quartet(/\*jpg\_sz + 8\*TOTALFRAMES + 4\*/);// size again  
     fputc('m', mjpgfile->avi\_jpeg\_stream);  
     fputc('o', mjpgfile->avi\_jpeg\_stream);  
     fputc('v', mjpgfile->avi\_jpeg\_stream);  
     fputc('i', mjpgfile->avi\_jpeg\_stream);

     avifilename\[\]++;  
 }

 if(state==)  
 {  
     framecnt++;  
     fputc('', mjpgfile->avi\_jpeg\_stream);  
     fputc('', mjpgfile->avi\_jpeg\_stream);  
     fputc('d', mjpgfile->avi\_jpeg\_stream);  
     fputc('c', mjpgfile->avi\_jpeg\_stream);  
     print\_quartet();

     mjpgfile->jpeg\_encode(&yuv\_buffer\_pointer);

     //printf("%ld\\n",mjpgfile->jpgsize);

     fseek(mjpgfile->avi\_jpeg\_stream,--(long)mjpgfile->jpgsize,SEEK\_CUR);  
     print\_quartet(mjpgfile->jpgsize);

     fseek(mjpgfile->avi\_jpeg\_stream,,SEEK\_CUR);  
     fwrite("AVI1",, , mjpgfile->avi\_jpeg\_stream);

     fseek(mjpgfile->avi\_jpeg\_stream,mjpgfile->jpgsize-,SEEK\_CUR);

 }  
 if(last\_state== && state==)  
 {

     fseek(mjpgfile->avi\_jpeg\_stream,,SEEK\_SET);  
     print\_quartet(mjpgfile->movisize+sizeofhdrl);//riff file size  
     fseek(mjpgfile->avi\_jpeg\_stream,,SEEK\_CUR);

     //overwrite hdrl  
     hdrl.avih.us\_per\_frame = /;//(per\_usec);  
     hdrl.avih.max\_bytes\_per\_sec = mjpgfile->movisize\*/framecnt;  
     hdrl.avih.tot\_frames = framecnt;  
     hdrl.strl.list\_odml.frames =framecnt;// (TOTALFRAMES);  
     hdrl.strl.strh.scale = ;//  
     hdrl.strl.strh.length =;//  
     hdrl.strl.strh.rate = ;

     fwrite(&hdrl, sizeofhdrl, , mjpgfile->avi\_jpeg\_stream);// write head  
     fseek(mjpgfile->avi\_jpeg\_stream,,SEEK\_CUR);

     print\_quartet(mjpgfile->movisize);// size again  
     fclose(mjpgfile->avi\_jpeg\_stream);  
 }  
 last\_state=state;

 convert\_yuv\_to\_rgb\_buffer();

 frame->loadFromData(rgb\_buffer, \*  \* );  
 ui->label->setPixmap(QPixmap::fromImage(\*frame,Qt::AutoColor));

 rs = vd->unget\_frame();  

}

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

在42行:avifilename[5]++;

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

五、测试效果

可执行程序:

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章