blob: f84db8bdbd60d02a5cc08a9d276ae793cd1c4630 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// WP2 lossy decoding of residuals.
//
// Author: Skal (pascal.massimino@gmail.com)
#include "src/common/constants.h"
#include "src/dec/wp2_dec_i.h"
#include "src/utils/ans_utils.h"
#include "src/utils/utils.h"
#include "src/wp2/decode.h"
namespace WP2 {
//------------------------------------------------------------------------------
WP2Status ResidualReader::ReadHeaderForResidualSymbols(uint32_t num_coeffs_max,
Channel channel,
SymbolReader* const sr) {
bool is_maybe_used[kResidual1Max + 1];
for (EncodingMethod method :
{EncodingMethod::kMethod0, EncodingMethod::kMethod1}) {
std::string label;
const uint32_t cluster = GetCluster(channel, num_channels_, method);
// Read the dictionaries for the number of consecutive zeros.
const uint32_t method_index = GetMethodIndex(method);
(void)method_index;
WP2SPrint(&label, "%s_num_zeros_%d", kChannelStr[channel], method_index);
WP2_CHECK_STATUS(sr->ReadHeader(cluster, num_coeffs_max, kSymbolResNumZeros,
label.c_str()));
// Read the dictionaries for small residuals.
WP2SPrint(&label, "%s_bits0_%d", kChannelStr[channel], method_index);
WP2_CHECK_STATUS(
sr->ReadHeader(cluster, num_coeffs_max, kSymbolBits0, label.c_str()));
sr->GetPotentialUsage(cluster, kSymbolBits0, is_maybe_used,
kResidual1Max + 1);
if (is_maybe_used[kResidual1Max]) {
// Read the dictionaries for prefixes of big residuals.
WP2SPrint(&label, "%s_bits1_%d", kChannelStr[channel], method_index);
WP2_CHECK_STATUS(
sr->ReadHeader(cluster, num_coeffs_max, kSymbolBits1, label.c_str()));
}
}
return WP2_STATUS_OK;
}
WP2Status ResidualReader::ReadHeader(SymbolReader* const sr,
uint32_t num_coeffs_max_y,
uint32_t num_coeffs_max_uv,
uint32_t num_transforms, bool has_alpha,
bool has_lossy_alpha) {
num_channels_ = (has_alpha ? 4 : 3);
if (use_aom_coeffs_) {
for (Symbol sym : kSymbolsForAOMCoeffs) {
WP2_CHECK_STATUS(sr->ReadHeader(num_transforms, sym, "aom_symbols"));
}
} else {
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (channel == kAChannel && !has_lossy_alpha) continue;
WP2_CHECK_STATUS(ReadHeaderForResidualSymbols(
(channel == kYChannel || channel == kAChannel) ? num_coeffs_max_y
: num_coeffs_max_uv,
channel, sr));
}
for (Symbol sym : kSymbolsForCoeffs) {
WP2_CHECK_STATUS(
sr->ReadHeader(num_coeffs_max_y, sym, "residual_symbols"));
}
for (Symbol sym : kSymbolsForCoeffsPerTf) {
WP2_CHECK_STATUS(sr->ReadHeader(num_transforms, sym, "block_symbols"));
}
}
if (has_lossy_alpha) {
WP2_CHECK_STATUS(sr->ReadHeader(num_transforms, kSymbolEncodingMethodA,
"coeff_method_alpha"));
}
WP2_CHECK_STATUS(
sr->ReadHeader(num_transforms, kSymbolHasCoeffs, "has_coeffs"));
WP2_CHECK_STATUS(
sr->ReadHeader(num_transforms, kSymbolEncodingMethod, "coeff_method"));
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
static int16_t ReadDC(Channel channel, uint32_t num_channels, bool can_be_zero,
SymbolReader* const sr) {
int n =
sr->Read(ResidualIO::GetCluster(channel, num_channels), kSymbolDC, "DC");
if (!can_be_zero) ++n;
// Converting the unsigned value to a signed one.
return (n % 2 == 0) ? n / 2 : -((n + 1) / 2);
}
inline void ReadBounds(Channel channel, uint32_t num_channels,
EncodingMethod method, TrfSize tdim, bool is_x_first,
ANSDec* const dec, SymbolReader* const sr,
bool* const use_bounds2, uint32_t* const val1,
uint32_t* const val2) {
uint8_t min1, max1;
if (is_x_first) {
ResidualBoxAnalyzer::GetRangeX(tdim, &min1, &max1);
} else {
ResidualBoxAnalyzer::GetRangeY(tdim, &min1, &max1);
}
*val1 = dec->ReadRange(min1, max1, "bound1");
uint8_t min2, max2;
if (is_x_first) {
ResidualBoxAnalyzer::GetRangePerX(tdim, *val1, &min2, &max2);
} else {
ResidualBoxAnalyzer::GetRangePerY(tdim, *val1, &min2, &max2);
}
if (min2 != 255 && min2 <= max2) {
*use_bounds2 = sr->Read(
ResidualReader::GetClusterMergedUV(channel, num_channels, method, tdim),
kSymbolResidualUseBound2, "use_bound2");
} else {
*use_bounds2 = false;
}
if (*use_bounds2) *val2 = dec->ReadRange(min2, max2, "bound2");
}
WP2Status ResidualReader::ReadCoeffs(Channel channel, ANSDec* const dec,
SymbolReader* const sr,
CodedBlock* const cb,
libgav1::AOMContext* const aom_context,
BlockInfo* const info) {
const bool is_uv = (channel == kUChannel || channel == kVChannel);
ANSDebugPrefix prefix(dec, is_uv ? "UV" : (channel == kAChannel) ? "A" : "Y");
CodedBlock::CodingParams* const params = cb->GetCodingParams(channel);
const BlockSize split_size = GetSplitSize(cb->dim(), params->split_tf);
const uint32_t split_w = BlockWidthPix(split_size);
const uint32_t split_h = BlockHeightPix(split_size);
uint32_t tf_i = 0;
for (uint32_t split_y = 0; split_y < cb->h_pix(); split_y += split_h) {
for (uint32_t split_x = 0; split_x < cb->w_pix(); split_x += split_w) {
int16_t* const coeffs = cb->coeffs_[channel][tf_i];
std::fill(coeffs, coeffs + cb->NumCoeffsPerTransform(channel), 0);
if (use_aom_coeffs_) {
const int max_num_coeffs = (cb->num_coeffs_[channel][tf_i] > 0)
? WP2::kNumCoeffs[cb->tdim(channel)]
: 0;
aom_reader_.ReadCoeffs(
channel, cb->x_pix() + split_x, cb->y_pix() + split_y, split_size,
cb->is420_, params->tf, cb->IsFirstCoeffDC(channel), max_num_coeffs,
dec, sr, aom_context, coeffs, &cb->num_coeffs_[channel][tf_i]);
SetGeometry(cb->num_coeffs_[channel][tf_i],
&cb->method_[channel][tf_i]);
} else if (cb->method_[channel][tf_i] == EncodingMethod::kAllZero) {
cb->num_coeffs_[channel][tf_i] = 0;
} else {
WP2_CHECK_STATUS(ReadCoeffsMethod01(channel, tf_i, dec, sr, cb, info));
}
++tf_i;
}
}
return WP2_STATUS_OK;
}
WP2Status ResidualReader::ReadCoeffsMethod01(Channel channel, uint32_t tf_i,
ANSDec* const dec,
SymbolReader* const sr,
CodedBlock* const cb,
BlockInfo* const info) {
int16_t* const coeffs = cb->coeffs_[channel][tf_i];
const EncodingMethod method = cb->method_[channel][tf_i];
const TrfSize tdim = cb->tdim(channel);
const bool first_is_dc = cb->IsFirstCoeffDC(channel);
if (first_is_dc) {
// Read the DC.
const bool can_be_zero = (method != EncodingMethod::kDCOnly);
coeffs[0] = ReadDC(channel, num_channels_, can_be_zero, sr);
// [-2047:2047] if zero is included, -2048 is possible if it is not.
// TODO(yguyon): Or is it GlobalParams::GetMaxAbsDC()?
assert(coeffs[0] >= -kMaxDcValue - (can_be_zero ? 0 : 1) &&
coeffs[0] <= kMaxDcValue);
if (method == EncodingMethod::kDCOnly) {
cb->num_coeffs_[channel][tf_i] = 1;
return WP2_STATUS_OK;
}
} else {
assert(method != EncodingMethod::kDCOnly);
}
std::string label;
WP2SPrint(&label, "C%d", GetMethodIndex(method));
ANSDebugPrefix coeff_prefix(dec, label.c_str());
const uint32_t bw = TrfWidth[tdim];
const uint32_t bh = TrfHeight[tdim];
bool use_bounds_x, use_bounds_y;
uint32_t max_x, max_y;
uint32_t ind_min = 0u;
bool can_use_bounds_x, can_use_bounds_y;
ResidualBoxAnalyzer::CanUseBounds(tdim, &can_use_bounds_x, &can_use_bounds_y);
if ((can_use_bounds_x || can_use_bounds_y) &&
sr->Read(GetClusterMergedUV(channel, num_channels_, method, tdim),
kSymbolResidualUseBounds, "use_bounds")) {
bool is_x_first;
if (can_use_bounds_x && !can_use_bounds_y) {
is_x_first = true;
} else if (!can_use_bounds_x && can_use_bounds_y) {
is_x_first = false;
} else {
is_x_first =
sr->Read(GetClusterMergedUV(channel, num_channels_, method, tdim),
kSymbolResidualBound1IsX, "is_x_first");
}
if (is_x_first) {
use_bounds_x = true;
ReadBounds(channel, num_channels_, method, tdim, /*is_x_first=*/true, dec,
sr, &use_bounds_y, &max_x, &max_y);
if (!use_bounds_y) max_y = bh - 1;
} else {
use_bounds_y = true;
ReadBounds(channel, num_channels_, method, tdim, /*is_x_first=*/false,
dec, sr, &use_bounds_x, &max_y, &max_x);
if (!use_bounds_x) max_x = bw - 1;
}
// Figure out the minimal index we will reach (and therefore the one from
// which we need to store EOB).
uint32_t min_zig_zag_ind_x, min_zig_zag_ind_y;
ResidualBoxAnalyzer::FindBounds(tdim, max_x, max_y, &min_zig_zag_ind_x,
&min_zig_zag_ind_y);
if (use_bounds_x) ind_min = min_zig_zag_ind_x;
if (use_bounds_y) ind_min = std::max(ind_min, min_zig_zag_ind_y);
} else {
use_bounds_x = use_bounds_y = false;
max_x = bw - 1;
max_y = bh - 1;
}
// Debug.
if (info != nullptr) {
if (use_bounds_x) {
info->residual_info[channel][tf_i].push_back("use x bound " +
std::to_string(max_x));
}
if (use_bounds_y) {
info->residual_info[channel][tf_i].push_back("use y bound " +
std::to_string(max_y));
}
}
bool has_written_zeros = false;
bool has_only_ones_left = false;
bool previous_is_a_one = false;
uint32_t sector_cluster;
BoundedResidualIterator iter(tdim, use_bounds_x, use_bounds_y, max_x, max_y);
if (first_is_dc) ++iter; // Skip the DC.
for (; !iter.IsDone();) {
const uint32_t x = iter.x();
const uint32_t y = iter.y();
const uint32_t i = iter.Index();
const uint32_t sector = ResidualIO::GetSector(x, y, tdim);
sector_cluster =
GetClusterMergedUV(channel, num_channels_, method, tdim, sector);
// If we have more than one element left and not written 0s before, check if
// there is any 0 coming.
if (!has_written_zeros && iter.MaxNumCoeffsLeft() > 1 &&
sr->Read(sector_cluster, kSymbolResidualIsZero, "is_zero")) {
// Read the number of consecutive 0s we have, by batches of
// kResidualCons0Max.
++iter;
int32_t num_zeros_tmp;
do {
if (iter.MaxNumCoeffsLeft() < kResidualCons0Max + 1u) {
// If the number of elements left is smaller than the max number of
// possible 0s plus one non-zero element, go with a ramge to force
// feasibility.
num_zeros_tmp = dec->ReadRValue(iter.MaxNumCoeffsLeft(), "num_zeros");
} else {
num_zeros_tmp = sr->Read(GetCluster(channel, num_channels_, method),
kSymbolResNumZeros, "num_zeros");
}
for (int32_t j = 0; j < num_zeros_tmp; ++j) ++iter;
} while (num_zeros_tmp == kResidualCons0Max);
has_written_zeros = true;
continue;
}
has_written_zeros = false;
iter.SetAsNonZero();
uint32_t abs_v;
if (has_only_ones_left ||
sr->Read(sector_cluster, kSymbolResidualIsOne, "is_one")) {
abs_v = 1;
} else {
if (sr->Read(sector_cluster, kSymbolResidualIsTwo, "is_two")) {
abs_v = 2;
} else {
const uint32_t residual1 =
sr->Read(GetCluster(channel, num_channels_, method), kSymbolBits0,
"residual1");
if (residual1 == kResidual1Max) {
abs_v = 3 + kResidual1Max;
const uint32_t prefix =
sr->Read(GetCluster(channel, num_channels_, method), kSymbolBits1,
"residual2_prefix");
const uint32_t extra =
dec->ReadUValue(Golomb::NumExtraBits(prefix, /*prefix_size=*/1),
"residual2_extra");
abs_v += Golomb::Merge(prefix, /*prefix_size=*/1, extra);
} else {
abs_v = 3 + residual1;
}
}
}
coeffs[i] =
(int16_t)(dec->ReadBool("is_negative") ? -(int16_t)abs_v : abs_v);
const uint32_t zigzag_ind = iter.ZigZagIndex();
++iter;
// Exit if we are at the last element.
if (iter.IsDone()) break;
// Read an End Of Block if we have reached both sides of the box.
if (zigzag_ind >= ind_min && iter.CanEOB() &&
sr->Read(sector_cluster, kSymbolResidualEndOfBlock, "eob")) {
break;
}
if (abs_v == 1 && !has_only_ones_left && !previous_is_a_one) {
has_only_ones_left = (bool)sr->Read(
sector_cluster, kSymbolResidualHasOnlyOnesLeft, "has_only_ones_left");
}
previous_is_a_one = (abs_v == 1);
}
cb->num_coeffs_[channel][tf_i] = 0;
const uint32_t start_i = (first_is_dc ? 1 : 0);
for (uint32_t i = cb->NumCoeffsPerTransform(channel); i-- > start_i;) {
if (coeffs[i] != 0) {
cb->num_coeffs_[channel][tf_i] = i + 1;
break;
}
}
assert(cb->num_coeffs_[channel][tf_i] > start_i);
for (uint32_t i = start_i; i < cb->num_coeffs_[channel][tf_i]; ++i) {
assert(coeffs[i] >= -kMaxCoeffValue);
assert(coeffs[i] <= kMaxCoeffValue);
}
// Debug.
if (info != nullptr) {
// Compute the real box bounds.
uint32_t real_max_x = 0u, real_max_y = 0u;
const uint32_t max_i = cb->NumCoeffsPerTransform(channel) - 1;
for (uint32_t i = 0u; i <= max_i; ++i) {
const uint32_t x = i % bw;
const uint32_t y = i / bw;
if (coeffs[i] != 0) {
real_max_x = std::max(real_max_x, x);
real_max_y = std::max(real_max_y, y);
}
}
// last_i contains the index of the last element in the whole block.
// last_j contains the index of the last element in the chosen box (there
// can be none).
// last_k contains the index of the last element in the bounding box.
uint32_t last_i = 0u, last_j = 0u, last_k = 0u;
for (uint32_t i = 0u, j = 0u, k = 0u; i <= max_i; ++i) {
const uint32_t x = i % bw;
const uint32_t y = i / bw;
// No need to store anything if we are out of bounds: it is 0s.
if (x > max_x || y > max_y) continue;
if (coeffs[i] != 0) {
last_i = i;
last_j = j;
last_k = k;
}
++j;
if (x <= real_max_x && y <= real_max_y) ++k;
}
assert(last_j <= last_i);
assert(last_k <= last_j);
info->residual_info[channel][tf_i].push_back("last index: " +
std::to_string(last_i));
if (use_bounds_x && use_bounds_y) {
info->residual_info[channel][tf_i].push_back(
"last index in chosen full box: " + std::to_string(last_j));
} else if (use_bounds_x || use_bounds_y) {
info->residual_info[channel][tf_i].push_back(
"last index in chosen box: " + std::to_string(last_j));
info->residual_info[channel][tf_i].push_back("last index in full box: " +
std::to_string(last_k));
} else {
info->residual_info[channel][tf_i].push_back("last index in full box: " +
std::to_string(last_k));
}
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
} // namespace WP2