VideoCodec/Modules/StreamCoding/HuffmanEntropyDecoder.cpp
#include "HuffmanEntropyDecoder.h"
// Macros to get the macroblock type based on the bitmask.
#define IS_QUANT(x) (((x) & 0x10) == 0x10)
#define IS_MOTION_FORWARD(x) (((x) & 0x8) == 0x8)
#define IS_MOTION_BACKWARD(x) (((x) & 0x4) == 0x4)
#define IS_PATTERN(x) (((x) & 0x2) == 0x2)
#define IS_INTRA(x) (((x) & 0x1) == 0x1)
namespace VideoCodec
{
static unsigned char reverseZigZagScanIndicesEightByEight[64] =
{0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63};
HuffmanEntropyDecoder::HuffmanEntropyDecoder() : Module(HUFFMAN_ENTROPY_DECODER_MODULE_ID)
{
}
HuffmanEntropyDecoder::~HuffmanEntropyDecoder()
{
}
ConfigurationStatus HuffmanEntropyDecoder::Configure(ConfigurationElement* configuration)
{
return MODULE_CONFIGURATION_OK;
}
void HuffmanEntropyDecoder::decodeInterFrame(BinaryStreamReader* reader, StreamHeader* streamHeader, FrameHeader* frameHeader, PictureInfo* picture)
{
BlockCount blockRows = streamHeader->blockRows;
BlockCount blockColumns = streamHeader->blockColumns;
SignedShort* blockEncodedValues = new SignedShort[8 * 8];
SignedShort* frameTransformedValues = new SignedShort[8 * 8 * 6 * blockRows * blockColumns];
memset((void*)frameTransformedValues, 0, sizeof(SignedShort) * 8 * 8 * 6 * blockRows * blockColumns);
// Motion vector component prediction values.
SignedByte xPrediction = 0;
SignedByte yPrediction = 0;
// Decoded motion vector components. Zero them so that we don't have to set blank ones manually.
SignedByte* xComponents;
SignedByte* yComponents;
if (streamHeader->mv4)
{
xComponents = new SignedByte[blockRows * blockColumns * 4];
yComponents = new SignedByte[blockRows * blockColumns * 4];
memset((void*)xComponents, 0, sizeof(SignedByte) * blockRows * blockColumns * 4);
memset((void*)yComponents, 0, sizeof(SignedByte) * blockRows * blockColumns * 4);
}
else
{
xComponents = new SignedByte[blockRows * blockColumns];
yComponents = new SignedByte[blockRows * blockColumns];
memset((void*)xComponents, 0, sizeof(SignedByte) * blockRows * blockColumns);
memset((void*)yComponents, 0, sizeof(SignedByte) * blockRows * blockColumns);
}
bool halfPixel = streamHeader->halfPixel;
// There is always an initial increment.
short int mbIndex = -1;
while (mbIndex < blockRows * blockColumns)
{
while (reader->MatchBits(MACROBLOCK_INCREMENT_ESCAPE_LENGTH, MACROBLOCK_INCREMENT_ESCAPE))
{
mbIndex += 33;
xPrediction = yPrediction = 0;
}
for (unsigned char length = 0; length < 11; length++)
for (unsigned char value = 0; value < 12; value++)
{
DecoderMAI mai = maisByLength[length][value];
// Skip if we reach the sentinel in this group.
if (mai.code == nullMAI.code)
break;
if (reader->MatchBits(length + 1, mai.code))
{
if (mai.increment > 1)
xPrediction = yPrediction = 0;
mbIndex += mai.increment;
goto addressIncrementMatched;
}
}
// A MAI should have been matched.
Logger::LogMessage("Warning: No macroblock address increment was matched.");
addressIncrementMatched:
// mbIndex is now at a coded macroblock, or at the end of the stream.
if (mbIndex == blockRows * blockColumns)
break;
// Get the type of macroblock.
UnsignedByte type = this->decodeMBType(reader, true);
unsigned short int blockCBP = 0x0000;
if (IS_PATTERN(type))
{
// Get the coded block pattern.
for (unsigned char length = 0; length < 7; length++)
for (unsigned char value = 0; value < 28; value++)
{
DecoderCBP cbp = cbpsByLength[length][value];
// Skip if we reach the sentinel in this group.
if (cbp.code == nullCBP.code)
break;
if (reader->MatchBits(length + 3, cbp.code))
{
blockCBP = cbp.bp;
goto blockPatternMatched;
}
}
// A CBP should have been matched.
Logger::LogMessage("Warning: No coded block pattern was matched.");
blockPatternMatched:
if (blockCBP == 0)
Logger::LogMessage("Warning: We should never have a zero CBP.");
}
Logger::DebugLog("(Inter %d) ", mbIndex);
if (IS_MOTION_FORWARD(type))
{
SignedByte px = this->decodeMVComponent(reader);
SignedByte py = this->decodeMVComponent(reader);
// Decode the motion vector components.
xComponents[streamHeader->mv4 ? mbIndex * 4 : mbIndex] = px + xPrediction;
yComponents[streamHeader->mv4 ? mbIndex * 4 : mbIndex] = py + yPrediction;
if (!halfPixel)
{
xComponents[streamHeader->mv4 ? mbIndex * 4 : mbIndex] *= 2;
yComponents[streamHeader->mv4 ? mbIndex * 4 : mbIndex] *= 2;
}
if (streamHeader->mv4)
{
for (int mvI = 1; mvI < 4; mvI++)
{
xComponents[mbIndex * 4 + mvI] = this->decodeMVComponent(reader) + xPrediction;
yComponents[mbIndex * 4 + mvI] = this->decodeMVComponent(reader) + yPrediction;
if (!halfPixel)
{
xComponents[mbIndex * 4 + mvI] *= 2;
yComponents[mbIndex * 4 + mvI] *= 2;
}
}
}
xPrediction += px; yPrediction += py;
Logger::DebugLog("MV %d, %d ", xPrediction, yPrediction);
}
else
{
// Whenever a block is not motion compensated, reset the prediction.
xPrediction = yPrediction = 0;
}
if (IS_INTRA(type) || IS_QUANT(type) || IS_MOTION_BACKWARD(type))
Logger::LogMessage("Warning: Unsupported macroblock type: intra in P-picture.");
// Is this is not a CBP macroblock, the specification says it's not coded, so skip the decoding stage.
if (!IS_PATTERN(type))
continue;
for (unsigned char block = 0; block < 6; block++)
{
if (((1 << (5 - block)) & blockCBP) == 0)
continue;
Logger::DebugLog(" block %d AC ", block);
// The offset into the coefficient block.
unsigned char acOffset = 0;
// For inter blocks, a special value is used for an initial run/level 0/1, because there is
// no chance of a clash with EOB (as empty blocks are not coded due to CBPs).
if (reader->MatchBits(1, INTER_VLC_ZERO_RUN_ONE_LEVEL_CODE))
{
Logger::DebugLog("{0, 1}* ");
blockEncodedValues[acOffset] = (reader->GetBits(1) == 0x0001 ? -1 : 1);
reader->AdvanceReader(1);
acOffset++;
}
// The next set of codes are AC coefficients.
// To decode these, we match bits into the AC VLC table to work out a zero run-length and level,
// then use the next bit to set the sign of the AC value. If it is not present in the table, try to match
// with the escape code and decode six bits of zero run-length and twelve bits of level
// Keep looping until we reach the EOB (end of block) marker, which indicates that the remainder of the coefficients are zero.
while (!reader->MatchBits(AC_END_OF_BLOCK_LENGTH, AC_END_OF_BLOCK))
{
short int level = -1;
unsigned char zeroRunLength = 0;
for (int i = 0; i < 15; i++)
{
DecoderVLCEntry thisEntry;
for (int j = 0; j < 16; j++)
{
thisEntry = patternLengthVLCTable[i][j];
// This is the sentinel at the end of this group. Try the next index in the table.
if (thisEntry.code == nullDecoderVLC.code)
break;
if (reader->MatchBits(i + 3 - 1, thisEntry.code))
{
level = thisEntry.level;
// A sign bit succeeeds the code.
if (reader->GetBits(1) == 0x0001)
level = -level;
reader->AdvanceReader(1);
zeroRunLength = thisEntry.zeroRunLength;
// Now that the string has been matched, jump to after escape codes.
goto interBitsMatched;
}
}
}
// Try matching with the escape code.
if (reader->MatchBits(AC_ESCAPE_LENGTH, AC_ESCAPE))
{
// There are now six bits of zero count and twelve bits of level value.
zeroRunLength = reader->GetBits(6);
reader->AdvanceReader(6);
level = reader->GetBits(11);
reader->AdvanceReader(11);
if ((reader->GetBits(1) & 0x0001) == 0x0001)
level = -level;
reader->AdvanceReader(1);
}
else
{
Logger::LogMessage("Warning: Unrecognized bit sequence in AC coefficients block.");
}
// We jump to here if there was a match in the VLC table.
// `level' and `zeroRunLength' have been set to their appropriate values here.
interBitsMatched:
// Add in the appropriate number of zeros, and then put the coefficient level in.
for (int i = 0; i < zeroRunLength; i++)
blockEncodedValues[acOffset + i] = 0x0000;
acOffset += zeroRunLength;
blockEncodedValues[acOffset] = level;
Logger::DebugLog("{%d, %d} ", zeroRunLength, level);
acOffset++;
}
// Fill in the rest with zeros.
while (acOffset < 64)
{
blockEncodedValues[acOffset] = 0x0000;
acOffset++;
}
Logger::DebugLog("EOB\n");
// The coefficients need to be reordered, as they are currently in zig-zag scan form.
this->zigZagScan(blockEncodedValues, frameTransformedValues + mbIndex * 64 * 6 + block * 64);
}
}
delete [] blockEncodedValues;
// TODO: Should have macroblock layout in the stream header.
// Populate the picture information with data about this frame and the pixel values.
picture->SetBlockRows(blockRows);
picture->SetBlockColumns(blockColumns);
picture->SetColourFormat(COLOUR_FORMAT_YUV);
picture->SetPixelLayout(PIXEL_LAYOUT_MB_FOUR_TWO_ZERO);
picture->SetPixelsX(blockColumns << 4);
picture->SetPixelsY(blockRows << 4);
picture->SetSignedShortData(frameTransformedValues, blockRows * blockColumns * 6 * 64);
picture->SetMVDisplacementsX(xComponents, blockRows * blockColumns);
picture->SetMVDisplacementsY(yComponents, blockRows * blockColumns);
}
inline UnsignedByte HuffmanEntropyDecoder::decodeMBType(BinaryStreamReader* reader, bool pPicture)
{
if (!pPicture)
{
if (reader->MatchBits(1, 0x1))
return 0x1;
else if (reader->MatchBits(2, 0x1))
return 0x11;
}
else
{
if (reader->MatchBits(1, 0x1))
return 0xA;
else if (reader->MatchBits(2, 0x1))
return 0x2;
else if (reader->MatchBits(3, 0x1))
return 0x8;
else if (reader->MatchBits(5, 0x3))
return 0x1;
else if (reader->MatchBits(5, 0x2))
return 0x1A;
else if (reader->MatchBits(5, 0x1))
return 0x12;
else if (reader->MatchBits(6, 0x1))
return 0x11;
}
Logger::LogMessage("Warning: Invalid macroblock type decoded.");
return 0x00;
}
inline SignedByte HuffmanEntropyDecoder::decodeMVComponent(BinaryStreamReader* reader)
{
unsigned char deltaLength;
SignedByte delta;
if (reader->MatchBits(2, 0x00))
{
delta = 0;
deltaLength = 0;
}
else if (reader->MatchBits(3, 0x2))
{
delta = 1;
deltaLength = 1;
}
else if (reader->MatchBits(3, 0x3))
{
delta = 2;
deltaLength = 2;
}
else if (reader->MatchBits(3, 0x4))
{
delta = 4;
deltaLength = 3;
}
else if (reader->MatchBits(3, 0x5))
{
delta = 8;
deltaLength = 4;
}
else if (reader->MatchBits(3, 0x6))
{
delta = 16;
deltaLength = 5;
}
else if (reader->MatchBits(4, 0xE))
{
delta = 32;
deltaLength = 6;
}
else if (reader->MatchBits(5, 0x1E))
{
delta = 64;
deltaLength = 7;
}
else
{
Logger::LogMessage("Warning: Invalid motion vector code encountered.");
return 0;
}
// Work out the sign.
if (delta != 0)
{
bool negative = ((reader->GetBits(1) & 0x1) == 0);
reader->AdvanceReader(1);
// Now get the actual delta.
unsigned short int readValue = reader->GetBits(deltaLength - 1);
reader->AdvanceReader(deltaLength - 1);
if (negative)
readValue = (~readValue) & ((1 << (deltaLength - 1)) - 1);
// Add on the extra delta.
delta += readValue;
// Incorporate the sign.
if (negative)
delta = -delta;
return delta;
}
else
// The zero vector.
return 0;
}
void HuffmanEntropyDecoder::DecodeFrame(BinaryStreamReader* reader, StreamHeader* streamHeader, FrameHeader* frameHeader, PictureInfo* picture)
{
// Set frame attributes.
picture->SetFrameType(frameHeader->pFrame ? FRAME_TYPE_INTER : FRAME_TYPE_INTRA);
if (picture->GetFrameType() == FRAME_TYPE_INTER)
{
this->decodeInterFrame(reader, streamHeader, frameHeader, picture);
return;
}
// Get the dimensions of this frame.
BlockCount blockRows = streamHeader->blockRows;
BlockCount blockColumns = streamHeader->blockColumns;
// An array to store the values once they are decoded.
SignedShort* blockEncodedValues = new SignedShort[8 * 8];
// An array to store the final values, out of which the picture information data will be constructed.
SignedShort* frameTransformedValues = new SignedShort[8 * 8 * 6 * blockRows * blockColumns];
// Initial values for the DC coefficients in each type of 8 * 8 block within the macroblock.
short int yDC = 0;
short int cbDC = 0;
short int crDC = 0;
// Reusable variable for the current absolute DC value.
short int dc;
unsigned char category;
for (int index = 0, y = 0; y < blockRows; y++)
{
for (int x = 0; x < blockColumns; x++)
{
// Decode the macroblock type.
UnsignedByte type = this->decodeMBType(reader, false);
if (IS_QUANT(type))
Logger::LogMessage("Warning: Unsupported macroblock type.");
else if (IS_MOTION_FORWARD(type) || IS_MOTION_BACKWARD(type) || IS_PATTERN(type) || !IS_INTRA(type))
Logger::LogMessage("Warning: Illegal macroblock type.");
for (int block = 0; block < 6; block++, index += 64)
{
// The next code is a DC coefficient delta.
// Decoding proceeds by matching the first bits with the category VLC, which yields a category number and range for the DC delta,
// and then selecting `category number' additional bits to give a sign and precise magnitude to this DC delta value.
if (block < 4)
{
for (category = 0; category < 9; category++)
if (reader->MatchBits(dcVLCsY[category].length, dcVLCsY[category].code))
break;
}
else if (block == 4)
{
for (category = 0; category < 9; category++)
if (reader->MatchBits(dcVLCsC[category].length, dcVLCsC[category].code))
break;
}
else
{
for (category = 0; category < 9; category++)
if (reader->MatchBits(dcVLCsC[category].length, dcVLCsC[category].code))
break;
}
// Read in `category' additional bits to specify the DC delta exactly.
// If the category is non-zero there is one bit for the sign first.
unsigned short int additional = reader->GetBits(category);
// Move the reader on past the additional bits.
reader->AdvanceReader(category);
bool negative = false;
if (category != 0 && (additional & (1 << (category - 1))) == 0x0000)
{
negative = true;
additional = ~additional & ((1 << category) - 1);
}
additional |= (1 << (category - 1));
if (negative)
additional = -additional;
// The DC coefficient delta is now stored in additional.
if (block < 4)
{
dc = yDC + additional;
yDC = dc;
}
else if (block == 4)
{
dc = cbDC + additional;
cbDC = dc;
}
else
{
dc = crDC + additional;
crDC = dc;
}
// Write the actual DC coefficient `dc' to this block.
blockEncodedValues[0] = dc;
Logger::DebugLog("(%d, %d, %d) DC %d AC ", x, y, block, dc);
// The offset past `index' into the coefficient block.
unsigned char acOffset = 1;
// The next set of codes are AC coefficients.
// To decode these, we match bits into the AC VLC table to work out a zero run-length and level,
// then use the next bit to set the sign of the AC value. If it is not present in the table, try to match
// with the escape code and decode six bits of zero run-length and twelve bits of level
// Keep looping until we reach the EOB (end of block) marker, which indicates that the remainder of the coefficients are zero.
while (!reader->MatchBits(AC_END_OF_BLOCK_LENGTH, AC_END_OF_BLOCK))
{
short int level = -1;
unsigned char zeroRunLength = 0;
for (int i = 0; i < 15; i++)
{
DecoderVLCEntry thisEntry;
for (int j = 0; j < 16; j++)
{
thisEntry = patternLengthVLCTable[i][j];
// This is the sentinel at the end of this group. Try the next index in the table.
if (thisEntry.code == nullDecoderVLC.code)
break;
if (reader->MatchBits(i + 3 - 1, thisEntry.code))
{
level = thisEntry.level;
// A sign bit succeeeds the code.
if (reader->GetBits(1) == 0x0001)
level = -level;
reader->AdvanceReader(1);
zeroRunLength = thisEntry.zeroRunLength;
// Now that the string has been matched, jump to after escape codes.
goto bitsMatched;
}
}
}
// Try matching with the escape code.
if (reader->MatchBits(AC_ESCAPE_LENGTH, AC_ESCAPE))
{
// There are now six bits of zero count and twelve bits of level value.
zeroRunLength = reader->GetBits(6);
reader->AdvanceReader(6);
level = reader->GetBits(11);
reader->AdvanceReader(11);
if ((reader->GetBits(1) & 0x0001) == 0x0001)
level = -level;
reader->AdvanceReader(1);
}
else
{
Logger::LogMessage("Warning: Unrecognized bit sequence in AC coefficients block.");
}
// We jump to here if there was a match in the VLC table.
// `level' and `zeroRunLength' have been set to their appropriate values here.
bitsMatched:
// Add in the appropriate number of zeros, and then put the coefficient level in.
for (int i = 0; i < zeroRunLength; i++)
blockEncodedValues[acOffset + i] = 0x0000;
acOffset += zeroRunLength;
blockEncodedValues[acOffset] = level;
Logger::DebugLog("{%d, %d} ", zeroRunLength, level);
acOffset++;
}
// Fill in the rest with zeros.
while (acOffset < 64)
{
blockEncodedValues[acOffset] = 0x0000;
acOffset++;
}
Logger::DebugLog("EOB\n");
// The coefficients need to be reordered, as they are currently in zig-zag scan form.
this->zigZagScan(blockEncodedValues, frameTransformedValues + index);
}
}
}
delete [] blockEncodedValues;
// TODO: Should have macroblock layout in the stream header.
// Populate the picture information with data about this frame and the pixel values.
picture->SetBlockRows(blockRows);
picture->SetBlockColumns(blockColumns);
picture->SetColourFormat(COLOUR_FORMAT_YUV);
picture->SetPixelLayout(PIXEL_LAYOUT_MB_FOUR_TWO_ZERO);
picture->SetPixelsX(blockColumns << 4);
picture->SetPixelsY(blockRows << 4);
picture->SetSignedShortData(frameTransformedValues, blockRows * blockColumns * 6 * 64);
}
void HuffmanEntropyDecoder::zigZagScan(short int* blockData, short int* output)
{
for (int i = 0; i < 64; i++)
output[reverseZigZagScanIndicesEightByEight[i]] = blockData[i];
}
}