See More

#include "Block.hpp" #include "main.hpp" #include "Logger.hpp" #include "utils.hpp" #include #include #include #include #include #include "Frame.hpp" /** * Transform values to [-128, 127] by subtracting 128? * This makes the DCT values smaller to fit in 0..255 */ #define SUBTRACT_128 /** * Template specification. * Specify the template class to use dc::BlockSize as default . */ template class dc::Block<:blocksize>; template class dc::Block<:macroblocksize>; /** * @brief Lookup table (vector) for zig-zag indices. */ static std::vector<:position_t> BlockZigZagLUT; static algo::MER_level_t BlockMERLUT; /** * @brief Default ctor * * @param row_offset_list * This should be an array of length with pointers to the * start of each row for a Block inside a byte stream. * * The values from the stream will be copied row-by-row to an internal * array of doubles for calculation. */ template dc::Block::Block(uint8_t *row_offset_list[]) : matrix{nullptr} , expanded{0.0} , rle_Data(nullptr) { this->updateRows(row_offset_list); for (size_t y = 0; y < size; y++) { std::copy_n(this->matrix[y], size, &this->expanded[y * size]); } } /** * @brief Default ctor for Macroblock * * @param row_offset_list * This should be an array of length with pointers to the * start of each row for a Block inside a byte stream. * * The values from the stream will be copied row-by-row to an internal * array of doubles for calculation. */ template dc::Block::Block(uint8_t *row_offset_list[], int16_t x, int16_t y) : matrix{nullptr} , expanded{0.0} , rle_Data(nullptr) , mvec_this{0, x, y, nullptr} , mvec{0, 0, 0, nullptr} { this->updateRows(row_offset_list); } /** * @brief Default dtor */ template dc::Block::~Block() { if (this->rle_Data != nullptr) { util::deallocVector(this->rle_Data); } } template void dc::Block::updateRows(uint8_t *row_offset_list[]) { std::copy_n(row_offset_list, size, this->matrix); } /** * @brief Copy the internal double data back into the original stream as bytes. * (all size*size elements, so only when decoding) * * Clamp the results to fit inside a byte. */ template void dc::Block::expand(void) const { for (size_t y = 0; y < size; y++) { for (size_t x = 0; x < size; x++) { this->matrix[y][x] = uint8_t(std::clamp(this->expanded[y * size + x], 0.0, 255.0)); } // std::copy_n(&this->expanded[y * size], size, this->matrix[y]); } } template void dc::Block::expandDifferences(void) const { for (size_t y = 0; y < size; y++) { for (size_t x = 0; x < size; x++) { this->matrix[y][x] = uint8_t(std::clamp(this->matrix[y][x] + this->expanded[y * size + x], 0.0, 255.0)); } } } template void dc::Block::copyBlockMatrixTo(dc::Block& other) const { // Write this->matrix to other.matrix for (size_t y = 0; y < size; y++) { std::copy_n(this->matrix[y], size, other.getRow(y)); } } /** * @brief Perform forward DCT on the Block data. * * Subtract 128 from every value (if enabled) to make the * resulting DCT components smaller, * call transformDCT on the data, * then divide each element with the quant_matrix. */ template void dc::Block::processDCTDivQ(const double m[]) { #ifdef SUBTRACT_128 std::transform(this->expanded, this->expanded + size * size, this->expanded, std::bind(std::plus(), std::placeholders::_1, -128)); #endif algo::transformDCT(this->expanded, size * size); // Divide every element from this->expanded with an element in m on the same index std::transform(this->expanded, this->expanded + size * size, m, this->expanded, [=](const double& e_, const double& m_){ return std::round(e_ / m_); }); } /** * @brief Perform inverse DCT on the Block data. * * Multiply with the quant_matrix, * call transformDCTinverse on the data, * then add 128 to every value (if enabled) to restore the original DCT components. */ template void dc::Block::processIDCTMulQ(const double m[]) { // Multiply every element from this->expanded with an element in m on the same index std::transform(this->expanded, this->expanded + size * size, m, this->expanded, std::multiplies()); algo::transformDCTinverse(this->expanded, size * size); #ifdef SUBTRACT_128 std::transform(this->expanded, this->expanded + size * size, this->expanded, std::bind(std::plus(), std::placeholders::_1, 128)); #endif } /** * @brief Create an RLE sequence from the calculated values according to zig-zag pattern. * For every element, store it in the form: (#zeroes, #bits)(data) * * Print with this->printRLE(). */ template void dc::Block::createRLESequence(void) { if (this->rle_Data != nullptr) { util::deallocVector(this->rle_Data); } this->rle_Data = util::allocVar<:vector>>(); algo::RLE_data_t *info = util::allocVar<:rle_data_t>(); algo::RLE_data_t *entry = nullptr; // Block info element info->zeroes = 0; // Unused info->data_bits = 0; // Max bits needed for any following element info->data = 0; // Amount of elements after this this->rle_Data->push_back(info); // Iterate Block data by zig-zag positions for (const algo::Position_t& p : BlockZigZagLUT) { const int16_t data = int16_t(this->expanded[p.y * size + p.x]); if (entry == nullptr) { entry = util::allocVar<:rle_data_t>(); } if (data == 0) { entry->zeroes++; } else { entry->data_bits = util::bits_needed(data); // Returns minimal bits needed to represent data as signed. entry->data = data; // Gather block info info->data_bits = std::max(info->data_bits, entry->data_bits); // Save max bits needed info->data += 1 + entry->zeroes; // Add total data elements this->rle_Data->push_back(entry); entry = nullptr; } } if (entry != nullptr) { delete entry; } // Increase needed data bits if the data length does not fit in the current amount of bits info->data_bits = std::max(info->data_bits, util::ffs(uint32_t(info->data))); } /** * @brief Difference between this and other Block. * Absolute substraction of every pixel, added together, * so smaller value is less difference. * @param other * @return */ template size_t dc::Block::relativeAbsDifferenceWith(const dc::Block<:macroblocksize>& other) { size_t diff = 0ull; for (size_t y = 0; y < size; y++) { const uint8_t* other_y = other.getRow(y); for (size_t x = 0; x < size; x++) { diff += size_t(std::abs(int16_t(this->matrix[y][x]) - int16_t(other_y[x]))); } } return diff; } template void dc::Block::expandDifferenceWith(const dc::Block<:macroblocksize>& other) { for (size_t y = 0; y < size; y++) { const uint8_t* other_y = other.getRow(y); for (size_t x = 0; x < size; x++) { this->expanded[y * size + x] = double(this->matrix[y][x]) - double(other_y[x]); } } } template void dc::Block::processFindMotionOffset(dc::Frame * const ref_frame) { // Store best value in motion vector mvec // mvec_this has block pixel coords algo::MER_level_t *lowest_point = &BlockMERLUT; dc::MacroBlock *lowest_block = ref_frame->getBlockAtCoord(lowest_point->x0, lowest_point->y0); size_t lowest_diff = std::numeric_limits::max(); // Find block in ref_frame at coord mvec with lowest diff while (lowest_point->points != nullptr) { // If lowest_point has points, a better point could still be found. // Best point found in lowest_point->points algo::MER_level_t *new_lowest_point = nullptr; size_t new_lowest_diff = lowest_diff; dc::MacroBlock *new_lowest_block = nullptr; // For each point offset in pattern for (size_t p = 0; p < algo::MER_PATTERN_SIZE; p++) { algo::MER_level_t *current_point = &lowest_point->points[p]; // Block pixel (x, y) = current block pixel + offset const int16_t pixel_x = current_point->x0 + this->mvec_this.x0; const int16_t pixel_y = current_point->y0 + this->mvec_this.y0; // Get MacroBlock at that offset dc::MacroBlock *current_block = ref_frame->getBlockAtCoord(pixel_x, pixel_y); if (p > 0 && !this->isDifferentBlock(*current_block)) { // If (clamped) coord is the same as this, skip util::deallocVar(current_block); continue; } // Calculate diff with offset block const size_t current_diff = this->relativeAbsDifferenceWith(*current_block); if (current_diff <= new_lowest_diff) { // Block at offset appears better than previously found Block util::deallocVar(new_lowest_block); new_lowest_point = current_point; new_lowest_diff = current_diff; new_lowest_block = current_block; } else { util::deallocVar(current_block); } } if (new_lowest_point == nullptr) { // No other point had a lower diff than current middle => early exit util::deallocVar(new_lowest_block); break; } else { util::deallocVar(lowest_block); lowest_point = new_lowest_point; lowest_block = new_lowest_block; lowest_diff = new_lowest_diff; } } // Relative offset only, this->mvec_this should be added to this value by the decoder // to get the pixel coordinate back (now in lowest_block->getCoord()). this->mvec.x0 = lowest_point->x0; this->mvec.y0 = lowest_point->y0; // Expand diff with lowest_block to this->expanded this->expandDifferenceWith(*lowest_block); util::deallocVar(lowest_block); } /** * @brief Give an upper estimate of the required bytes to represent this Block as encoded data. * @return Returns the length for the Block in bits. */ template size_t dc::Block::streamSize(void) const { if (this->rle_Data == nullptr) { return dc::Block::SIZE_LEN_BITS // 4 bits for bit length + (size * size * 16u); // Upper estimate for needed bits } else { // Exact prediction if RLE sequence is known return dc::Block::SIZE_LEN_BITS + (size * size * this->rle_Data->front()->data_bits); } } /** * @brief Stream the encoded Block data to the given BitStreamWriter. * * 1. Write the required bit length for every element to the stream * 2. If using RLE, write the amount of data elements that will follow, * else assume it will always be (size*size) * 3. Write zeroes and data according to the RLE sequence (RLE sequence is already stored in zig-zag pattern), * limiting to the maximum required bit_len. * 4. Append trailing zeroes if length was not reached (not using RLE) * * @param writer * The BitStreamWriter to stream the encoded data to. * @param use_rle * Whether to use RLE. */ template void dc::Block::streamEncoded(util::BitStreamWriter& writer, bool use_rle) const { if (this->rle_Data == nullptr) { return; } const algo::RLE_data_t* info = this->rle_Data->front(); const uint32_t bit_len = info->data_bits; int32_t length = info->data; writer.put(Block::SIZE_LEN_BITS, bit_len); // If using RLE, strip last data element and its leading zeroes from the length // and add the length to the stream. // Else, use the entire Block size as length and don't add the length to the // stream, since it will be (size*size) for every Block. if (use_rle) { if ((length == size * size) && this->rle_Data->back()->zeroes) { length -= this->rle_Data->back()->zeroes + 1; // Loose last zeroes and data } // Write amount of data elements written writer.put(bit_len, uint32_t(length)); } else { // Always use (size * size) amount of data elements if not using rle, so no need to include length length = size * size; } // Iterate over the RLE sequence and add zeroes and the data element, according to // the maximum required length to write. for (auto start = this->rle_Data->begin() + 1; start != this->rle_Data->end() && length > 0; start++, length--) { for (size_t i = (*start)->zeroes; i--;) { writer.put(bit_len, 0u); length--; } writer.put(bit_len, uint32_t((*start)->data)); } // Append extra zeroes if length is not reached (when not using rle) for (int32_t i = length; i > 0; i--) { writer.put(bit_len, 0u); } } template void dc::Block::streamMVec(util::BitStreamWriter& writer) const { // Write Motion vector to writer // This is only the relative offset in pixels from the position of this block, // So the full vector would be this->mvec_this + this->mvec writer.put(dc::Frame::MVEC_BIT_SIZE, uint32_t(this->mvec.x0)); writer.put(dc::Frame::MVEC_BIT_SIZE, uint32_t(this->mvec.y0)); } /** * @brief Load the Block data from the given BitStreamReader. * Do the reverse as in this->streamEncoded() and read every element * according to its encoded representation. * * 1. Read the bit length from the stream * 2. If using RLE, read the amount of data element that will follow, * else, read (size*size) data elements * 3. Read every data element with a maximum bit_len and * store it in the internal double array by the zig-zag positions. * If the amount of data elements was smaller than the size of the array, * the other elements will stay at 0.0 as expected. * * @param reader * @param use_rle */ template void dc::Block::loadFromStream(util::BitStreamReader &reader, bool use_rle) { const size_t bit_len = reader.get(Block::SIZE_LEN_BITS); const size_t length = (use_rle ? (reader.get(bit_len)) : (size * size)); #ifdef LOG_LOCAL util::Logger::WriteLn("Loaded from stream:", false); util::Logger::WriteLn(std::string_format("Bits: %d, data: %d", bit_len, length), false); size_t start = reader.get_position(); for (size_t i = 0; i < length; i++) { util::Logger::Write(std::string_format("%X ", int16_t(reader.get(bit_len))), false); } util::Logger::WriteLn("", false); reader.set_position(start); #endif for (size_t i = 0; i < length; i++) { const algo::Position_t pos = BlockZigZagLUT[i]; // Shift data exactly bit_len bits to the left, and shift back to the right // to make it properly signed again. this->expanded[pos.y * size + pos.x] = util::shift_signed(reader.get(bit_len), bit_len); } // Fill values that were not read with 0 for (size_t i = length; i < size * size; i++) { const algo::Position_t pos = BlockZigZagLUT[i]; this->expanded[pos.y * size + pos.x] = 0; } } template void dc::Block::loadFromReferenceStream(util::BitStreamReader& reader, dc::Frame * const ref_frame) { // Unused for size != dc::MacroBlockSize (void)reader; (void)ref_frame; } template<> void dc::Block<:macroblocksize>::loadFromReferenceStream(util::BitStreamReader& reader, dc::Frame * const ref_frame) { // Get motion vector offset from stream this->mvec.x0 = util::shift_signed(reader.get(dc::Frame::MVEC_BIT_SIZE), dc::Frame::MVEC_BIT_SIZE); this->mvec.y0 = util::shift_signed(reader.get(dc::Frame::MVEC_BIT_SIZE), dc::Frame::MVEC_BIT_SIZE); const algo::MER_level_t mvec_coord = this->getCoordAfterMotion(); // Get Macroblock in reference frame at location of motion offset dc::MacroBlock *ref_block = ref_frame->getBlockAtCoord(mvec_coord.x0, mvec_coord.y0); // Copy values from reference block to this ref_block->copyBlockMatrixTo(*this); util::deallocVar(ref_block); } ////////////////////////////////////////////////////////////////// /// Debug print functions ////////////////////////////////////////////////////////////////// /** * @brief Print the internal double matrix in a zig-zag pattern. * (As seen from the wiki) * * Example matrix: * 0 1 2 3 * 4 5 6 7 * 8 9 10 11 * 12 13 14 15 * * Printed pattern: * 0 * 1 4 * 8 5 2 * 3 6 9 12 * 13 10 7 * 11 14 * 15 */ template void dc::Block::printZigzag(void) const { size_t line_length = 1, current = 0; bool incr = true; util::Logger::WriteLn("Zigzag:"); for (const algo::Position_t& p : BlockZigZagLUT) { util::Logger::Write(std::string_format("%3d ", int16_t(this->expanded[p.y * size + p.x])), false); if (++current >= line_length) { current = 0; if (line_length >= size) { incr = false; } if (incr) line_length++; else line_length--; util::Logger::WriteLn("", false); } } util::Logger::WriteLn("", false); } /** * @brief Print the RLE sequence (if initialised). * * In the form: (#zeroes, #bits)(data) * with #zeroes: the amount of zeroes preceeding this element, * #bits : the amount of bits needed to represent this element, * data : the data itself. * * The first element of the sequence will be printed seperately, as * it is used for information about the entire sequence. * (total bits needed and amount of data+zero elements) */ template void dc::Block::printRLE(void) const { bool info = true; util::Logger::WriteLn("RLE:"); if (this->rle_Data == nullptr) { return; } for (const algo::RLE_data_t* e : *this->rle_Data) { if (info) { util::Logger::WriteLn(std::string_format("Bits needed: %d\n" "Data length: %d\n" "Sequence : (#zeroes, #bits)(data)", e->data_bits, e->data), false); info = false; } else { util::Logger::Write(std::string_format("(%d,%d)(%02X), ", e->zeroes, e->data_bits, uint8_t(e->data)), false); } } util::Logger::WriteLn("", false); util::Logger::WriteLn("", false); } /** * @brief Print the internal double matrix data as an int16. */ template void dc::Block::printExpanded(void) const { for (size_t y = 0; y < size; y++) { for (size_t x = 0; x < size; x++) { util::Logger::Write(std::string_format("% 4d ", int16_t(this->expanded[y * size + x])), false); } util::Logger::WriteLn("", false); } } /** * @brief Print the matrix data as seen inside the bytestream. */ template void dc::Block::printMatrix(void) const { for (size_t y = 0; y < size; y++) { for (size_t x = 0; x < size; x++) { util::Logger::Write(std::string_format("%3d ", this->matrix[y][x]), false); } util::Logger::WriteLn("", false); } } /** * @brief Create the zig-zag positions LUT. */ template void dc::Block::CreateZigZagLUT(void) { // TODO Zigzag for Macroblock? if (BlockZigZagLUT.size() == 0) { util::Logger::WriteLn(std::string_format("[Block] Caching zig-zag pattern for blocksize %d...", dc::BlockSize)); algo::createZigzagLUT(BlockZigZagLUT, size); } } template void dc::Block::CreateMERLUT(const uint16_t& merange) { if (BlockMERLUT.points == nullptr) { util::Logger::WriteLn(std::string_format("[MacroBlock] Caching motion estimation patterns for merange %d...", merange)); algo::createMERLUT(BlockMERLUT, merange); } } template void dc::Block::DestroyMERLUT(void) { algo::destroyMERLUT(BlockMERLUT); }