17、MJPG編碼和AVI封裝


一、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處理

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

        for (ypos=0; ypos<IMG_HEIGTH; ypos+=16)
        {
                for (xpos=0; xpos<(IMG_WIDTH); xpos+=16)
                {
                        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函數:

 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"依次計數增加。

五、測試效果

 

可執行程序:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM