package org.jcodec.codecs.h264.decode.deblock; import static java.lang.Math.abs; import static org.jcodec.common.tools.MathUtil.clip; import org.jcodec.codecs.h264.decode.DeblockerInput; import org.jcodec.codecs.h264.io.model.SliceHeader; import org.jcodec.common.model.ColorSpace; import org.jcodec.common.model.Picture8Bit; import org.jcodec.common.tools.MathUtil; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * A filter that removes DCT artifacts on block boundaries. * * It's operation is dependant on QP and is designed the way that the strenth is * adjusted to the likelyhood of appearence of blocking artifacts on the * specific edges. * * Builds a parameter for deblocking filter based on the properties of specific * macroblocks. * * A parameter specifies the behavior of deblocking filter on each of 8 edges * that need to filtered for a macroblock. * * For each edge the following things are evaluated on it's both sides: presence * of DCT coded residual; motion vector difference; spatial location. * * * @author The JCodec project * */ public class DeblockingFilter { public static int[] alphaTab = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 17, 20, 22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 71, 80, 90, 101, 113, 127, 144, 162, 182, 203, 226, 255, 255 }; public static int[] betaTab = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18 }; public static int[][] tcs = new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6, 6, 7, 8, 9, 10, 11, 13 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 10, 11, 12, 13, 15, 17 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6, 6, 7, 8, 9, 10, 11, 13, 14, 16, 18, 20, 23, 25 } }; private DeblockerInput di; public DeblockingFilter(int bitDepthLuma, int bitDepthChroma, DeblockerInput di) { this.di = di; } public void deblockFrame(Picture8Bit result) { ColorSpace color = result.getColor(); // for (int i = 0; i < shs.length; i++) // printMB(result.getPlaneData(2), result.getPlaneWidth(2), i, shs[i], // "!--!--!--!--!--!--!--!--!--!--!--!"); // printMB(result.getPlaneData(0), result.getPlaneWidth(0), 0, shs[0], // "!--!--!--!--!--!--!--!--!--!--!--!"); int[][] bsV = new int[4][4], bsH = new int[4][4]; for (int i = 0; i < di.shs.length; i++) { calcBsH(result, i, bsH); calcBsV(result, i, bsV); for (int c = 0; c < color.nComp; c++) { fillVerticalEdge(result, c, i, bsV); fillHorizontalEdge(result, c, i, bsH); // printMB(result.getPlaneData(1), result.getPlaneWidth(1), i, // shs[i], // "!**!**!**!**!--!--!--!--!--!--!--!"); } } // printMB(result.getPlaneData(0), result.getPlaneWidth(0), 235, // shs[235], "!**!**!**!**!--!--!--!--!--!--!--!"); } // private void printMB(int[] is, int stride, int mbAddr, SliceHeader sh, // String delim) { // int mbWidth = sh.sps.pic_width_in_mbs_minus1 + 1; // int mbX = mbAddr % mbWidth; // int mbY = mbAddr / mbWidth; // // System.out.println("MB: " + mbX + ", " + mbY); // System.out.println(delim); // for (int j = 0; j < 16; j++) { // for (int i = 0; i < 16; i++) // System.out.print(String.format("%3d,", is[((mbY << 4) + j) * stride + // (mbX << 4) + i])); // System.out.println(); // } // System.out.println(delim); // } static int[] inverse = new int[] { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15 }; private int calcBoundaryStrenth(boolean atMbBoundary, boolean leftIntra, boolean rightIntra, int leftCoeff, int rightCoeff, int[] mvA0, int[] mvB0, int[] mvA1, int[] mvB1, int mbAddrA, int mbAddrB) { if (atMbBoundary && (leftIntra || rightIntra)) return 4; else if (leftIntra || rightIntra) return 3; else { if (leftCoeff > 0 || rightCoeff > 0) return 2; int nA = (mvA0[2] == -1 ? 0 : 1) + (mvA1[2] == -1 ? 0 : 1); int nB = (mvB0[2] == -1 ? 0 : 1) + (mvB1[2] == -1 ? 0 : 1); if (nA != nB) return 1; Picture8Bit ra0 = mvA0[2] < 0 ? null : di.refsUsed[mbAddrA][0][mvA0[2]]; Picture8Bit ra1 = mvA1[2] < 0 ? null : di.refsUsed[mbAddrA][1][mvA1[2]]; Picture8Bit rb0 = mvB0[2] < 0 ? null : di.refsUsed[mbAddrB][0][mvB0[2]]; Picture8Bit rb1 = mvB1[2] < 0 ? null : di.refsUsed[mbAddrB][1][mvB1[2]]; if (ra0 != rb0 && ra0 != rb1 || ra1 != rb0 && ra1 != rb1 || rb0 != ra0 && rb0 != ra1 || rb1 != ra0 && rb1 != ra1) return 1; if (ra0 == ra1 && ra1 == rb0 && rb0 == rb1) { return ra0 != null && (mvThresh(mvA0, mvB0) || mvThresh(mvA1, mvB0) || mvThresh(mvA0, mvB1) || mvThresh(mvA1, mvB1)) ? 1 : 0; } else if (ra0 == rb0 && ra1 == rb1) { return ra0 != null && mvThresh(mvA0, mvB0) || ra1 != null && mvThresh(mvA1, mvB1) ? 1 : 0; } else if (ra0 == rb1 && ra1 == rb0) { return ra0 != null && mvThresh(mvA0, mvB1) || ra1 != null && mvThresh(mvA1, mvB0) ? 1 : 0; } } return 0; } private boolean mvThresh(int[] v0, int[] v1) { return abs(v0[0] - v1[0]) >= 4 || abs(v0[1] - v1[1]) >= 4; } private static int getIdxBeta(int sliceBetaOffset, int avgQp) { return MathUtil.clip(avgQp + sliceBetaOffset, 0, 51); } private static int getIdxAlpha(int sliceAlphaC0Offset, int avgQp) { return MathUtil.clip(avgQp + sliceAlphaC0Offset, 0, 51); } private void calcBsH(Picture8Bit pic, int mbAddr, int[][] bs) { SliceHeader sh = di.shs[mbAddr]; int mbWidth = sh.sps.pic_width_in_mbs_minus1 + 1; int mbX = mbAddr % mbWidth; int mbY = mbAddr / mbWidth; boolean topAvailable = mbY > 0 && (sh.disable_deblocking_filter_idc != 2 || di.shs[mbAddr - mbWidth] == sh); boolean thisIntra = di.mbTypes[mbAddr] != null && di.mbTypes[mbAddr].isIntra(); if (topAvailable) { boolean topIntra = di.mbTypes[mbAddr - mbWidth] != null && di.mbTypes[mbAddr - mbWidth].isIntra(); for (int blkX = 0; blkX < 4; blkX++) { int thisBlkX = (mbX << 2) + blkX; int thisBlkY = (mbY << 2); bs[0][blkX] = calcBoundaryStrenth(true, topIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], di.nCoeff[thisBlkY - 1][thisBlkX], di.mvs[0][thisBlkY][thisBlkX], di.mvs[0][thisBlkY - 1][thisBlkX], di.mvs[1][thisBlkY][thisBlkX], di.mvs[1][thisBlkY - 1][thisBlkX], mbAddr, mbAddr - mbWidth); } } for (int blkY = 1; blkY < 4; blkY++) { for (int blkX = 0; blkX < 4; blkX++) { int thisBlkX = (mbX << 2) + blkX; int thisBlkY = (mbY << 2) + blkY; bs[blkY][blkX] = calcBoundaryStrenth(false, thisIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], di.nCoeff[thisBlkY - 1][thisBlkX], di.mvs[0][thisBlkY][thisBlkX], di.mvs[0][thisBlkY - 1][thisBlkX], di.mvs[1][thisBlkY][thisBlkX], di.mvs[1][thisBlkY - 1][thisBlkX], mbAddr, mbAddr); } } } private void fillHorizontalEdge(Picture8Bit pic, int comp, int mbAddr, int[][] bs) { SliceHeader sh = di.shs[mbAddr]; int mbWidth = sh.sps.pic_width_in_mbs_minus1 + 1; int alpha = sh.slice_alpha_c0_offset_div2 << 1; int beta = sh.slice_beta_offset_div2 << 1; int mbX = mbAddr % mbWidth; int mbY = mbAddr / mbWidth; boolean topAvailable = mbY > 0 && (sh.disable_deblocking_filter_idc != 2 || di.shs[mbAddr - mbWidth] == sh); int curQp = di.mbQps[comp][mbAddr]; int cW = 2 - pic.getColor().compWidth[comp]; int cH = 2 - pic.getColor().compHeight[comp]; if (topAvailable) { int topQp = di.mbQps[comp][mbAddr - mbWidth]; int avgQp = (topQp + curQp + 1) >> 1; for (int blkX = 0; blkX < 4; blkX++) { int thisBlkX = (mbX << 2) + blkX; int thisBlkY = (mbY << 2); filterBlockEdgeHoris(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, avgQp), getIdxBeta(beta, avgQp), bs[0][blkX], 1 << cW); } } boolean skip4x4 = comp == 0 && di.tr8x8Used[mbAddr] || cH == 1; for (int blkY = 1; blkY < 4; blkY++) { if (skip4x4 && (blkY & 1) == 1) continue; for (int blkX = 0; blkX < 4; blkX++) { int thisBlkX = (mbX << 2) + blkX; int thisBlkY = (mbY << 2) + blkY; filterBlockEdgeHoris(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, curQp), getIdxBeta(beta, curQp), bs[blkY][blkX], 1 << cW); } } } private void calcBsV(Picture8Bit pic, int mbAddr, int[][] bs) { SliceHeader sh = di.shs[mbAddr]; int mbWidth = sh.sps.pic_width_in_mbs_minus1 + 1; int mbX = mbAddr % mbWidth; int mbY = mbAddr / mbWidth; boolean leftAvailable = mbX > 0 && (sh.disable_deblocking_filter_idc != 2 || di.shs[mbAddr - 1] == sh); boolean thisIntra = di.mbTypes[mbAddr] != null && di.mbTypes[mbAddr].isIntra(); if (leftAvailable) { boolean leftIntra = di.mbTypes[mbAddr - 1] != null && di.mbTypes[mbAddr - 1].isIntra(); for (int blkY = 0; blkY < 4; blkY++) { int thisBlkX = (mbX << 2); int thisBlkY = (mbY << 2) + blkY; bs[blkY][0] = calcBoundaryStrenth(true, leftIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], di.nCoeff[thisBlkY][thisBlkX - 1], di.mvs[0][thisBlkY][thisBlkX], di.mvs[0][thisBlkY][thisBlkX - 1], di.mvs[1][thisBlkY][thisBlkX], di.mvs[1][thisBlkY][thisBlkX - 1], mbAddr, mbAddr - 1); } } for (int blkX = 1; blkX < 4; blkX++) { for (int blkY = 0; blkY < (1 << 2); blkY++) { int thisBlkX = (mbX << 2) + blkX; int thisBlkY = (mbY << 2) + blkY; bs[blkY][blkX] = calcBoundaryStrenth(false, thisIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], di.nCoeff[thisBlkY][thisBlkX - 1], di.mvs[0][thisBlkY][thisBlkX], di.mvs[0][thisBlkY][thisBlkX - 1], di.mvs[1][thisBlkY][thisBlkX], di.mvs[1][thisBlkY][thisBlkX - 1], mbAddr, mbAddr); } } } private void fillVerticalEdge(Picture8Bit pic, int comp, int mbAddr, int[][] bs) { SliceHeader sh = di.shs[mbAddr]; int mbWidth = sh.sps.pic_width_in_mbs_minus1 + 1; int alpha = sh.slice_alpha_c0_offset_div2 << 1; int beta = sh.slice_beta_offset_div2 << 1; int mbX = mbAddr % mbWidth; int mbY = mbAddr / mbWidth; boolean leftAvailable = mbX > 0 && (sh.disable_deblocking_filter_idc != 2 || di.shs[mbAddr - 1] == sh); int curQp = di.mbQps[comp][mbAddr]; int cW = 2 - pic.getColor().compWidth[comp]; int cH = 2 - pic.getColor().compHeight[comp]; if (leftAvailable) { int leftQp = di.mbQps[comp][mbAddr - 1]; int avgQpV = (leftQp + curQp + 1) >> 1; for (int blkY = 0; blkY < 4; blkY++) { int thisBlkX = (mbX << 2); int thisBlkY = (mbY << 2) + blkY; filterBlockEdgeVert(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, avgQpV), getIdxBeta(beta, avgQpV), bs[blkY][0], 1 << cH); } } boolean skip4x4 = comp == 0 && di.tr8x8Used[mbAddr] || cW == 1; for (int blkX = 1; blkX < 4; blkX++) { if (skip4x4 && (blkX & 1) == 1) continue; for (int blkY = 0; blkY < 4; blkY++) { int thisBlkX = (mbX << 2) + blkX; int thisBlkY = (mbY << 2) + blkY; filterBlockEdgeVert(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, curQp), getIdxBeta(beta, curQp), bs[blkY][blkX], 1 << cH); } } } private void filterBlockEdgeHoris(Picture8Bit pic, int comp, int x, int y, int indexAlpha, int indexBeta, int bs, int blkW) { int stride = pic.getPlaneWidth(comp); int offset = y * stride + x; for (int pixOff = 0; pixOff < blkW; pixOff++) { int p2Idx = offset - 3 * stride + pixOff; int p1Idx = offset - 2 * stride + pixOff; int p0Idx = offset - stride + pixOff; int q0Idx = offset + pixOff; int q1Idx = offset + stride + pixOff; int q2Idx = offset + 2 * stride + pixOff; if (bs == 4) { int p3Idx = offset - 4 * stride + pixOff; int q3Idx = offset + 3 * stride + pixOff; filterBs4(indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p3Idx, p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, q3Idx, comp != 0); } else if (bs > 0) { filterBs(bs, indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, comp != 0); } } } private void filterBlockEdgeVert(Picture8Bit pic, int comp, int x, int y, int indexAlpha, int indexBeta, int bs, int blkH) { int stride = pic.getPlaneWidth(comp); for (int i = 0; i < blkH; i++) { int offsetQ = (y + i) * stride + x; int p2Idx = offsetQ - 3; int p1Idx = offsetQ - 2; int p0Idx = offsetQ - 1; int q0Idx = offsetQ; int q1Idx = offsetQ + 1; int q2Idx = offsetQ + 2; if (bs == 4) { int p3Idx = offsetQ - 4; int q3Idx = offsetQ + 3; filterBs4(indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p3Idx, p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, q3Idx, comp != 0); } else if (bs > 0) { filterBs(bs, indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, comp != 0); } } } public static void filterBs(int bs, int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p2Idx, int p1Idx, int p0Idx, int q0Idx, int q1Idx, int q2Idx, boolean isChroma) { int p1 = pelsP[p1Idx]; int p0 = pelsP[p0Idx]; int q0 = pelsQ[q0Idx]; int q1 = pelsQ[q1Idx]; int alphaThresh = alphaTab[indexAlpha]; int betaThresh = betaTab[indexBeta]; boolean filterEnabled = abs(p0 - q0) < alphaThresh && abs(p1 - p0) < betaThresh && abs(q1 - q0) < betaThresh; if (!filterEnabled) return; // System.out.printf("%h %h %h %h %h %h %h %h\n", q3, q2, q1, q0, p0, // p1, p2, p3); int tC0 = tcs[bs - 1][indexAlpha]; boolean conditionP, conditionQ; int tC; if (!isChroma) { int ap = abs(pelsP[p2Idx] - p0); int aq = abs(pelsQ[q2Idx] - q0); tC = tC0 + ((ap < betaThresh) ? 1 : 0) + ((aq < betaThresh) ? 1 : 0); conditionP = ap < betaThresh; conditionQ = aq < betaThresh; } else { tC = tC0 + 1; conditionP = false; conditionQ = false; } int sigma = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); sigma = sigma < -tC ? -tC : (sigma > tC ? tC : sigma); int p0n = p0 + sigma; p0n = p0n < -128 ? -128 : p0n; int q0n = q0 - sigma; q0n = q0n < -128 ? -128 : q0n; if (conditionP) { int p2 = pelsP[p2Idx]; int diff = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1; diff = diff < -tC0 ? -tC0 : (diff > tC0 ? tC0 : diff); int p1n = p1 + diff; pelsP[p1Idx] = (byte)clip(p1n, -128, 127); } if (conditionQ) { int q2 = pelsQ[q2Idx]; int diff = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1; diff = diff < -tC0 ? -tC0 : (diff > tC0 ? tC0 : diff); int q1n = q1 + diff; pelsQ[q1Idx] = (byte)clip(q1n, -128, 127); } pelsQ[q0Idx] = (byte)clip(q0n, -128, 127); pelsP[p0Idx] = (byte)clip(p0n, -128, 127); } public static void filterBs4(int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p3Idx, int p2Idx, int p1Idx, int p0Idx, int q0Idx, int q1Idx, int q2Idx, int q3Idx, boolean isChroma) { int p0 = pelsP[p0Idx]; int q0 = pelsQ[q0Idx]; int p1 = pelsP[p1Idx]; int q1 = pelsQ[q1Idx]; int alphaThresh = alphaTab[indexAlpha]; int betaThresh = betaTab[indexBeta]; boolean filterEnabled = abs(p0 - q0) < alphaThresh && abs(p1 - p0) < betaThresh && abs(q1 - q0) < betaThresh; if (!filterEnabled) return; boolean conditionP, conditionQ; if (isChroma) { conditionP = false; conditionQ = false; } else { int ap = abs(pelsP[p2Idx] - p0); int aq = abs(pelsQ[q2Idx] - q0); conditionP = ap < betaThresh && abs(p0 - q0) < ((alphaThresh >> 2) + 2); conditionQ = aq < betaThresh && abs(p0 - q0) < ((alphaThresh >> 2) + 2); } if (conditionP) { int p3 = pelsP[p3Idx]; int p2 = pelsP[p2Idx]; int p0n = (p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3; int p1n = (p2 + p1 + p0 + q0 + 2) >> 2; int p2n = (2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3; pelsP[p0Idx] = (byte)clip(p0n, -128, 127); pelsP[p1Idx] = (byte)clip(p1n, -128, 127); pelsP[p2Idx] = (byte)clip(p2n, -128, 127); } else { int p0n = (2 * p1 + p0 + q1 + 2) >> 2; pelsP[p0Idx] = (byte)clip(p0n, -128, 127); } if (conditionQ && !isChroma) { int q2 = pelsQ[q2Idx]; int q3 = pelsQ[q3Idx]; int q0n = (p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3; int q1n = (p0 + q0 + q1 + q2 + 2) >> 2; int q2n = (2 * q3 + 3 * q2 + q1 + q0 + p0 + 4) >> 3; pelsQ[q0Idx] = (byte)clip(q0n, -128, 127); pelsQ[q1Idx] = (byte)clip(q1n, -128, 127); pelsQ[q2Idx] = (byte)clip(q2n, -128, 127); } else { int q0n = (2 * q1 + q0 + p1 + 2) >> 2; pelsQ[q0Idx] = (byte)clip(q0n, -128, 127); } } }