1、JPEG和MJPG編碼介紹算法
一、JPEG編碼數組
我我的簡單的理解是,JPEG便是Joint Photographic Experts Group(聯合圖像專家組)的縮寫,更是一種圖像壓縮編碼算法。JPEG編碼算法過程簡單能夠歸結於下:其中DCT變換和量化是有損的,而熵編碼(通常是哈夫曼編碼)是無損的。量化和編碼均可以經過量化表和編碼表查詢獲得。函數
二、MJPG編碼測試
Motion JPEG是一種基於靜態圖像JPEG 壓縮標準的動態圖像壓縮標準,壓縮時將連續圖像的每個幀視爲一幅靜止圖像進行壓縮,從而能夠生成序列化運動圖像。壓縮時不對幀間的時間冗餘進行壓縮,也就是說沒有MJPEG或者H264那樣的幀間編碼,陣內預測編碼也沒有,因此較於實現,只是壓縮效率較低。一個MJPG幀序列能夠看出是N幀JPEG編碼後的數據流鏈接而成。ui
2、基於MJPG編碼的AVI視頻封裝介紹this
AVI是一種RIFF(Resource Interchange File Format)文件格式,多用於音視頻捕捉、編輯、回放等應用程序。AVI包含三個部分:文件頭、數據塊和索引塊。其中文件頭包括文件的通用信息,定義數據格式,所用的壓縮算法等參數。數據塊包含實際數據流,即圖像和聲音序列數據。這是文件的主體,也是決定文件容量的主要部分。視頻文件的大小等於該文件的數據率乘以該視頻播放的時間長度。索引塊包括數據塊列表和它們在文件中的位置,以提供文件內數據隨機存取能力。AVI文件的整體結構:編碼
3、ZED上JPEG編碼實現spa
整個編碼過程比較繁瑣,這裏只作簡單介紹。後續若是有時間,專門開闢一篇博客介紹JPEG編碼過程。3d
一、主編碼指針
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的整數倍。
二、獲取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 }
三、對每一個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); } } }
4、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"依次計數增長。
5、測試效果
可執行程序: