日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

音视频编解码之路:JPEG编码

發(fā)布時(shí)間:2023/12/18 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 音视频编解码之路:JPEG编码 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

音視頻編解碼之路:JPEG編碼

本文首發(fā)于音視頻編解碼之路:JPEG編碼

前言

本篇是新開坑的 音視頻編解碼之路 的第一篇,這個(gè)系列主要通過書籍、網(wǎng)上的博文/代碼等渠道,整理與各編碼協(xié)議相關(guān)的資料和自己的理解,同時(shí)手?jǐn)]下對(duì)應(yīng)格式的“編解碼器”,形成對(duì)真正編解碼器的原理的基礎(chǔ)認(rèn)識(shí),從而后續(xù)可以進(jìn)一步研究真正意義上的編解碼器如libx264的邏輯與優(yōu)化。

之前在查找編解碼的學(xué)習(xí)資料時(shí),看到了韋神的經(jīng)驗(yàn)之談,因此就以JPEG的編碼來開篇吧。

本篇整體脈絡(luò)來自于手動(dòng)生成一張JPEG圖片,不過針對(duì)文章中的諸多細(xì)節(jié)做了補(bǔ)充和資料匯總,另外把代碼也用C++和OOP的方式修改了下。范例工程位于avcodec_tutorial。

編碼步驟

基本系統(tǒng)的 JPEG 壓縮編碼算法一共分為 10 個(gè)步驟:

  • 顏色模式轉(zhuǎn)換
  • 分塊
  • 離散余弦變換(DCT)
  • 量化
  • Zigzag 掃描排序
  • DC 系數(shù)的差分脈沖調(diào)制編碼(DPCM)
  • DC 系數(shù)的中間格式計(jì)算
  • AC 系數(shù)的游程編碼(RLE)
  • AC 系數(shù)的中間格式計(jì)算
  • 熵編碼
  • 接下去我們將逐一介紹上述的各個(gè)步驟,并在其中穿插涉及的一些概念與實(shí)際代碼。

    顏色模式轉(zhuǎn)換

    JPEG 采用的是 YCbCr 顏色空間,這里不再贅述為啥選擇YUV等等重復(fù)較多的內(nèi)容,之前沒有接觸過的可以看下一文讀懂 YUV 的采樣與格式和其他資料來補(bǔ)補(bǔ)課。

    顏色模式從RGB轉(zhuǎn)為YUV的過程中可以把采樣也一起做了,這里Demo采樣按照YUV444也就是全采樣不做額外處理的方式簡(jiǎn)單實(shí)現(xiàn),代碼如下:

    uint8_t bound(uint8_t min, int value, uint8_t max) {if(value <= min) {return min;}if(value >= max) {return max;}return value; }bool JpegEncoder::rgbToYUV444(const uint8_t *r, const uint8_t *g, const uint8_t *b,const unsigned int &w, const unsigned int &h,uint8_t *const y, uint8_t *const u, uint8_t *const v) {for (int row = 0; row < h; row++) {for (int column = 0; column < w; column++) {int index = row * w + column;// rgb -> yuv 公式// 這里在實(shí)現(xiàn)的時(shí)候踩了個(gè)坑// 之前直接將cast后的值賦值給了y/u/v數(shù)組,y/u/v數(shù)組類型是uint8,計(jì)算出來比如v是256直接越界數(shù)值會(huì)被轉(zhuǎn)成其他如0之類的值// 導(dǎo)致最后顏色效果錯(cuò)誤int yValue = static_cast<int>(round(0.299 * r[index] + 0.587 * g[index] + 0.114 * b[index]));int uValue = static_cast<int>(round(-0.169 * r[index] - 0.331 * g[index] + 0.500 * b[index] + 128));int vValue = static_cast<int>(round(0.500 * r[index] - 0.419 * g[index] - 0.081 * b[index] + 128));// 做下邊界容錯(cuò)y[index] = bound(0, yValue, 255);u[index] = bound(0, uValue, 255);v[index] = bound(0, vValue, 255);}}return true; }

    分塊

    由于后面的 DCT 變換需要對(duì) 8x8 的子塊進(jìn)行處理,因此在進(jìn)行 DCT 變換之前必須把源圖像數(shù)據(jù)進(jìn)行分塊。源圖象經(jīng)過上面的顏色模式轉(zhuǎn)換、采樣后變成了 YUV 數(shù)據(jù),所以需要分別對(duì) Y U V 三個(gè)分量進(jìn)行分塊。具體分塊方式為由左及右,由上到下依次讀取 8x8 的子塊,存放在長(zhǎng)度為 64 的數(shù)組中,之后再進(jìn)行 DCT 變換。

    因?yàn)檫@個(gè)分塊機(jī)制的原因,有些素材的寬高如果不是8的倍數(shù)的話,都需要在處理的時(shí)候進(jìn)行補(bǔ)齊。

    bool JpegEncoder::yuvToBlocks(const uint8_t *y, const uint8_t *u, const uint8_t *v,const unsigned int &w, const unsigned int &h,uint8_t yBlocks[][64], uint8_t uBlocks[][64], uint8_t vBlocks[][64]) {int wBlockSize = w / 8 + (w % 8 == 0 ? 0 : 1);int hBlockSize = h / 8 + (h % 8 == 0 ? 0 : 1);for (int blockRow = 0; blockRow < hBlockSize; blockRow++) {for (int blockColumn = 0; blockColumn < wBlockSize; blockColumn++) {int blockIndex = blockRow * wBlockSize + blockColumn; // 當(dāng)前子塊下標(biāo)uint8_t *yBlock = yBlocks[blockIndex];uint8_t *uBlock = uBlocks[blockIndex];uint8_t *vBlock = vBlocks[blockIndex];for (int row = 0; row < 8; row++) {for (int column = 0; column < 8; column++) {int indexInSubBlock = row * 8 + column; // 塊中數(shù)據(jù)位置int realPosX = blockColumn * 8 + column; // 在完整YUV數(shù)據(jù)中的X位置int realPosY = blockRow * 8 + row; // 在完整YUV數(shù)據(jù)中的Y位置int indexInOriginData = realPosY * w + realPosX; // 在原始數(shù)據(jù)中的位置if (realPosX >= w || realPosY >= h) {// 補(bǔ)齊數(shù)據(jù)yBlock[indexInSubBlock] = 0;uBlock[indexInSubBlock] = 0;vBlock[indexInSubBlock] = 0;} else {yBlock[indexInSubBlock] = y[indexInOriginData];uBlock[indexInSubBlock] = u[indexInOriginData];vBlock[indexInSubBlock] = v[indexInOriginData];}}}}}return true; }

    分塊后的結(jié)果類似下面這樣,假設(shè)源圖像像素寬高為64x64,顏色轉(zhuǎn)換并分塊后將變成YUV三個(gè)通道,且每通道按8x8進(jìn)行拆分:

    DCT

    JPEG 里要對(duì)數(shù)據(jù)壓縮,就需要先要做一次 DCT 變換。數(shù)學(xué)方面的細(xì)節(jié)不是目前的重點(diǎn),只需要知道這個(gè)變換是將數(shù)據(jù)域從時(shí)(空)域變換到頻域,把圖片里點(diǎn)和點(diǎn)間的規(guī)律呈現(xiàn)出來,是為了更方便后續(xù)的壓縮的。

    先貼一下公式,對(duì)數(shù)學(xué)原理感興趣的話可以擴(kuò)展看看JPEG編碼&算術(shù)編碼、LZW編碼等資料:

    DCT變換與圖像壓縮、去燥里面還講到了為什么JPEG選擇DCT而不選擇DFT的原因。

    再貼一下代碼:

    /********* 外部邏輯 *********/ bool JpegEncoder::blocksFDCT(const uint8_t (*yBlocks)[64], const uint8_t (*uBlocks)[64], const uint8_t (*vBlocks)[64],const unsigned int &w, const unsigned int &h,int yDCT[][64], int uDCT[][64], int vDCT[][64]) {int wBlockSize = w / 8 + (w % 8 == 0 ? 0 : 1);int hBlockSize = h / 8 + (h % 8 == 0 ? 0 : 1);int blockSize = wBlockSize * hBlockSize;std::shared_ptr<JpegFDCT> fdct = std::make_shared<JpegFDCT>();for (int blockIndex = 0; blockIndex < blockSize; blockIndex++) {uint8_t *yBlock = yBlocks[blockIndex];uint8_t *uBlock = uBlocks[blockIndex];uint8_t *vBlock = vBlocks[blockIndex];for (int i = 0; i < 64; i++) {yDCT[blockIndex][i] = yBlock[i] - 128;uDCT[blockIndex][i] = uBlock[i] - 128;vDCT[blockIndex][i] = vBlock[i] - 128;yDCT[blockIndex][i] = yDCT[blockIndex][i] << 2;uDCT[blockIndex][i] = uDCT[blockIndex][i] << 2;vDCT[blockIndex][i] = vDCT[blockIndex][i] << 2;}fdct->fdct2d8x8(yDCT[blockIndex]);fdct->fdct2d8x8(uDCT[blockIndex]);fdct->fdct2d8x8(vDCT[blockIndex]);}return true; }/********* DCT 邏輯 *********/ JpegFDCT::JpegFDCT() {int i, j;float factor[64];// 初始化fdct factorconst float AAN_DCT_FACTOR[DCT_SIZE] = {1.0f, 1.387039845f, 1.306562965f, 1.175875602f,1.0f, 0.785694958f, 0.541196100f, 0.275899379f,};for (i = 0; i < DCT_SIZE; i++) {for (j = 0; j < DCT_SIZE; j++) {factor[i * 8 + j] = 1.0f / (AAN_DCT_FACTOR[i] * AAN_DCT_FACTOR[j] * 8);}}for (i = 0; i < 64; i++) {mFDCTFactor[i] = float2Fix(factor[i]);} } int JpegFDCT::float2Fix(float value) {return static_cast<int>(value * (1 << FIX_Q)); } void JpegFDCT::fdct2d8x8(int *data8x8, int *ftab) {int tmp0, tmp1, tmp2, tmp3;int tmp4, tmp5, tmp6, tmp7;int tmp10, tmp11, tmp12, tmp13;int z1, z2, z3, z4, z5, z11, z13;int *dataptr;int ctr;/* Pass 1: process rows. */dataptr = data8x8;for (ctr = 0; ctr < DCT_SIZE; ctr++) {tmp0 = dataptr[0] + dataptr[7];tmp7 = dataptr[0] - dataptr[7];tmp1 = dataptr[1] + dataptr[6];tmp6 = dataptr[1] - dataptr[6];tmp2 = dataptr[2] + dataptr[5];tmp5 = dataptr[2] - dataptr[5];tmp3 = dataptr[3] + dataptr[4];tmp4 = dataptr[3] - dataptr[4];/* Even part */tmp10 = tmp0 + tmp3; /* phase 2 */tmp13 = tmp0 - tmp3;tmp11 = tmp1 + tmp2;tmp12 = tmp1 - tmp2;dataptr[0] = tmp10 + tmp11; /* phase 3 */dataptr[4] = tmp10 - tmp11;z1 = (tmp12 + tmp13) * float2Fix(0.707106781f) >> FIX_Q; /* c4 */dataptr[2] = tmp13 + z1; /* phase 5 */dataptr[6] = tmp13 - z1;/* Odd part */tmp10 = tmp4 + tmp5; /* phase 2 */tmp11 = tmp5 + tmp6;tmp12 = tmp6 + tmp7;/* The rotator is modified from fig 4-8 to avoid extra negations. */z5 = (tmp10 - tmp12) * float2Fix(0.382683433f) >> FIX_Q; /* c6 */z2 = (float2Fix(0.541196100f) * tmp10 >> FIX_Q) + z5; /* c2-c6 */z4 = (float2Fix(1.306562965f) * tmp12 >> FIX_Q) + z5; /* c2+c6 */z3 = tmp11 * float2Fix(0.707106781f) >> FIX_Q; /* c4 */z11 = tmp7 + z3; /* phase 5 */z13 = tmp7 - z3;dataptr[5] = z13 + z2; /* phase 6 */dataptr[3] = z13 - z2;dataptr[1] = z11 + z4;dataptr[7] = z11 - z4;dataptr += DCT_SIZE; /* advance pointer to next row */}/* Pass 2: process columns. */dataptr = data8x8;for (ctr = 0; ctr < DCT_SIZE; ctr++) {tmp0 = dataptr[DCT_SIZE * 0] + dataptr[DCT_SIZE * 7];tmp7 = dataptr[DCT_SIZE * 0] - dataptr[DCT_SIZE * 7];tmp1 = dataptr[DCT_SIZE * 1] + dataptr[DCT_SIZE * 6];tmp6 = dataptr[DCT_SIZE * 1] - dataptr[DCT_SIZE * 6];tmp2 = dataptr[DCT_SIZE * 2] + dataptr[DCT_SIZE * 5];tmp5 = dataptr[DCT_SIZE * 2] - dataptr[DCT_SIZE * 5];tmp3 = dataptr[DCT_SIZE * 3] + dataptr[DCT_SIZE * 4];tmp4 = dataptr[DCT_SIZE * 3] - dataptr[DCT_SIZE * 4];/* Even part */tmp10 = tmp0 + tmp3; /* phase 2 */tmp13 = tmp0 - tmp3;tmp11 = tmp1 + tmp2;tmp12 = tmp1 - tmp2;dataptr[DCT_SIZE * 0] = tmp10 + tmp11; /* phase 3 */dataptr[DCT_SIZE * 4] = tmp10 - tmp11;z1 = (tmp12 + tmp13) * float2Fix(0.707106781f) >> FIX_Q; /* c4 */dataptr[DCT_SIZE * 2] = tmp13 + z1; /* phase 5 */dataptr[DCT_SIZE * 6] = tmp13 - z1;/* Odd part */tmp10 = tmp4 + tmp5; /* phase 2 */tmp11 = tmp5 + tmp6;tmp12 = tmp6 + tmp7;/* The rotator is modified from fig 4-8 to avoid extra negations. */z5 = (tmp10 - tmp12) * float2Fix(0.382683433f) >> FIX_Q; /* c6 */z2 = (float2Fix(0.541196100f) * tmp10 >> FIX_Q) + z5; /* c2-c6 */z4 = (float2Fix(1.306562965f) * tmp12 >> FIX_Q) + z5; /* c2+c6 */z3 = tmp11 * float2Fix(0.707106781f) >> FIX_Q; /* c4 */z11 = tmp7 + z3; /* phase 5 */z13 = tmp7 - z3;dataptr[DCT_SIZE * 5] = z13 + z2; /* phase 6 */dataptr[DCT_SIZE * 3] = z13 - z2;dataptr[DCT_SIZE * 1] = z11 + z4;dataptr[DCT_SIZE * 7] = z11 - z4;dataptr++; /* advance pointer to next column */}ftab = ftab ? ftab : mFDCTFactor;for (ctr = 0; ctr < 64; ctr++) {data8x8[ctr] *= ftab[ctr];data8x8[ctr] >>= FIX_Q + 2;} }

    JPEG 里是對(duì)每 8x8 個(gè)點(diǎn)作為一個(gè)單位處理的,上述代碼就是對(duì)我們剛才所劃分好的各個(gè) 8x8 的子塊進(jìn)行DCT變換。首先我們看到在進(jìn)行變換前需要將矩陣中的每個(gè)數(shù)值減去 128,然后再一一帶入 DCT 變換公式,這是因?yàn)?DCT 公式所接受的數(shù)字范圍是 -128 到 127 之間,其目的在于使像素的絕對(duì)值出現(xiàn)3位10進(jìn)制的概率大大減少。其他部分的處理邏輯暫時(shí)沒有去深究。

    假定有一個(gè)8x8的圖像數(shù)據(jù)如下圖所示:

    那么在減去128之后,將變成:

    再經(jīng)過DCT變換,最終將變成DCT系數(shù)矩陣:

    對(duì)應(yīng)于u=0,v=0的系數(shù),稱做直流分量,即DC系數(shù),即位于矩陣的最左上角,上圖是-415的位置 對(duì)于除DC系數(shù)意外的矩陣中的其余 63 個(gè)則稱為是交流系數(shù)(AC)

    DCT 輸出的頻率系數(shù)矩陣中最左上角的直流(DC)系數(shù)幅度最大,以 DC 系數(shù)為出發(fā)點(diǎn)向下、向右的其它 DCT 系數(shù),離 DC 分量越遠(yuǎn),頻率越高,幅度值越小,即圖像信息的大部分集中于直流系數(shù)及其附近的低頻頻譜上,離 DC 系數(shù)越來越遠(yuǎn)的高頻頻譜幾乎不含圖像信息,甚至于只含雜波。這個(gè)點(diǎn)從數(shù)據(jù)上的理解可以看下JPEG壓縮原理與DCT離散余弦變換中量化與反量化部分。

    關(guān)于圖像頻率可以擴(kuò)展看下圖像上的頻率指的是什么。

    量化

    量化是對(duì)經(jīng)過 FDCT(解碼為IDCT) 變換后的頻率系數(shù)進(jìn)行量化,是一個(gè)將信號(hào)的幅度離散化的過程,量化過程實(shí)際上就是對(duì) DCT 系數(shù)的一個(gè)優(yōu)化過程,它是利用了人眼對(duì)高頻部分不敏感的特性來實(shí)現(xiàn)數(shù)據(jù)的大幅簡(jiǎn)化。在這個(gè)過程實(shí)際上是簡(jiǎn)單地把頻率領(lǐng)域上每個(gè)成份,除以一個(gè)對(duì)于該成份的常數(shù),且接著四舍五入取最接近的整數(shù),這就是整個(gè)過程中的主要有損運(yùn)算。

    從結(jié)果來看,這個(gè)過程經(jīng)常會(huì)把很多高頻率的成份四舍五入而接近0,且剩下很多會(huì)變成小的正或負(fù)數(shù)。而整個(gè)量化的目的正是減小非“0”系數(shù)的幅度以及增加“0”值系數(shù)的數(shù)目,量化是圖像質(zhì)量下降的最主要原因,量化表也是控制 JPEG 壓縮比的關(guān)鍵。

    因?yàn)槿搜蹖?duì)亮度信號(hào)比對(duì)色差信號(hào)更敏感,因此使用了兩種量化表:亮度量化值和色差量化值。

    將前面所得到的 DCT 系數(shù)矩陣與上圖中的亮度/色度量化矩陣進(jìn)行相除并四舍五入后可得到:

    總體來說這個(gè)過程就類似于是一個(gè)空間域的低通濾波器,對(duì) Y 分量采用細(xì)量化,對(duì) UV 采用粗量化,對(duì)低頻細(xì)量化,對(duì)高頻粗量化。對(duì)于濾波器感興趣的話可以擴(kuò)展看看這篇文章:常見低通、高通、帶通三種濾波器的工作原理

    JPEG壓縮比例,就是通過控制量化的多少來控制。比如,上面的量化矩陣,如果我們把矩陣的每個(gè)數(shù)都double一下,那是不是會(huì)出現(xiàn)更多的0了?說不定都只有DC系數(shù)非0,其他都是0,如果是這樣那編碼時(shí)就可以更省空間了,N個(gè)0只要一個(gè)游程編碼搞定,數(shù)據(jù)量超小。但也意味著,恢復(fù)時(shí),會(huì)帶來更多的誤差,圖像質(zhì)量也會(huì)變差了。

    雖然量化步驟除掉了一些高頻量,也就是損失了高頻細(xì)節(jié),但事實(shí)上人眼對(duì)高空間頻率遠(yuǎn)沒有低頻敏感,所以處理后的視覺損失很小。另一個(gè)重要原因是所有圖片的點(diǎn)與點(diǎn)之間會(huì)有一個(gè)色彩過渡的過程,大量的圖像信息被包含在低頻率中,經(jīng)過量化處理后,在高頻率段,將出現(xiàn)大量連續(xù)的零。對(duì)于這部分可以擴(kuò)展閱讀下為什么說圖像的低頻是輪廓,高頻是噪聲和細(xì)節(jié)以及圖像壓縮中,為什么要將圖像從空間域轉(zhuǎn)換到頻率域

    /********* 外部邏輯 *********/ bool JpegEncoder::fdctToQuant(int yDCT[][64], int uDCT[][64], int vDCT[][64],const unsigned int &w, const unsigned int &h) {int wBlockSize = w / 8 + (w % 8 == 0 ? 0 : 1);int hBlockSize = h / 8 + (h % 8 == 0 ? 0 : 1);int blockSize = wBlockSize * hBlockSize;std::shared_ptr<JpegQuant> quant = std::make_shared<JpegQuant>();for (int blockIndex = 0; blockIndex < blockSize; blockIndex++) {int *yBlock = yDCT[blockIndex];int *uBlock = uDCT[blockIndex];int *vBlock = vDCT[blockIndex];quant->quantEncode8x8(yBlock, true);quant->quantEncode8x8(uBlock, false);quant->quantEncode8x8(vBlock, false);}return true; }/********* 量化邏輯 *********/ void JpegQuant::quantEncode8x8(int *data8x8, bool luminance) {for (int i = 0; i < 64; i++) {if (luminance) {data8x8[i] /= STD_QUANT_TAB_LUMIN[i];} else {data8x8[i] /= STD_QUANT_TAB_CHROM[i];}} }

    Zigzag 掃描排序

    量化后的數(shù)據(jù),有一個(gè)很大的特點(diǎn),就是直流分量相對(duì)于交流分量來說要大,而且交流分量中含有大量的 0。這樣,對(duì)這個(gè)量化后的數(shù)據(jù)如何來進(jìn)行簡(jiǎn)化,從而再更大程度地進(jìn)行壓縮呢?

    這就出現(xiàn)了“Z”字形編排的想法,主要思路就是從左上角第一個(gè)像素開始以Z字形進(jìn)行編排:

    至于為什么使用 Zigzag 進(jìn)行掃描排序,我個(gè)人認(rèn)為主要是因?yàn)閳D像信息的大部分集中于直流系數(shù)及其附近的低頻頻譜上,離 DC 系數(shù)越來越遠(yuǎn)的高頻頻譜幾乎不含圖像信息,因此通過該方式可以將更多的高頻數(shù)據(jù)排序到一起,以便于后續(xù)的游程編碼(RLE:Run Length Coding)對(duì)它們進(jìn)行編碼。大家可以對(duì)照上面量化后的矩陣看下使用ZigZag排序與不使用的話0數(shù)據(jù)的連續(xù)性上的差異。

    /********* 外部邏輯 *********/ bool JpegEncoder::quantToZigzag(int yQuant[][64], int uQuant[][64], int vQuant[][64],const unsigned int &w, const unsigned int &h) {int wBlockSize = w / 8 + (w % 8 == 0 ? 0 : 1);int hBlockSize = h / 8 + (h % 8 == 0 ? 0 : 1);int blockSize = wBlockSize * hBlockSize;std::shared_ptr<JpegZigzag> zigzag = std::make_shared<JpegZigzag>();for (int blockIndex = 0; blockIndex < blockSize; blockIndex++) {int *yBlock = yQuant[blockIndex];int *uBlock = uQuant[blockIndex];int *vBlock = vQuant[blockIndex];zigzag->zigzag(yBlock);zigzag->zigzag(uBlock);zigzag->zigzag(vBlock);}return true; }/********* 排序邏輯 *********/ void JpegZigzag::zigzag(int *const data8x8) {int temp[64];for (int i = 0; i < 64; i++) {temp[i] = data8x8[ZIGZAG_INDEX[i]];}for (int i = 0; i < 64; i++) {data8x8[i] = temp[i];} }

    DC 系數(shù)的 DPCM

    8x8 圖像塊經(jīng)過 DCT 變換之后得到的 DC 直流系數(shù)有兩個(gè)特點(diǎn),一是系數(shù)的數(shù)值比較大,二是相鄰 8x8 圖像塊的 DC 系數(shù)值變化不大。根據(jù)這個(gè)特點(diǎn),JPEG 算法使用了差分脈沖調(diào)制編碼 (DPCM) 技術(shù),對(duì)相鄰圖像塊之間量化DC系數(shù)的差值 (Delta) 進(jìn)行編碼。

    DC(0)=0 Delta = DC(i) - DC(i-1)

    此部分代碼混雜在最后熵編碼的整個(gè)流程中,截取部分代碼:

    ... // DC 系數(shù)的差分脈沖調(diào)制編碼(DPCM) diff = block[0] - dc; dc = block[0]; code = diff; // 對(duì)code做中間格式計(jì)算 ...

    DC 系數(shù)的中間格式計(jì)算

    JPEG 中為了更進(jìn)一步節(jié)約空間,并不直接保存數(shù)據(jù)的具體數(shù)值,而是將數(shù)據(jù)按照位數(shù)分為 16 組,保存在表里面。這也就是所謂的變長(zhǎng)整數(shù)編碼 VLI。即,第 0 組中保存的編碼位數(shù)為 0,其編碼所代表的數(shù)字為 0;第 1 組中保存的編碼位數(shù)為 1,編碼所代表的數(shù)字為 -1 或者 1 ......,如下面的表格所示,這里,暫且稱其為 VLI 編碼表:

    如果 DC 系數(shù)差值為 3,通過查找 VLI 可以發(fā)現(xiàn),整數(shù) 3 位于 VLI 表格的第 2 組,因此,可以寫成(2)(3)的形式,這里的2代表后面的數(shù)字(3)的編碼長(zhǎng)度為2位,該形式稱之為 DC 系數(shù)的中間格式。

    對(duì)于VLI可以擴(kuò)展閱讀下可變長(zhǎng)度整數(shù)的編碼,這類思想核心就是用較小的空間存儲(chǔ)小數(shù)字,而用較大的空間存儲(chǔ)大數(shù)字,采用這種算法來對(duì)整數(shù)進(jìn)行編碼是有意義的,它可以節(jié)省存儲(chǔ)數(shù)據(jù)需要的空間或者傳輸數(shù)據(jù)時(shí)所需的帶寬。

    // 接上一章節(jié)最后傳入其Code可進(jìn)行DC系數(shù)的中間格式計(jì)算 // 這里category的命名如果覺得不好理解可以進(jìn)一步去看下 // https://sce.umkc.edu/faculty-sites/lizhu/teaching/2018.fall.video-com/notes/lec04.pdf 第16頁(yè) void HuffmanCodec::categoryEncode(int &code, int &size) {unsigned absc = abs(code);unsigned mask = (1 << 15);int i = 15;if (absc == 0) {size = 0;return;}while (i && !(absc & mask)) {mask >>= 1;i--;}size = i + 1;if (code < 0) {code = (1 << size) - absc - 1;} }

    AC 系數(shù)的 RLE

    游程編碼 RLC(Run Length Coding)是一種比較簡(jiǎn)單的壓縮算法,其基本思想是將重復(fù)且連續(xù)出現(xiàn)多次的字符使用(連續(xù)出現(xiàn)次數(shù),字符)來描述,從而來更進(jìn)一步降低數(shù)據(jù)的傳輸量,舉例來說,一組數(shù)據(jù)"AAAABBBCCDEEEE",由4個(gè)A、3個(gè)B、2個(gè)C、1個(gè)D、4個(gè)E組成,經(jīng)過RLC可將數(shù)據(jù)壓縮為4A3B2C1D4E(由14個(gè)單位轉(zhuǎn)成10個(gè)單位)。簡(jiǎn)而言之,其優(yōu)點(diǎn)在于將重復(fù)性高的數(shù)據(jù)量壓縮成小單位,然而,其缺點(diǎn)在于─若該數(shù)據(jù)出現(xiàn)頻率不高,可能導(dǎo)致壓縮結(jié)果數(shù)據(jù)量比原始數(shù)據(jù)量大,例如:原始數(shù)據(jù)"ABCDE",壓縮結(jié)果為"1A1B1C1D1E"(由5個(gè)單位轉(zhuǎn)成10個(gè)單位)。

    但是,在JPEG編碼中,RLC的含義就同其原有的意義略有不同。在JPEG編碼中,假設(shè)RLC編碼之后得到了一個(gè)(M,N)的數(shù)據(jù)對(duì),其中M是兩個(gè)非零AC系數(shù)之間連續(xù)的0的個(gè)數(shù)(即,行程長(zhǎng)度),N是下一個(gè)非零的AC系數(shù)的值。采用這樣的方式進(jìn)行表示,是因?yàn)锳C系數(shù)當(dāng)中有大量的0,而采用Zigzag掃描也會(huì)使得AC系數(shù)中有很多連續(xù)的0的存在,如此一來,便非常適合于用RLC進(jìn)行編碼。

    舉個(gè)例子來解釋一下,假設(shè)有以下數(shù)據(jù):

    • 57, 45, 0, 0, 0, 0, 23, 0, -30, -8, 0, 0, 1, 000…

    經(jīng)過 0 RLC 之后:

    • (0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)

    注意,如果 AC 系數(shù)之間連續(xù) 0 的個(gè)數(shù)超過 16,則需要用一個(gè)擴(kuò)展字節(jié) (15,0) 來表示 16 連續(xù)的 0。這是因?yàn)楹竺?huffman 編碼的要求,每組數(shù)字前一個(gè)表示 0 的數(shù)量的必須是 4 bit,因此只能是 0~15,所以,如果有這么一組數(shù)字:

    • 57, 十八個(gè)0, 3, 0, 0, 0, 0, 2, 三十三個(gè)0, 895, EOB

    我們實(shí)際這樣編碼:

    • (0,57) ; (15,0) (2,3) ; (4,2) ; (15,0) (15,0) (1,895) , (0,0) 注意 (15,0) 表示了 16 個(gè)連續(xù)的 0。

    EOB:EOB 是一個(gè)結(jié)束標(biāo)記, 表示后面都是 0 了。我們用 (0,0) 表示 EOB,但是,如果這組數(shù)字不以 0 結(jié)束, 那么就不需要 EOB。

    /********* RLE的數(shù)據(jù) *********/typedef struct { unsigned runlen : 4; unsigned codesize : 4; unsigned codedata : 16;} RLEITEM;// AC 系數(shù)的游程長(zhǎng)度編碼(RLE) // AC 系數(shù)的中間格式計(jì)算 // rle encode for acfor (i = 1, j = 0, n = 0, eob = 0; i < 64 && j < 63; i++) { if (du[i] == 0 && n < 15) { n++; } else { code = du[i]; size = 0; // AC 系數(shù)的中間格式計(jì)算 categoryEncode(code, size); rlelist[j].runlen = n; rlelist[j].codesize = size; rlelist[j].codedata = code; n = 0; j++; if (size != 0) { eob = j; } }}// 設(shè)置 eobif (du[63] == 0) { rlelist[eob].runlen = 0; rlelist[eob].codesize = 0; rlelist[eob].codedata = 0; j = eob + 1;}

    AC 系數(shù)的中間格式計(jì)算

    以DC 系數(shù)的中間格式計(jì)算中的編碼表以及AC 系數(shù)的 RLE中所舉例的RLC后的數(shù)據(jù)為例:

    (0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)

    我們只處理每對(duì)數(shù)據(jù)中右邊的那個(gè)數(shù),對(duì)其進(jìn)行 VLI 編碼 :查找上面的 VLI 編碼表,可以發(fā)現(xiàn),57 在第 6 組當(dāng)中,因此可以將其寫成 (0,6),57 的形式,該形式便稱之為 AC 系數(shù)的中間格式。

    同樣的 (0,45) 的中間格式為 (0,6),45 ;(1,-30) 的中間格式為 (1,5),-30 。

    該部分在上面章節(jié)中已有涉及,就不貼代碼了。

    熵編碼

    在得到 DC 系數(shù)的中間格式和 AC 系數(shù)的中間格式之后,為進(jìn)一步壓縮圖像數(shù)據(jù),有必要對(duì)兩者進(jìn)行熵編碼,通過對(duì)出現(xiàn)概率較高的字符采用較小的 bit 數(shù)編碼達(dá)到壓縮的目的。JPEG 標(biāo)準(zhǔn)具體規(guī)定了兩種熵編碼方式:Huffman 編碼和算術(shù)編碼。JPEG 基本系統(tǒng)規(guī)定采用 Huffman 編碼。

    熵編碼的介紹可以擴(kuò)展閱讀下三分鐘學(xué)習(xí) | 熵編碼,簡(jiǎn)單說熵編碼就是在信息熵的極限范圍內(nèi)進(jìn)行編碼,即無損壓縮

    Huffman 編碼:對(duì)出現(xiàn)概率大的字符分配字符長(zhǎng)度較短的二進(jìn)制編碼,對(duì)出現(xiàn)概率小的字符分配字符長(zhǎng)度較長(zhǎng)的二進(jìn)制編碼,從而使得字符的平均編碼長(zhǎng)度最短。Huffman 編碼的原理可以擴(kuò)展閱讀下算法科普:有趣的霍夫曼編碼。

    Huffman 編碼時(shí) DC 系數(shù)與 AC 系數(shù)分別采用不同的 Huffman 編碼表,對(duì)于亮度和色度也采用不同的 Huffman 編碼表。因此,需要 4 張 Huffman 編碼表才能完成熵編碼的工作,等到具體 Huffman 編碼時(shí)再采用查表的方式來高效地完成。然而,在 JPEG 標(biāo)準(zhǔn)中沒有定義缺省的 Huffman 表,用戶可以根據(jù)實(shí)際應(yīng)用自由選擇,也可以使用 JPEG 標(biāo)準(zhǔn)推薦的 Huffman 表,或者預(yù)先定義一個(gè)通用的 Huffman 表,也可以針對(duì)一副特定的圖像,在壓縮編碼前通過搜集其統(tǒng)計(jì)特征來計(jì)算 Huffman 表的值。

    結(jié)合 Huffman 編碼以及上述的DPCM、RLE以及對(duì)應(yīng)的中間格式,參照VLI編碼表,我們?cè)賮碚w地通過一個(gè)例子解釋下數(shù)據(jù)最終壓縮后的樣子:

    假定經(jīng)過RLE之后有如下AC數(shù)據(jù):

    (0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)

    只處理每對(duì)數(shù)右邊的那個(gè):

    • 57 是第 6 組的, 實(shí)際保存值為 111001 , 所以被編碼為 (6,111001)
    • 45 , 同樣的操作, 編碼為 (6,101101)
    • 23 -> (5,10111)
    • -30 -> (5,00001)
    • -8 -> (4,0111)
    • 1 -> (1,1)

    最后,最開始的那串?dāng)?shù)字就變成了:

    • (0,6), 111001 ; (0,6), 101101 ; (4,5), 10111; (1,5), 00001; (0,4) , 0111 ; (2,1), 1 ; (0,0)

    括號(hào)里的數(shù)值正好合成一個(gè)字節(jié),后面被編碼的數(shù)字表示范圍是 -32767..32767。合成的字節(jié)里,高 4 位是前續(xù) 0 的個(gè)數(shù),低 4 位描述了后面數(shù)字的位數(shù)。

    再進(jìn)一步通過 Huffman 查找得到如果 (0,6) 的 huffman 編碼為 111000 ,那么最終編碼的數(shù)據(jù)便是:

    • 111000 111001

    最后看下DC的編碼,假設(shè)DC的diff值是-511,編碼為 (9, 000000000) ,如果 9 的 Huffman 編碼是 1111110 ,那么在 JPG 文件中, DC 的 2 進(jìn)制表示為 1111110 000000000,最終加上上面AC的第一個(gè)數(shù)據(jù),編碼為:

    • 1111110 000000000 111000 111001 ...
    /********* 初始化編碼表 *********/HuffmanCodec::HuffmanCodec() { initCoddList(true, true); initCoddList(true, false); initCoddList(false, true); initCoddList(false, false);void HuffmanCodec::initCoddList(bool dc, bool luminance) { int i, j, k; int symbol; int code; uint8_t hufsize[256]; int hufcode[256]; int tabsize; k = 0; code = 0x00; const uint8_t *hufTable; HUFCODEITEM *codeList; if (dc && luminance) { hufTable = STD_HUFTAB_LUMIN_DC; codeList = mCodeListDCLumin; } else if (dc && !luminance) { hufTable = STD_HUFTAB_CHROM_DC; codeList = mCodeListDCChrom; } else if (!dc && luminance) { hufTable = STD_HUFTAB_LUMIN_AC; codeList = mCodeListACLumin; } else { hufTable = STD_HUFTAB_CHROM_AC; codeList = mCodeListACChrom; } for (i = 0; i < MAX_HUFFMAN_CODE_LEN; i++) { for (j = 0; j < hufTable[i]; j++) { hufsize[k] = i + 1; hufcode[k] = code; code++; k++; } code <<= 1; } tabsize = k; for (i = 0; i < tabsize; i++) { symbol = hufTable[MAX_HUFFMAN_CODE_LEN + i]; codeList[symbol].depth = hufsize[i]; codeList[symbol].code = hufcode[i]; }}/********* 編碼 *********/bool HuffmanCodec::huffmanEncode(HUFCODEITEM *codeList, int size) { unsigned code; int len; if (!mBitStream) { return false; } code = codeList[size].code; len = codeList[size].depth; if (EOF == bitstr_put_bits(mBitStream, code, len)) { return false; } return true;}

    JPEG 文件寫入

    JPEG 文件大體上可以分成兩個(gè)部分:標(biāo)記碼(Tag)和壓縮數(shù)據(jù)。

    常用的標(biāo)記有 SOI、APP0、APPn、DQT、SOF0、DHT、DRI、SOS、EOI:

    標(biāo)記標(biāo)記代碼描述
    SOI0xD8圖像開始
    APP00xE0JFIF應(yīng)用數(shù)據(jù)塊
    APPn0xE1 - 0xEF其他的應(yīng)用數(shù)據(jù)塊(n, 1~15)
    DQT0xDB量化表
    SOF00xC0幀開始
    DHT0xC4霍夫曼(Huffman)表
    DRI0xDD差分編碼累計(jì)復(fù)位的間隔
    SOS0xDA掃描線開始
    EOI0xD9圖像結(jié)束

    更具體的細(xì)節(jié)可擴(kuò)展閱讀下JPEG文件格式詳解。

    bool JpegEncoder::writeToFile(char* buffer, long dataLength, const unsigned int &w, const unsigned int &h) { FILE *fp = fopen(mOutputPath.data(), "wb"); // SOI fputc(0xff, fp); fputc(0xd8, fp); // DQT const int *pqtab[2] = {JpegQuant::STD_QUANT_TAB_LUMIN, JpegQuant::STD_QUANT_TAB_CHROM}; for (int i = 0; i < 2; i++) { int len = 2 + 1 + 64; fputc(0xff, fp); fputc(0xdb, fp); fputc(len >> 8, fp); fputc(len >> 0, fp); fputc(i, fp); for (int j = 0; j < 64; j++) { fputc(pqtab[i][JpegZigzag::ZIGZAG_INDEX[j]], fp); } } // SOF0 int SOF0Len = 2 + 1 + 2 + 2 + 1 + 3 * 3; fputc(0xff, fp); fputc(0xc0, fp); fputc(SOF0Len >> 8, fp); fputc(SOF0Len >> 0, fp); fputc(8, fp); // precision 8bit fputc(h >> 8, fp); // height fputc(h >> 0, fp); // height fputc(w >> 8, fp); // width fputc(w >> 0, fp); // width fputc(3, fp); fputc(0x01, fp); fputc(0x11, fp); fputc(0x00, fp); fputc(0x02, fp); fputc(0x11, fp); fputc(0x01, fp); fputc(0x03, fp); fputc(0x11, fp); fputc(0x01, fp); // DHT AC const uint8_t *huftabAC[] = { HuffmanCodec::STD_HUFTAB_LUMIN_AC, HuffmanCodec::STD_HUFTAB_CHROM_AC }; for (int i = 0; i < 2; i++) { fputc(0xff, fp); fputc(0xc4, fp); int len = 2 + 1 + 16; for (int j = 0; j < 16; j++) { len += huftabAC[i][j]; } fputc(len >> 8, fp); fputc(len >> 0, fp); fputc(i + 0x10, fp); fwrite(huftabAC[i], len - 3, 1, fp); } // DHT DC const uint8_t *huftabDC[] = { HuffmanCodec::STD_HUFTAB_LUMIN_DC, HuffmanCodec::STD_HUFTAB_CHROM_DC }; for (int i = 0; i < 2; i++) { fputc(0xff, fp); fputc(0xc4, fp); int len = 2 + 1 + 16; for (int j = 0; j < 16; j++) { len += huftabDC[i][j]; } fputc(len >> 8, fp); fputc(len >> 0, fp); fputc(i + 0x00, fp); fwrite(huftabDC[i], len - 3, 1, fp); } // SOS int SOSLen = 2 + 1 + 2 * 3 + 3; fputc(0xff, fp); fputc(0xda, fp); fputc(SOSLen >> 8, fp); fputc(SOSLen >> 0, fp); fputc(3, fp); fputc(0x01, fp); fputc(0x00, fp); fputc(0x02, fp); fputc(0x11, fp); fputc(0x03, fp); fputc(0x11, fp); fputc(0x00, fp); fputc(0x3F, fp); fputc(0x00, fp); // data fwrite(buffer, dataLength, 1, fp); // EOI fputc(0xff, fp); fputc(0xd9, fp); fflush(fp); fclose(fp); return true;}

    完工

    到這里我們編寫的JpegEncoder就可以將傳入的RGB24格式的數(shù)據(jù)壓縮編碼成YUV444的JPEG文件了,可以運(yùn)行 avcodec_tutorial 項(xiàng)目,運(yùn)行后將在你的桌面看到如下內(nèi)容隨機(jī)生成的圖片:

    參考文章

    手動(dòng)生成一張JPEG圖片

    JPEG 簡(jiǎn)易文檔

    JPEG 中的范式哈夫曼編碼

    JPEG圖像壓縮算法流程詳解

    JPEG編解碼原理及代碼調(diào)試

    JPEG standard

    Variable Length Coding (VLC) in JPEG

    JPEG編碼&算術(shù)編碼、LZW編碼

    圖像的時(shí)頻變換——離散余弦變換

    圖像上的頻率指的是什么

    為什么說圖像的低頻是輪廓,高頻是噪聲和細(xì)節(jié)

    DCT變換與圖像壓縮、去燥

    JPEG壓縮原理與DCT離散余弦變換

    JPEG 標(biāo)準(zhǔn)推薦的亮度、色度DC、AC Huffman 編碼表

    一文讀懂 YUV 的采樣與格式

    JPEG文件格式詳解

    [數(shù)據(jù)壓縮之游程編碼](

    本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!

    總結(jié)

    以上是生活随笔為你收集整理的音视频编解码之路:JPEG编码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 欧美美女性生活视频 | 国产探花在线精品一区二区 | 黄色小视频在线看 | 日韩欧美精品 | 日本在线观看一区二区三区 | av怡红院| 黄色视屏在线 | 国产在线观看免费av | 好吊色青青草 | 日韩3区| 1769国产| 免费观看视频在线观看 | 最新日韩av | 亚洲精品aⅴ中文字幕乱码 国产精品调教视频 | 欧美在线中文字幕 | 一区日韩| 大地资源影视在线播放观看高清视频 | 日本寂寞少妇 | 欧美在线一二三四区 | 精品视频一区二区三区在线观看 | 国产亚洲欧美日韩精品一区二区三区 | 99在线视频观看 | 国产素人自拍 | av制服丝袜在线 | 日韩免费小视频 | 日韩乱码人妻无码中文字幕 | 日韩无码专区 | 天堂中文字幕在线 | www.色综合.com | 中文字幕在线播 | 欧美精品1 | 欧美精品亚洲一区 | 国产精品美女久久久久av超清 | 91亚洲精 | 亚洲成人一区在线观看 | www.欧美 | 一二三区不卡 | 成人动漫一区二区 | www日韩在线观看 | 在线观看av网 | 国产午夜精品一区二区三区欧美 | 日本不卡视频在线播放 | 米奇久久| 欧美a√在线 | 捆绑调教在线观看 | 色av吧 | 一本一本久久a久久精品综合麻豆 | 漂亮人妻被中出中文字幕 | 欧美乱妇高清无乱码 | 国产片一区二区三区 | 中文字幕欧美在线观看 | 久久夜色精品国产欧美乱 | 在线免费一区 | 毛片日韩 | 日本亚洲一区二区 | 国产精品自拍小视频 | 精品视频 | 亚洲xxxx视频 | 欧美性tv | 性色在线视频 | 爱情岛论坛永久入址在线 | 黄网在线观看免费 | 韩国久久久久久 | 欧美综合视频 | 日韩高清一区 | 久久天| 欧美极品少妇xxxxⅹ免费视频 | 日日淫| 国产剧情一区 | 日韩av一区二区在线 | 专干老肥女人88av | 精品人妻一区二区三区香蕉 | 国产91熟女高潮一区二区 | 久久夜色精品国产噜噜亚洲av | 国产成人精品一区二三区四区五区 | 亚洲一区二区网站 | 熟妇女人妻丰满少妇中文字幕 | 国产精选毛片 | 亚洲毛片一区二区 | exo妈妈mv在线播放免费 | av看片在线 | 玉足调教丨vk24分钟 | 99热网址 | 香蕉精品视频在线观看 | 中文字幕第2页 | 韩国三级在线 | 偷拍亚洲综合 | 中国在线观看免费高清视频播放 | 久久老熟女一区二区三区 | aaa一级片 | 亚洲乱亚洲 | 一区二区三区视频网 | 午夜av免费看 | 国产h视频 | 欧美色视| 日韩午夜毛片 | 成人一级影片 | 91精品91久久久中77777 | 亚洲欧洲视频在线观看 |