1. 程式人生 > >x265原始碼分析:sao.cpp 自適應樣點補償

x265原始碼分析:sao.cpp 自適應樣點補償

/* 對|num / den|四捨五入,然後前面新增符號 */
inline int32_t roundIBDI(int32_t num, int32_t den)
{
    return num >= 0 ? ((num * 2 + den)/(den * 2)) : -((-num * 2 + den)/(den * 2));
}

/* 獲取輸入變數x的符號 */
inline int8_t signOf(int x)
{
    return (x >> 31) | ((int)((((uint32_t)-x)) >> 31));
}

/* a等於b返回0, a小於b就返回-1,a大於b就返回1 */
inline int signOf2(const int a, const int b) { int r = 0; if (a < b) r = -1; if (a > b) r = 1; return r; } /** * @brief 計算D_post和 D_pre的差值,其中D_pre和D_post分別表示原始畫素與重構畫素(SAO補償前、補償後)之間的失真。 * @param count : 一個CTB內某個特定SAO型別樣本的個數 * @param offset : 一個CTB內某個特定SAO型別樣本的補償值 * @param
offsetOrg : 原始畫素與重構畫素(SAO補償前)之間的差值之和 */
inline int64_t estSaoDist(int32_t count, int32_t offset, int32_t offsetOrg) { return (count * offset - offsetOrg * 2) * offset; } /** * @brief 邊界補償模式下畫素的5種分類 : * 第1類谷點和第2類凹拐點,需要加上一個正補償值; * 第4類峰點和第3類凸拐點,需要加上一個負補償值; * 第0類畫素不進行補償。 */ const
uint32_t SAO::s_eoTable[NUM_EDGETYPE] = { 1, // 0 2, // 1 0, // 2 3, // 3 4 // 4 }; /** * @brief 建立SAO的部分引數 */ bool SAO::create(x265_param* param, int initCommon) { m_param = param; // 編碼器引數集 m_chromaFormat = param->internalCsp; // 內部影象顏色空間,此處只考慮 I420 // 色度水平和垂直方向移動的位數,對於I420格式的影象,此處都是1 m_hChromaShift = CHROMA_H_SHIFT(param->internalCsp); m_vChromaShift = CHROMA_V_SHIFT(param->internalCsp); // 計算水平和垂直方向CU(編碼單元)的個數,長度不足 g_maxCUSize 也算一個; // maxCUSize 表示CU的最大尺寸,此處取值為 64. m_numCuInWidth = (m_param->sourceWidth + g_maxCUSize - 1) / g_maxCUSize; m_numCuInHeight = (m_param->sourceHeight + g_maxCUSize - 1) / g_maxCUSize; // maxY表示亮度的最大值,對於8位深度的影象來說,該最大值為255; // rangeExt 擴充套件範圍為最大值的一半,此處為127; // numCtu 表示一幀中 CU (編碼單元)的個數. const pixel maxY = (1 << X265_DEPTH) - 1; const pixel rangeExt = maxY >> 1; int numCtu = m_numCuInWidth * m_numCuInHeight; // 為當前CU的左邊和上面CU申請空間,備份左邊和上面CU主要用於預測當前CU; for (int i = 0; i < (param->internalCsp != X265_CSP_I400 ? 3 : 1); i++) { CHECKED_MALLOC(m_tmpL1[i], pixel, g_maxCUSize + 1); CHECKED_MALLOC(m_tmpL2[i], pixel, g_maxCUSize + 1); // SAO asm code will read 1 pixel before and after, so pad by 2 // NOTE: m_param->sourceWidth+2 enough, to avoid condition check in // copySaoAboveRef(), I alloc more up to 63 bytes in here CHECKED_MALLOC(m_tmpU[i], pixel, m_numCuInWidth * g_maxCUSize + 2 + 32); m_tmpU[i] += 1; } if (initCommon) { // 選擇SAO方法處理去方塊邊界畫素,如果開啟則處理所有邊界畫素, // 關閉則不處理右邊和下面邊界的畫素;預設是關閉。 if (m_param->bSaoNonDeblocked) { CHECKED_MALLOC(m_countPreDblk, PerPlane, numCtu); CHECKED_MALLOC(m_offsetOrgPreDblk, PerPlane, numCtu); } CHECKED_MALLOC(m_depthSaoRate, double, 2 * SAO_DEPTHRATE_SIZE); m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 0] = 0; m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 1] = 0; m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 2] = 0; m_depthSaoRate[0 * SAO_DEPTHRATE_SIZE + 3] = 0; m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 0] = 0; m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 1] = 0; m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 2] = 0; m_depthSaoRate[1 * SAO_DEPTHRATE_SIZE + 3] = 0; CHECKED_MALLOC(m_clipTableBase, pixel, maxY + 2 * rangeExt); m_clipTable = &(m_clipTableBase[rangeExt]); // 建立一個快速查詢表m_clipTable(用於補償,限制越界),即: // {0, 0, ..., 0 (127個); 0, 1, 2, ..., 255; 255, 255, ..., 255(127個)} for (int i = 0; i < rangeExt; i++) m_clipTableBase[i] = 0; for (int i = 0; i < maxY; i++) m_clipTable[i] = (pixel)i; for (int i = maxY; i < maxY + rangeExt; i++) m_clipTable[i] = maxY; } else { // must initialize these common pointer outside of function m_countPreDblk = NULL; m_offsetOrgPreDblk = NULL; m_clipTableBase = NULL; m_clipTable = NULL; } return true; fail: return false; } /* 為當前CTU的SAO引數分配空間並初始化 */ void SAO::allocSaoParam(SAOParam* saoParam) const { int planes = (m_param->internalCsp != X265_CSP_I400) ? 3 : 1; saoParam->numCuInWidth = m_numCuInWidth; for (int i = 0; i < planes; i++) saoParam->ctuParam[i] = new SaoCtuParam[m_numCuInHeight * m_numCuInWidth]; } /** * @brief 根據SAO補償模式對重構畫素值進行補償. * @param addr : 從上到下、從左到右,當前CTU的序號 * @param typeIdx : SAO補償模式,取值SAO_EO_X 或 SAO_BO * @param plane : 顏色空間平面的序號,亮度平面為0,兩個色度平面分別為1和2. */ void SAO::applyPixelOffsets(int addr, int typeIdx, int plane) { // reconPic為YUV重構影象,rec為當面顏色平面當前CTU的在重構影象中起始地址 PicYuv* reconPic = m_frame->m_reconPic; pixel* rec = reconPic->getPlaneAddr(plane, addr); // 獲取重構影象顏色平面對應的跨度,亮度和色度的跨度不一樣 intptr_t stride = plane ? reconPic->m_strideC : reconPic->m_stride; uint32_t picWidth = m_param->sourceWidth; // 原始影象的寬 uint32_t picHeight = m_param->sourceHeight; // 原始影象的高 const CUData* cu = m_frame->m_encData->getPicCTU(addr); int ctuWidth = g_maxCUSize; // 當前CU的寬度 int ctuHeight = g_maxCUSize; // 當前CU的高度 // 當前CU最左邊的橫座標和最上面的縱座標 uint32_t lpelx = cu->m_cuPelX; uint32_t tpely = cu->m_cuPelY; // 如果是色度平面,相應的寬度和高度都要減半,即左移一位 if (plane) { picWidth >>= m_hChromaShift; picHeight >>= m_vChromaShift; ctuWidth >>= m_hChromaShift; ctuHeight >>= m_vChromaShift; lpelx >>= m_hChromaShift; tpely >>= m_vChromaShift; } // 獲取當前CU最右邊和最下面的邊界值,不超出原始影象的最右邊和最下面 uint32_t rpelx = x265_min(lpelx + ctuWidth, picWidth); uint32_t bpely = x265_min(tpely + ctuHeight, picHeight); // 當前CU實際的寬度和高度,除了最右邊和最下面的CU外,其他都是64x64 ctuWidth = rpelx - lpelx; ctuHeight = bpely - tpely; int8_t _upBuff1[MAX_CU_SIZE + 2], *upBuff1 = _upBuff1 + 1, signLeft1[2]; int8_t _upBufft[MAX_CU_SIZE + 2], *upBufft = _upBufft + 1; memset(_upBuff1 + MAX_CU_SIZE, 0, 2 * sizeof(int8_t)); pixel* tmpL = m_tmpL1[plane]; pixel* tmpU = &(m_tmpU[plane][lpelx]); int8_t* offsetEo = m_offsetEo[plane]; // 根據邊界或邊帶型別進行相應的SAO補償 switch (typeIdx) { case SAO_EO_0: // dir: - {... ...} case SAO_EO_1: // dir: | {... ...} case SAO_EO_2: // dir: 135 {... ...} case SAO_EO_3: // dir: 45 {... ...} case SAO_BO: // 邊帶補償 {... ...} default: break; } } /* 生成亮度CTU的各種模式下的SAO補償值並進行補償 */ void SAO::generateLumaOffsets(SaoCtuParam* ctuParam, int idxY, int idxX) { PicYuv* reconPic = m_frame->m_reconPic; intptr_t stride = reconPic->m_stride; int ctuWidth = g_maxCUSize; int ctuHeight = g_maxCUSize; // 根據idxX和idxY得到CTU的序號,再根據序號獲取CTU在重構影象緩衝區中起始位置 int addr = idxY * m_numCuInWidth + idxX; pixel* rec = reconPic->getLumaAddr(addr); // 如果是水平方向第一個CTU,則用m_tmpL1[0]儲存CTU左邊一列(即左邊CTU最右邊的一列, // 不屬於該CTU)的重構值 if (idxX == 0) { for (int i = 0; i < ctuHeight + 1; i++) { m_tmpL1[0][i] = rec[0]; rec += stride; } } // 判斷當前CTU是否與左邊CTU的SAO模式一樣 bool mergeLeftFlag = (ctuParam[addr].mergeMode == SAO_MERGE_LEFT); int typeIdx = ctuParam[addr].typeIdx; // 當前CTU不是水平方向的最後一個CTU,則用m_tmpL2[0]來儲存當前CTU最右邊的一列 //(屬於該CTU)重構值,後續跟m_tmpL1[0]交換可以用於下一個CTU的SAO模式計算 if (idxX != (m_numCuInWidth - 1)) { rec = reconPic->getLumaAddr(addr); for (int i = 0; i < ctuHeight + 1; i++) { m_tmpL2[0][i] = rec[ctuWidth - 1]; rec += stride; } } // SAO補償模式總共五種,取值0 – 4. if (typeIdx >= 0) { // 如果跟左邊的CTU相同的SAO模式,則 m_offsetEo 直接採用左邊CTU的值 if (!mergeLeftFlag) { if (typeIdx == SAO_BO) { memset(m_offsetBo[0], 0, sizeof(m_offsetBo[0])); for (int i = 0; i < SAO_NUM_OFFSET; i++) m_offsetBo[0][((ctuParam[addr].bandPos + i) & (MAX_NUM_SAO_CLASS - 1))] = (int8_t)(ctuParam[addr].offset[i] << SAO_BIT_INC); } else // 邊界補償,即SAO_EO_X, X = 0,1,2,3 { int offset[NUM_EDGETYPE]; offset[0] = 0; for (int i = 0; i < SAO_NUM_OFFSET; i++) offset[i + 1] = ctuParam[addr].offset[i] << SAO_BIT_INC; for (int edgeType = 0; edgeType < NUM_EDGETYPE; edgeType++) m_offsetEo[0][edgeType] = (int8_t)offset[s_eoTable[edgeType]]; } } // m_offsetEo[0]儲存了亮度平面各種邊界或邊帶需要補償的值,將該值用到SAO補償中 applyPixelOffsets(addr, typeIdx, 0); } // 交換m_tmpL1[0]與m_tmpL2[0],就得到下一個CTU左邊一列的重構值,即:m_tmpL1[0] std::swap(m_tmpL1[0], m_tmpL2[0]); } /* 生成色度CTU的各種模式下的SAO補償值並進行補償*/ void SAO::generateChromaOffsets(SaoCtuParam* ctuParam[3], int idxY, int idxX); /* 統計當前CTU在BO和EO各模式下的畫素歸類,包括重構畫素與原始畫素差值之和,以及對classIdx的計數 */ void SAO::calcSaoStatsCTU(int addr, int plane) { const PicYuv* reconPic = m_frame->m_reconPic; const CUData* cu = m_frame->m_encData->getPicCTU(addr); const pixel* fenc0 = m_frame->m_fencPic->getPlaneAddr(plane, addr); const pixel* rec0 = reconPic->getPlaneAddr(plane, addr); const pixel* fenc; const pixel* rec; // 亮度和色度平面的跨度不一樣,plane為0表示亮度,非0表示色度 intptr_t stride = plane ? reconPic->m_strideC : reconPic->m_stride; uint32_t picWidth = m_param->sourceWidth; uint32_t picHeight = m_param->sourceHeight; int ctuWidth = g_maxCUSize; int ctuHeight = g_maxCUSize; uint32_t lpelx = cu->m_cuPelX; // 當前CTU最左邊畫素的橫座標 uint32_t tpely = cu->m_cuPelY; // 當前CTU最上面畫素的縱座標 // 色度平面,相應的值都要減半,即左移一位 if (plane) { picWidth >>= m_hChromaShift; picHeight >>= m_vChromaShift; ctuWidth >>= m_hChromaShift; ctuHeight >>= m_vChromaShift; lpelx >>= m_hChromaShift; tpely >>= m_vChromaShift; } // 當前CTU最右邊畫素的橫座標、最下面畫素的縱座標 uint32_t rpelx = x265_min(lpelx + ctuWidth, picWidth); uint32_t bpely = x265_min(tpely + ctuHeight, picHeight); // 當前CTU實際的寬度和高度,除了最右邊和最下面的CTU外,其他CTU一般都是64x64 ctuWidth = rpelx - lpelx; ctuHeight = bpely - tpely; int startX, startY, endX, endY; const int plane_offset = plane ? 2 : 0; int skipB = 4, skipR = 5; int8_t _upBuff[2 * (MAX_CU_SIZE + 16 + 16)], *upBuff1 = _upBuff + 16, *upBufft = upBuff1 +(MAX_CU_SIZE + 16 + 16); ALIGN_VAR_32(int16_t, diff[MAX_CU_SIZE * MAX_CU_SIZE]); // 計算 (fenc - frec),結果放入diff中,即原始畫素與重構畫素間的失真 if ((lpelx + ctuWidth < picWidth) & (tpely + ctuHeight < picHeight)) { // WARNING: *) May read beyond bound on video than ctuWidth or // ctuHeight is NOT multiple of cuSize X265_CHECK((ctuWidth == ctuHeight) || (m_chromaFormat != X265_CSP_I420), "video size check failure\n"); // 對於square的CU可以採用SIMD流指令計算(fenc - frec), // 此處 MAX_CU_SIZE = 64 可以看作 diff的跨度,fenc0和rec0的跨度都是stride if (plane) primitives.chroma[m_chromaFormat].cu[g_maxLog2CUSize - 2]. sub_ps(diff, MAX_CU_SIZE, fenc0, rec0, stride, stride); else primitives.cu[g_maxLog2CUSize - 2].sub_ps(diff, MAX_CU_SIZE, fenc0, rec0, stride, stride); } else { // path for non-square area (most in edge) // 最右邊或最下面的CTU可能不是square,另外單獨計算 (fenc - frec) for(int y = 0; y < ctuHeight; y++) { for(int x = 0; x < ctuWidth; x++) { diff[y * MAX_CU_SIZE + x] = (fenc0[y * stride + x] – rec0[y * stride + x]); } } } // SAO_BO: { // 預設是disable,表示右邊和底部邊界不做去方塊濾波 if (m_param->bSaoNonDeblocked) { skipB = 3; skipR = 4; } endX = (rpelx == picWidth) ? ctuWidth : ctuWidth - skipR + plane_offset; endY = (bpely == picHeight) ? ctuHeight : ctuHeight - skipB + plane_offset; // 當前CTU按照BO補償模式對畫素進行歸類, // 包括每個條帶畫素個數、原始畫素與重構畫素差值之和 primitives.saoCuStatsBO(diff, rec0, stride, endX, endY, m_offsetOrg[plane][SAO_BO], m_count[plane][SAO_BO]); } // SAO_EO_0: // dir: - { if (m_param->bSaoNonDeblocked) // 預設是disable, 忽略 { skipB = 3; skipR = 5; } startX = !lpelx; endX = (rpelx == picWidth) ? ctuWidth - 1 : ctuWidth - skipR + plane_offset; // 當前CTU按照 EO_0 模式對畫素進行歸類 primitives.saoCuStatsE0(diff + startX, rec0 + startX, stride, endX - startX, ctuHeight - skipB + plane_offset, m_offsetOrg[plane][SAO_EO_0], m_count[plane][SAO_EO_0]); } // SAO_EO_1: // dir: | { if (m_param->bSaoNonDeblocked) // 預設是disable, 忽略 { skipB = 4; skipR = 4; } rec = rec0; // 如果tpely = 0,就表示當前CTU位於最上方,因此從CTU的第二行開始進行統計 startY = !tpely; endX = (rpelx == picWidth) ? ctuWidth : ctuWidth - skipR + plane_offset; endY = (bpely == picHeight) ? ctuHeight - 1 : ctuHeight - skipB + plane_offset; if (!tpely) rec += stride; // 當前CTU第二行起始地址,為下面的sign計算做準備 // 計算當前CTU第二行與第一行的畫素差值,儲存在 upBuff1 中 primitives.sign(upBuff1, rec, &rec[- stride], ctuWidth); // 當前CTU按照 EO_1 模式對畫素進行歸類 primitives.saoCuStatsE1(diff + startY * MAX_CU_SIZE, rec0 + startY * stride, stride, upBuff1, endX, endY - startY, m_offsetOrg[plane][SAO_EO_1], m_count[plane][SAO_EO_1]); } // SAO_EO_2: // dir: 135 { if (m_param->bSaoNonDeblocked) // 預設是disable, 忽略 { skipB = 4; skipR = 5; } fenc = fenc0; rec = rec0; // 要計算某個畫素與左上方畫素(即135度方向)的差值,要確保左上方畫素存在, // 因此如果CTU位於影象的最左邊或最上方,startX、startY需為1 startX = !lpelx; endX = (rpelx == picWidth) ? ctuWidth - 1 : ctuWidth - skipR + plane_offset; startY = !tpely; endY = (bpely == picHeight) ? ctuHeight - 1 : ctuHeight - skipB + plane_offset; if (!tpely) { fenc += stride; // 原始影象第二行起始地址 rec += stride; // 當前CTU第二行起始地址,為下面的sign計算做準備 } // 計算當前CTU第二行與第一行的畫素差值(即與左上方畫素的差值),儲存在 upBuff1 中 primitives.sign(upBuff1, &rec[startX], &rec[startX - stride - 1], (endX - startX)); // 當前CTU按照 EO_2 模式對畫素進行歸類 primitives.saoCuStatsE2(diff + startX + startY * MAX_CU_SIZE, rec0 + startX + startY * stride, stride, upBuff1, upBufft, endX - startX, endY - startY, m_offsetOrg[plane][SAO_EO_2], m_count[plane][SAO_EO_2]); } // SAO_EO_3: // dir: 45 { if (m_param->bSaoNonDeblocked) // 預設是disable, 忽略 { skipB = 4; skipR = 5; } fenc = fenc0; rec = rec0; startX = !lpelx; endX = (rpelx == picWidth) ? ctuWidth - 1 : ctuWidth - skipR + plane_offset; startY = !tpely; endY = (bpely == picHeight) ? ctuHeight - 1 : ctuHeight - skipB + plane_offset; if (!tpely) { fenc += stride; // 原始影象第二行起始地址 rec += stride; // 當前CTU第二行起始地址,為下面的sign計算做準備 } // 計算當前CTU第二行與第一行的畫素差值(即與右上方畫素的差值),儲存在 upBuff1 中 primitives.sign(upBuff1, &rec[startX - 1], &rec[startX - 1 - stride + 1],(endX - startX + 1)); // 當前CTU按照 EO_3 模式對畫素進行歸類 primitives.saoCuStatsE3(diff + startX + startY * MAX_CU_SIZE, rec0 + startX + startY * stride, stride, upBuff1 + 1, endX - startX, endY - startY, m_offsetOrg[plane][SAO_EO_3], m_count[plane][SAO_EO_3]); } } /* 去方塊濾波前對CTU的畫素統計歸類,只有當SAO和bSaoNonDeblocked都開啟的情況下才使用,因此暫時忽略 */ void SAO::calcSaoStatsCu_BeforeDblk(Frame* frame, int idxX, int idxY); /* 計算CTU在各種模式下的最優SAO代價,與直接採用左邊或上面CTU的SAO引數作比較,找出最優的SAO代價, 並將最優SAO模式下的各種引數儲存在 saoParam->ctuParam[plane][add]中 */ void SAO::rdoSaoUnitCu(SAOParam* saoParam, int rowBaseAddr, int idxX, int addr) { Slice* slice = m_frame->m_encData->m_slice; const CUData* cu = m_frame->m_encData->getPicCTU(addr); int qp = cu->m_qp[0]; int64_t lambda[2] = { 0 }; int qpCb = qp; // 色度量化因子qpCb if (m_param->internalCsp == X265_CSP_I420) qpCb = x265_clip3(QP_MIN, QP_MAX_MAX, (int)g_chromaScale[qp + slice->m_pps->chromaQpOffset[0]]); else qpCb = X265_MIN(qp + slice->m_pps->chromaQpOffset[0], QP_MAX_SPEC); // lambda[0]用於亮度SAO引數計算,lambda[1]用於色度SAO引數計算 lambda[0] = (int64_t)floor(256.0 * x265_lambda2_tab[qp]); lambda[1] = (int64_t)floor(256.0 * x265_lambda2_tab[qpCb]); // 左邊和上面的CU是否存在 const bool allowMerge[2] = {(idxX != 0), (rowBaseAddr != 0)}; // 左邊和上面的CU的編號 const int addrMerge[2] = {(idxX ? addr - 1 : -1), (rowBaseAddr ? addr - m_numCuInWidth : -1)}; // 是否存在色度平面 bool chroma = m_param->internalCsp != X265_CSP_I400 && m_frame->m_fencPic->m_picCsp != X265_CSP_I400; // 我們只考慮I420格式,因此存在色度平面,因此此處planes取值3 int planes = chroma ? 3 : 1; // 選擇SAO方法處理去方塊邊界畫素,如果開啟則處理所有邊界畫素, // 關閉則不處理右邊和下面邊界的畫素;預設是關閉。 if (m_param->bSaoNonDeblocked) { memcpy(m_count, m_countPreDblk[addr], sizeof(m_count)); memcpy(m_offsetOrg, m_offsetOrgPreDblk[addr], sizeof(m_offsetOrg)); } else { // 初始化各模式各型別點的個數和失真值為0 memset(m_count, 0, sizeof(m_count)); memset(m_offsetOrg, 0, sizeof(m_offsetOrg)); } for (int i = 0; i < planes; i++) saoParam->ctuParam[i][addr].reset(); // 統計當前CTU的亮度塊在BO和EO各模式下的畫素歸類, // 包括重構畫素與原始畫素差值之和,以及對classIdx的計數 if (saoParam->bSaoFlag[0]) calcSaoStatsCTU(addr, 0); // 統計當前CTU的色度塊在BO和EO各模式下的畫素歸類 if (saoParam->bSaoFlag[1]) { calcSaoStatsCTU(addr, 1); calcSaoStatsCTU(addr, 2); } // 利用上一步的統計資訊計算BO和EO初始補償值 saoStatsInitialOffset(planes); // SAO distortion calculation m_entropyCoder.load(m_rdContexts.cur); m_entropyCoder.resetBits(); if (allowMerge[0]) m_entropyCoder.codeSaoMerge(0); if (allowMerge[1]) m_entropyCoder.codeSaoMerge(0); m_entropyCoder.store(m_rdContexts.temp); // Estimate distortion and cost of new SAO params int64_t bestCost = 0; int64_t rateDist = 0; // Estimate distortion and cost of new SAO params // 亮度和色度最優SAO模式的選擇,得到最優率失真代價 saoLumaComponentParamDist(saoParam, addr, rateDist, lambda, bestCost); if (chroma) saoChromaComponentParamDist(saoParam, addr, rateDist, lambda, bestCost); if (saoParam->bSaoFlag[0] || saoParam->bSaoFlag[1]) { // Cost of merge left or Up, mergeIdx為0表示左邊,為1表示上面 // 計算直接採用左邊和上面CTU的SAO引數的代價 for (int mergeIdx = 0; mergeIdx < 2; ++mergeIdx) { // 如果左邊或上面的CTU不存在,則跳過下面的計算,進入下一輪迴圈 if (!allowMerge[mergeIdx]) continue; int64_t mergeDist = 0; for (int plane = 0; plane < planes; plane++) { // 初始失真值為0,獲取左邊或上面CTU的SAO引數 int64_t estDist = 0; SaoCtuParam* mergeSrcParam = &(saoParam->ctuParam[plane][addrMerge[mergeIdx]]); int typeIdx = mergeSrcParam->typeIdx; if (typeIdx >= 0) { // 如果是邊帶模式,獲取第一個條帶的編號;否則取值1 int bandPos = (typeIdx == SAO_BO) ? mergeSrcParam->bandPos : 1; for (int classIdx = 0; classIdx < SAO_NUM_OFFSET; classIdx++) { // 根據4種類型的補償值來計算失真差值 int mergeOffset = mergeSrcParam->offset[classIdx]; estDist += estSaoDist(m_count[plane][typeIdx][classIdx + bandPos], mergeOffset, m_offsetOrg[plane][typeIdx][classIdx + bandPos]); } } mergeDist += (estDist << 8) / lambda[!!plane]; } m_entropyCoder.load(m_rdContexts.cur); m_entropyCoder.resetBits(); if (allowMerge[0]) m_entropyCoder.codeSaoMerge(1 - mergeIdx); if (allowMerge[1] && (mergeIdx == 1)) m_entropyCoder.codeSaoMerge(1); uint32_t estRate = m_entropyCoder.getNumberOfWrittenBits(); int64_t mergeCost = mergeDist + estRate; if (mergeCost < bestCost) { // merge的代價比SAO各模式代價更小,就採用merge模式 SaoMergeMode mergeMode = mergeIdx ? SAO_MERGE_UP : SAO_MERGE_LEFT; bestCost = mergeCost; m_entropyCoder.store(m_rdContexts.temp); for (int plane = 0; plane < planes; plane++) { // 更新SAO引數為merge模式下的引數 if (saoParam->bSaoFlag[plane > 0]) { SaoCtuParam* dstCtuParam = &saoParam->ctuParam[plane][addr]; SaoCtuParam* mergeSrcParam = &(saoParam->ctuParam[plane][addrMerge[mergeIdx]]); dstCtuParam->mergeMode = mergeMode; dstCtuParam->typeIdx = mergeSrcParam->typeIdx; dstCtuParam->bandPos = mergeSrcParam->bandPos; for (int i = 0; i < SAO_NUM_OFFSET; i++) dstCtuParam->offset[i] = mergeSrcParam->offset[i]; } } } // if (mergeCost < bestCost) 結束 } // mergeIdx迴圈結束 if (saoParam->ctuParam[0][addr].typeIdx < 0) m_numNoSao[0]++; if (chroma && saoParam->ctuParam[1][addr].typeIdx < 0) m_numNoSao[1]++; m_entropyCoder.load(m_rdContexts.temp); m_entropyCoder.store(m_rdContexts.cur); } } /* 利用先前已經得到的統計資訊(即m_count和m_offsetOrg)計算初始補償值(即m_offset) */ void SAO::saoStatsInitialOffset(int planes) { memset(m_offset, 0, sizeof(m_offset)); // EO for (int plane = 0; plane < planes; plane++) { // typeIdx, 邊界補償的四種模式,即 SAO_EO_X for (int typeIdx = 0; typeIdx < MAX_NUM_SAO_TYPE - 1; typeIdx++) { // 任意一種模式下,邊界點有四個種類 for (int classIdx = 1; classIdx < SAO_NUM_OFFSET + 1; classIdx++) { int32_t& count = m_count[plane][typeIdx][classIdx]; int32_t& offsetOrg = m_offsetOrg[plane][typeIdx][classIdx]; int32_t& offsetOut = m_offset[plane][typeIdx][classIdx]; if (count) { // 計算平均失真(offsetOrg/count並四捨五入),將其限制在[-7,7]之內 offsetOut = roundIBDI(offsetOrg, count << SAO_BIT_INC); offsetOut = x265_clip3(-OFFSET_THRESH + 1, OFFSET_THRESH - 1, offsetOut); // 種類1、種類2的補償值必須大於等於0; // 種類3、種類4的補償值必須小於等於0. if (classIdx < 3) offsetOut = X265_MAX(offsetOut, 0); else offsetOut = X265_MIN(offsetOut, 0); } } } } // BO,為每個條帶計算初始補償值 for (int plane = 0; plane < planes; plane++) { // 深度8位,256級分為32個邊帶,即[8k, 8k + 7]為第k個邊帶 for (int classIdx = 0; classIdx < MAX_NUM_SAO_CLASS; classIdx++) { int32_t& count = m_count[plane][SAO_BO][classIdx]; int32_t& offsetOrg = m_offsetOrg[plane][SAO_BO][classIdx]; int32_t& offsetOut = m_offset[plane][SAO_BO][classIdx]; if (count) { // 計算平均失真,並將其限制在[-7,7]之內 offsetOut = roundIBDI(offsetOrg, count << SAO_BIT_INC); offsetOut = x265_clip3(-OFFSET_THRESH + 1, OFFSET_THRESH - 1, offsetOut); } } } } /* 計算率失真代價值,公式為:(失真 + lambda * 編碼位元數)*/ inline int64_t SAO::calcSaoRdoCost(int64_t distortion, uint32_t bits, int64_t lambda) { // lambda = 256.0 * x265_lambda2_tab[],所以需要右移8位,即除以256 // 陣列x265_lambda2_tab 定義在 common/constants.cpp 中 return distortion + ((bits * lambda + 128) >> 8); } /** * @brief 找到最優率失真代價及對應的補償值和失真值. * @param typeIdx : SAO模式,即 SAO_EO_X 和 SAO_BO * @param lambda : 拉格朗日乘子,取值依賴QP,即 256.0 * x265_lambda2_tab[qp] * @param count : typeIdx模式下,某classIdx的點的數目 * @param offsetOrg : 原始畫素與重構畫素(SAO補償前)之間的差值之和 * @param offset[輸出] : 最優率失真代價對應的補償值 * @param distClasses[輸出] : 最優率失真代價對應的失真 * @param costClasses[輸出] : 最優率失真代價 */ void SAO::estIterOffset(int typeIdx, int64_t lambda, int32_t count, int32_t offsetOrg, int32_t& offset, int32_t& distClasses, int64_t& costClasses) { int bestOffset = 0; distClasses = 0; // Assuming sending quantized value 0 results in zero offset // and sending the value zero needs 1 bit. // entropy coder can be used to measure the exact rate here. int64_t bestCost = calcSaoRdoCost(0, 1, lambda); while (offset != 0) { // Calculate the bits required for signalling the offset uint32_t rate = (typeIdx == SAO_BO) ? (abs(offset) + 2) : (abs(offset) + 1); if (abs(offset) == OFFSET_THRESH - 1) rate--; // Do the dequntization before distorion calculation // 計算D_post和 D_pre的差值,即SAO補償前後失真的差值 int64_t dist = estSaoDist(count, offset << SAO_BIT_INC, offsetOrg); // 計算率失真代價 int64_t cost = calcSaoRdoCost(dist, rate, lambda); if (cost < bestCost) { bestCost = cost; bestOffset = offset; distClasses = (int)dist; } offset = (offset > 0) ? (offset - 1) : (offset + 1); } costClasses = bestCost; offset = bestOffset; } /* 尋找亮度最優SAO模式,得到最優率失真代價 */ void SAO::saoLumaComponentParamDist(SAOParam* saoParam, int32_t addr, int64_t& rateDist, int64_t* lambda, int64_t &bestCost) { int64_t bestDist = 0; int bestTypeIdx = -1; SaoCtuParam* lclCtuParam = &saoParam->ctuParam[0][addr]; int32_t distClasses[MAX_NUM_SAO_CLASS]; int64_t costClasses[MAX_NUM_SAO_CLASS]; // RDO SAO_NA m_entropyCoder.load(m_rdContexts.temp); m_entropyCoder.resetBits(); m_entropyCoder.codeSaoType(0); // 計算初始的率失真代價值 int64_t costPartBest = calcSaoRdoCost(0, m_entropyCoder.getNumberOfWrittenBits(), lambda[0]); // EO distortion calculation // 外迴圈是EO的模式,即4種方向,內迴圈是點的種類 for (int typeIdx = 0; typeIdx < MAX_NUM_SAO_TYPE - 1; typeIdx++) { int64_t estDist = 0; // 用於儲存某EO模式下各種類失真總和 for (int classIdx = 1; classIdx < SAO_NUM_OFFSET + 1; classIdx++) { int32_t& count = m_count[0][typeIdx][classIdx]; int32_t& offsetOrg = m_offsetOrg[0][typeIdx][classIdx]; int32_t& offsetOut = m_offset[0][typeIdx][classIdx]; // 計算率失真代價值最小的 offset estIterOffset(typeIdx, lambda[0], count, offsetOrg, offsetOut, distClasses[classIdx], costClasses[classIdx]); // Calculate distortion estDist += distClasses[classIdx]; } m_entropyCoder.load(m_rdContexts.temp); m_entropyCoder.resetBits(); m_entropyCoder.codeSaoOffsetEO(m_offset[0][typeIdx] + 1, typeIdx, 0); // 計算某EO模式下的率失真代價, // 如果比前面計算的更小,則更新最優率失真代和相應的EO模式值 int64_t cost = calcSaoRdoCost(estDist, m_entropyCoder.getNumberOfWrittenBits(), lambda[0]); if (cost < costPartBest) { costPartBest = cost; bestDist = estDist; bestTypeIdx = typeIdx; } } // 找到了最優的EO模式,則將最優模式值和補償值儲存起來 if (bestTypeIdx != -1) { lclCtuParam->mergeMode = SAO_MERGE_NONE; lclCtuParam->typeIdx = bestTypeIdx; lclCtuParam->bandPos = 0; for (int classIdx = 0; classIdx < SAO_NUM_OFFSET; classIdx++) lclCtuParam->offset[classIdx] = m_offset[0][bestTypeIdx][classIdx + 1]; } // BO RDO,為每個條帶計算最優率失真代價及對應的補償值 // costClasses 儲存了每個條帶的最優率失真代價 int64_t estDist = 0; for (int classIdx = 0; classIdx < MAX_NUM_SAO_CLASS; classIdx++) { int32_t& count = m_count[0][SAO_BO][classIdx]; int32_t& offsetOrg = m_offsetOrg[0][SAO_BO][classIdx]; int32_t& offsetOut = m_offset[0][SAO_BO][classIdx]; estIterOffset(SAO_BO, lambda[0], count, offsetOrg, offsetOut, distClasses[classIdx], costClasses[classIdx]); } // Estimate Best Position int64_t bestRDCostBO = MAX_INT64; int32_t bestClassBO = 0; // 統計任意連續4個條帶的最優率失真代價之和,找出值最小的連續4個條帶 for (int i = 0; i < MAX_NUM_SAO_CLASS - SAO_NUM_OFFSET + 1; i++) { int64_t currentRDCost = 0; for (int j = i; j < i + SAO_NUM_OFFSET; j++) currentRDCost += costClasses[j]; if (currentRDCost < bestRDCostBO) { bestRDCostBO = currentRDCost; bestClassBO = i; // 連續4個條帶的起始條帶編號 } } // 計算最優的連續4個條帶的失真之和 estDist = 0; for (int classIdx = bestClassBO; classIdx < bestClassBO + SAO_NUM_OFFSET; classIdx++) estDist += distClasses[classIdx]; m_entropyCoder.load(m_rdContexts.temp); m_entropyCoder.resetBits(); m_entropyCoder.codeSaoOffsetBO(m_offset[0][SAO_BO] + bestClassBO, bestClassBO, 0); // 計算BO模式下的率失真代價 int64_t cost = calcSaoRdoCost(estDist, m_entropyCoder.getNumberOfWrittenBits(), lambda[0]); // 如果BO模式下的率失真代價比上面EO模式下的率失真代價更小,則更新相應的SAO引數 if (cost < costPartBest) { costPartBest = cost; bestDist = estDist; lclCtuParam->mergeMode = SAO_MERGE_NONE; lclCtuParam->typeIdx = SAO_BO; lclCtuParam->bandPos = bestClassBO; for (int classIdx = 0; classIdx < SAO_NUM_OFFSET; classIdx++) lclCtuParam->offset[classIdx] = m_offset[0][SAO_BO][classIdx + bestClassBO]; } rateDist = (bestDist << 8) / lambda[0]; m_entropyCoder.load(m_rdContexts.temp); m_entropyCoder.codeSaoOffset(*lclCtuParam, 0); m_entropyCoder.store(m_rdContexts.temp); } /* 尋找色度最優SAO模式,得到最優率失真代價 */ void SAO::saoChromaComponentParamDist(SAOParam* saoParam, int32_t addr, int64_t& rateDist, int64_t* lambda, int64_t &bestCost); /* 統計某個CU內條帶點數目及失真之和,count和stats分別是條帶點計數和失真之和 */ void saoCuStatsBO_c(const int16_t *diff, const pixel *rec, intptr_t stride, int endX, int endY, int32_t *stats, int32_t *count) { const int boShift = X265_DEPTH - SAO_BO_BITS; for (int y = 0; y < endY; y++) { for (int x = 0; x < endX; x++) { int classIdx = rec[x] >> boShift; // 條帶編號 stats[classIdx] += diff[x]; // 某條帶失真累計 count[classIdx]++; // 某條帶點數目累計 } diff += MAX_CU_SIZE; // 下一行失真資料地址 rec += stride; // 當前CU的下一行重構影象地址 } } /* 統計CU內的點在EO_0模式(水平方向)下的各種類點的數目及失真之和 */ void saoCuStatsE0_c(const int16_t *diff, const pixel *rec, intptr_t stride, int endX, int endY, int32_t *stats, int32_t *count) { int32_t tmp_stats[SAO::NUM_EDGETYPE]; int32_t tmp_count[SAO::NUM_EDGETYPE]; memset(tmp_stats, 0, sizeof(tmp_stats)); memset(tmp_count, 0, sizeof(tmp_count)); for (int y = 0; y < endY; y++) { int signLeft = signOf(rec[0] - rec[-1]); // 當前邊界點的左符號 for (int x = 0; x < endX; x++) { int signRight = signOf2(rec[x], rec[x + 1]); // 當前邊界點的右符號 uint32_t edgeType = signRight + signLeft + 2; // 邊界點型別 signLeft = -signRight; // 當前點的右符號 = - 右邊點的左符號 // edgeType與真實的點種類轉換關係就是陣列 s_eoTable[] X265_CHECK(edgeType <= 4, "edgeType check failure\n"); tmp_stats[edgeType] += diff[x]; // 該型別點的失真累計 tmp_count[edgeType]++; // 該型別點的數目累計 } diff += MAX_CU_SIZE; rec += stride; } // 返回各型別點的失真之和和數目 for (int x = 0; x < SAO::NUM_EDGETYPE; x++) { stats[SAO::s_eoTable[x]] += tmp_stats[x]; count[SAO::s_eoTable[x]] += tmp_count[x]; } } /* 統計CU內的點在EO_1模式(垂直方向)下的各種類點的數目及失真之和 */ void saoCuStatsE1_c(const int16_t *diff, const pixel *rec, intptr_t stride, int8_t *upBuff1, int endX, int endY, int32_t *stats, int32_t *count); /* 統計CU內的點在EO_2模式(135度方向)下的各種類點的數目及失真之和 */ void saoCuStatsE2_c(const int16_t *diff, const pixel *rec, intptr_t stride, int8_t *upBuff1, int8_t *upBufft, int endX, int endY, int32_t *stats, int32_t *count); /* 統計CU內的點在EO_3模式(45度方向)下的各種類點的數目及失真之和 */ void saoCuStatsE3_c(const int16_t *diff, const pixel *rec, intptr_t stride, int8_t *upBuff1, int endX, int endY, int32_t *stats, int32_t *count);