package org.jcodec.codecs.h264.encode; import static java.lang.Math.abs; import static org.jcodec.common.tools.MathUtil.clip; import org.jcodec.codecs.h264.decode.deblock.DeblockingFilter; 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 * * Contains various deblocking filter routines for deblocking on MB bases * * @author Stan Vitvitskyy * */ public class MBDeblocker { static int[][] LOOKUP_IDX_P_V = new int[][] { { 3, 7, 11, 15 }, { 0, 4, 8, 12 }, { 1, 5, 9, 13 }, { 2, 6, 10, 14 } }; static int[][] LOOKUP_IDX_Q_V = new int[][] { { 0, 4, 8, 12 }, { 1, 5, 9, 13 }, { 2, 6, 10, 14 }, { 3, 7, 11, 15 } }; static int[][] LOOKUP_IDX_P_H = new int[][] { { 12, 13, 14, 15 }, { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } }; static int[][] LOOKUP_IDX_Q_H = new int[][] { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 }, { 12, 13, 14, 15 } }; // Luma Chroma // 4444 44 // 4333 43 // 4333 // 4333 private static int[][] BS_I = { { 4, 4, 4, 4 }, { 3, 3, 3, 3 }, { 3, 3, 3, 3 }, { 3, 3, 3, 3 } }; /** * Deblocks bottom edge of topOutMB, right edge of leftOutMB and left/top * and inner block edges of outMB * * @param curPix * Pixels of the current MB * @param leftPix * Pixels of the leftMB * @param topPix * Pixels of the tipMB * * @param vertStrength * Border strengths for vertical edges (filtered first) * @param horizStrength * Border strengths for the horizontal edges * * @param curQp * Current MB's qp * @param leftQp * Left MB's qp * @param topQp * Top MB's qp */ public void deblockMBGeneric(EncodedMB curMB, EncodedMB leftMB, EncodedMB topMB, int[][] vertStrength, int horizStrength[][]) { Picture8Bit curPix = curMB.getPixels(); if (leftMB != null) { Picture8Bit leftPix = leftMB.getPixels(); int avgQp = MathUtil.clip((leftMB.getQp() + curMB.getQp() + 1) >> 1, 0, 51); deblockBorder(vertStrength[0], avgQp, leftPix.getPlaneData(0), 3, curPix.getPlaneData(0), 0, P_POS_V, Q_POS_V, false); deblockBorderChroma(vertStrength[0], avgQp, leftPix.getPlaneData(1), 3, curPix.getPlaneData(1), 0, P_POS_V_CHR, Q_POS_V_CHR, false); deblockBorderChroma(vertStrength[0], avgQp, leftPix.getPlaneData(2), 3, curPix.getPlaneData(2), 0, P_POS_V_CHR, Q_POS_V_CHR, false); } for (int i = 0; i < 3; i++) { deblockBorder(vertStrength[i + 1], curMB.getQp(), curPix.getPlaneData(0), i, curPix.getPlaneData(0), i + 1, P_POS_V, Q_POS_V, false); deblockBorderChroma(vertStrength[i + 1], curMB.getQp(), curPix.getPlaneData(1), i, curPix.getPlaneData(1), i + 1, P_POS_V_CHR, Q_POS_V_CHR, false); deblockBorderChroma(vertStrength[i + 1], curMB.getQp(), curPix.getPlaneData(2), i, curPix.getPlaneData(2), i + 1, P_POS_V_CHR, Q_POS_V_CHR, false); } if (topMB != null) { Picture8Bit topPix = topMB.getPixels(); int avgQp = MathUtil.clip((topMB.getQp() + curMB.getQp() + 1) >> 1, 0, 51); deblockBorder(horizStrength[0], avgQp, topPix.getPlaneData(0), 3, curPix.getPlaneData(0), 0, P_POS_H, Q_POS_H, true); deblockBorderChroma(horizStrength[0], avgQp, topPix.getPlaneData(1), 3, curPix.getPlaneData(1), 0, P_POS_H_CHR, Q_POS_H_CHR, true); deblockBorderChroma(horizStrength[0], avgQp, topPix.getPlaneData(2), 3, curPix.getPlaneData(2), 0, P_POS_H_CHR, Q_POS_H_CHR, true); } for (int i = 0; i < 3; i++) { deblockBorder(horizStrength[i + 1], curMB.getQp(), curPix.getPlaneData(0), i, curPix.getPlaneData(0), i + 1, P_POS_H, Q_POS_H, true); deblockBorderChroma(horizStrength[i + 1], curMB.getQp(), curPix.getPlaneData(1), i, curPix.getPlaneData(1), i + 1, P_POS_H_CHR, Q_POS_H_CHR, true); deblockBorderChroma(horizStrength[i + 1], curMB.getQp(), curPix.getPlaneData(2), i, curPix.getPlaneData(2), i + 1, P_POS_H_CHR, Q_POS_H_CHR, true); } } public void deblockMBI(EncodedMB outMB, EncodedMB leftOutMB, EncodedMB topOutMB) { deblockMBGeneric(outMB, leftOutMB, topOutMB, BS_I, BS_I); } /** * Deblocks P-macroblock * * @param cur * Pixels and parameters of encoded and reconstructed current * macroblock * @param left * Pixels and parameters of encoded and reconstructed left * macroblock * @param top * Pixels and parameters of encoded and reconstructed top * macroblock */ public void deblockMBP(EncodedMB cur, EncodedMB left, EncodedMB top) { int[][] vertStrength = new int[4][4]; int[][] horizStrength = new int[4][4]; calcStrengthForBlocks(cur, left, vertStrength, LOOKUP_IDX_P_V, LOOKUP_IDX_Q_V); calcStrengthForBlocks(cur, top, horizStrength, LOOKUP_IDX_P_H, LOOKUP_IDX_Q_H); deblockMBGeneric(cur, left, top, vertStrength, horizStrength); } private void deblockBorder(int[] boundary, int qp, byte[] p, int pi, byte[] q, int qi, int[][] pTab, int[][] qTab, boolean horiz) { int inc1 = horiz ? 16 : 1, inc2 = inc1 * 2, inc3 = inc1 * 3; for (int b = 0; b < 4; b++) { if (boundary[b] == 4) { for (int i = 0, ii = b << 2; i < 4; ++i, ++ii) filterBs4(qp, qp, p, q, pTab[pi][ii] - inc3, pTab[pi][ii] - inc2, pTab[pi][ii] - inc1, pTab[pi][ii], qTab[qi][ii], qTab[qi][ii] + inc1, qTab[qi][ii] + inc2, qTab[qi][ii] + inc3); } else if (boundary[b] > 0) { for (int i = 0, ii = b << 2; i < 4; ++i, ++ii) filterBs(boundary[b], qp, qp, p, q, pTab[pi][ii] - inc2, pTab[pi][ii] - inc1, pTab[pi][ii], qTab[qi][ii], qTab[qi][ii] + inc1, qTab[qi][ii] + inc2); } } } protected void filterBs4Chr(int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p1Idx, int p0Idx, int q0Idx, int q1Idx) { _filterBs4(indexAlpha, indexBeta, pelsP, pelsQ, -1, -1, p1Idx, p0Idx, q0Idx, q1Idx, -1, -1, true); } protected void filterBsChr(int bs, int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p1Idx, int p0Idx, int q0Idx, int q1Idx) { _filterBs(bs, indexAlpha, indexBeta, pelsP, pelsQ, -1, p1Idx, p0Idx, q0Idx, q1Idx, -1, true); } protected 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) { _filterBs4(indexAlpha, indexBeta, pelsP, pelsQ, p3Idx, p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, q3Idx, false); } protected void filterBs(int bs, int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p2Idx, int p1Idx, int p0Idx, int q0Idx, int q1Idx, int q2Idx) { _filterBs(bs, indexAlpha, indexBeta, pelsP, pelsQ, p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, false); } protected 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 = DeblockingFilter.alphaTab[indexAlpha]; int betaThresh = DeblockingFilter.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); } } protected 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 = DeblockingFilter.alphaTab[indexAlpha]; int betaThresh = DeblockingFilter.betaTab[indexBeta]; boolean filterEnabled = abs(p0 - q0) < alphaThresh && abs(p1 - p0) < betaThresh && abs(q1 - q0) < betaThresh; if (!filterEnabled) return; int tC0 = DeblockingFilter.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); } private void deblockBorderChroma(int[] boundary, int qp, byte[] p, int pi, byte[] q, int qi, int[][] pTab, int[][] qTab, boolean horiz) { int inc1 = horiz ? 8 : 1; for (int b = 0; b < 4; b++) { if (boundary[b] == 4) { for (int i = 0, ii = b << 1; i < 2; ++i, ++ii) filterBs4Chr(qp, qp, p, q, pTab[pi][ii] - inc1, pTab[pi][ii], qTab[qi][ii], qTab[qi][ii] + inc1); } else if (boundary[b] > 0) { for (int i = 0, ii = b << 1; i < 2; ++i, ++ii) filterBsChr(boundary[b], qp, qp, p, q, pTab[pi][ii] - inc1, pTab[pi][ii], qTab[qi][ii], qTab[qi][ii] + inc1); } } } private static int[][] buildPPosH() { int[][] qPos = new int[4][16]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 16; j++) { qPos[i][j] = j + (i << 6) + 48; } } return qPos; } private static int[][] buildQPosH() { int[][] pPos = new int[4][16]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 16; j++) { pPos[i][j] = j + (i << 6); } } return pPos; } private static int[][] buildPPosV() { int[][] qPos = new int[4][16]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 16; j++) { qPos[i][j] = (j << 4) + (i << 2) + 3; } } return qPos; } private static int[][] buildQPosV() { int[][] pPos = new int[4][16]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 16; j++) { pPos[i][j] = (j << 4) + (i << 2); } } return pPos; } private static int[][] buildPPosHChr() { int[][] qPos = new int[4][8]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 8; j++) { qPos[i][j] = j + (i << 4) + 8; } } return qPos; } private static int[][] buildQPosHChr() { int[][] pPos = new int[4][8]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 8; j++) { pPos[i][j] = j + (i << 4); } } return pPos; } private static int[][] buildPPosVChr() { int[][] qPos = new int[4][8]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 8; j++) { qPos[i][j] = (j << 3) + (i << 1) + 1; } } return qPos; } private static int[][] buildQPosVChr() { int[][] pPos = new int[4][8]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 8; j++) { pPos[i][j] = (j << 3) + (i << 1); } } return pPos; } static void calcStrengthForBlocks(EncodedMB cur, EncodedMB other, int[][] outStrength, int[][] LOOKUP_IDX_P, int[][] LOOKUP_IDX_Q) { if (other != null) { for (int i = 0; i < 4; ++i) { outStrength[0][i] = other.getType().isIntra() ? 4 : MathUtil.max3( strengthMv(other.getMx()[LOOKUP_IDX_P[0][i]], cur.getMx()[LOOKUP_IDX_Q[0][i]]), strengthMv(other.getMy()[LOOKUP_IDX_P[0][i]], cur.getMy()[LOOKUP_IDX_Q[0][i]]), strengthNc(other.getNc()[LOOKUP_IDX_P[0][i]], cur.getNc()[LOOKUP_IDX_Q[0][i]])); } } for (int i = 1; i < 4; i++) { for (int j = 0; j < 4; ++j) { outStrength[i][j] = MathUtil.max3( strengthMv(cur.getMx()[LOOKUP_IDX_P[i][j]], cur.getMx()[LOOKUP_IDX_Q[i][j]]), strengthMv(cur.getMy()[LOOKUP_IDX_P[i][j]], cur.getMy()[LOOKUP_IDX_Q[i][j]]), strengthNc(cur.getNc()[LOOKUP_IDX_P[i][j]], cur.getNc()[LOOKUP_IDX_Q[i][j]])); } } } private static int strengthNc(int ncA, int ncB) { return ncA > 0 || ncB > 0 ? 2 : 0; } private static int strengthMv(int v0, int v1) { return abs(v0 - v1) >= 4 ? 1 : 0; } private static int[][] P_POS_V = buildPPosV(); private static int[][] Q_POS_V = buildQPosV(); private static int[][] P_POS_H = buildPPosH(); private static int[][] Q_POS_H = buildQPosH(); private static int[][] P_POS_V_CHR = buildPPosVChr(); private static int[][] Q_POS_V_CHR = buildQPosVChr(); private static int[][] P_POS_H_CHR = buildPPosHChr(); private static int[][] Q_POS_H_CHR = buildQPosHChr(); }