416 lines
17 KiB
C++
416 lines
17 KiB
C++
#include "joc_bitstream/joc_parser.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include "foundation/bit_reader.h"
|
|
#include "joc_huffman_tables.h"
|
|
|
|
namespace joc::joc {
|
|
|
|
namespace {
|
|
|
|
struct NumChannelsEntry {
|
|
std::uint32_t config;
|
|
std::int16_t channels;
|
|
};
|
|
constexpr NumChannelsEntry kNumChannels[] = {
|
|
{0u, 5}, {1u, 7}, {2u, 7}, {3u, 5}, {4u, 7},
|
|
};
|
|
|
|
struct NumBandsEntry {
|
|
std::uint32_t index;
|
|
std::int16_t bands;
|
|
};
|
|
constexpr NumBandsEntry kNumBands[] = {
|
|
{0u, 1}, {1u, 3}, {2u, 5}, {3u, 7}, {4u, 9}, {5u, 12}, {6u, 15}, {7u, 23},
|
|
};
|
|
|
|
// Floored modulo: Python's % operator semantics, so that the ported
|
|
inline std::int64_t floored_mod(std::int64_t value, std::int64_t modulus) {
|
|
const std::int64_t remainder = value % modulus;
|
|
return remainder < 0 ? remainder + modulus : remainder;
|
|
}
|
|
|
|
enum class SymbolKind { Mtx, Idx, Vec };
|
|
|
|
struct Tree {
|
|
const int (*nodes)[2] = nullptr;
|
|
int count = 0;
|
|
};
|
|
|
|
Tree select_tree(std::uint32_t quant_idx, SymbolKind kind, int n_channels) {
|
|
Tree tree;
|
|
switch (kind) {
|
|
case SymbolKind::Idx:
|
|
if (n_channels == 5) {
|
|
tree.nodes = joc_huff_code_5ch_pos_index_sparse;
|
|
tree.count = static_cast<int>(sizeof(joc_huff_code_5ch_pos_index_sparse) /
|
|
sizeof(joc_huff_code_5ch_pos_index_sparse[0]));
|
|
} else {
|
|
tree.nodes = joc_huff_code_7ch_pos_index_sparse;
|
|
tree.count = static_cast<int>(sizeof(joc_huff_code_7ch_pos_index_sparse) /
|
|
sizeof(joc_huff_code_7ch_pos_index_sparse[0]));
|
|
}
|
|
break;
|
|
case SymbolKind::Vec:
|
|
if (quant_idx == 0u) {
|
|
tree.nodes = joc_huff_code_coarse_coeff_sparse;
|
|
tree.count = static_cast<int>(sizeof(joc_huff_code_coarse_coeff_sparse) /
|
|
sizeof(joc_huff_code_coarse_coeff_sparse[0]));
|
|
} else {
|
|
tree.nodes = joc_huff_code_fine_coeff_sparse;
|
|
tree.count = static_cast<int>(sizeof(joc_huff_code_fine_coeff_sparse) /
|
|
sizeof(joc_huff_code_fine_coeff_sparse[0]));
|
|
}
|
|
break;
|
|
case SymbolKind::Mtx:
|
|
default:
|
|
if (quant_idx == 0u) {
|
|
tree.nodes = joc_huff_code_coarse_generic;
|
|
tree.count = static_cast<int>(sizeof(joc_huff_code_coarse_generic) /
|
|
sizeof(joc_huff_code_coarse_generic[0]));
|
|
} else {
|
|
tree.nodes = joc_huff_code_fine_generic;
|
|
tree.count = static_cast<int>(sizeof(joc_huff_code_fine_generic) /
|
|
sizeof(joc_huff_code_fine_generic[0]));
|
|
}
|
|
break;
|
|
}
|
|
return tree;
|
|
}
|
|
|
|
// infinite loop (plan 40.4 BL-6).
|
|
bool huff_decode(const Tree& tree, bits::BitReader& reader, std::int16_t* out_value) {
|
|
int node = 0;
|
|
int steps = 0;
|
|
while (node >= 0) {
|
|
if (node >= tree.count || steps > tree.count) {
|
|
reader.fail(JOC_ERR_JOC_SYNTAX, "Huffman tree walk left the valid node range");
|
|
return false;
|
|
}
|
|
++steps;
|
|
const std::uint32_t bit = reader.read(1);
|
|
if (reader.failed()) {
|
|
return false;
|
|
}
|
|
node = tree.nodes[node][bit];
|
|
}
|
|
*out_value = static_cast<std::int16_t>(-node - 1);
|
|
return true;
|
|
}
|
|
|
|
Status syntax_fail(const std::string& message) {
|
|
return Status::fail(JOC_ERR_JOC_SYNTAX, stage::kJoc, message);
|
|
}
|
|
|
|
Status truncated_fail(const bits::BitReader& reader) {
|
|
if (reader.error() == JOC_ERR_JOC_SYNTAX) {
|
|
return syntax_fail(reader.error_message());
|
|
}
|
|
return Status::fail(JOC_ERR_BITSTREAM_TRUNCATED, stage::kJoc,
|
|
std::string("JOC bitstream truncated: ") + reader.error_message());
|
|
}
|
|
|
|
void reconstruct_dense(const ObjectSymbols& symbols, std::uint32_t dp, int n_channels, std::int64_t nquant,
|
|
std::int64_t offset,
|
|
std::int64_t q[JOC_MAX_DPOINTS][JOC_MAX_CORE_CHANNELS][JOC_MAX_PARAMETER_BANDS]) {
|
|
for (int ch = 0; ch < n_channels; ++ch) {
|
|
q[dp][ch][0] =
|
|
floored_mod(offset + static_cast<std::int64_t>(symbols.mtx[dp][ch][0]), nquant);
|
|
for (int pb = 1; pb < symbols.n_bands; ++pb) {
|
|
q[dp][ch][pb] = floored_mod(
|
|
q[dp][ch][pb - 1] + static_cast<std::int64_t>(symbols.mtx[dp][ch][pb]), nquant);
|
|
}
|
|
}
|
|
}
|
|
|
|
// across parameter bands and is deliberately NOT reset when the active channel
|
|
Status reconstruct_sparse(
|
|
const ObjectSymbols& symbols, std::uint32_t dp, int n_channels, std::int64_t nquant, std::int64_t offset,
|
|
std::int64_t q[JOC_MAX_DPOINTS][JOC_MAX_CORE_CHANNELS][JOC_MAX_PARAMETER_BANDS]) {
|
|
if (n_channels != 5 && n_channels != 7) {
|
|
return Status::fail(JOC_ERR_JOC_UNSUPPORTED_VARIANT, stage::kJoc,
|
|
"sparse JOC requires 5 or 7 core channels, got " +
|
|
std::to_string(n_channels));
|
|
}
|
|
const int initial_channel = symbols.idx[dp][0];
|
|
if (initial_channel < 0 || initial_channel >= n_channels) {
|
|
return syntax_fail("sparse JOC initial channel " + std::to_string(initial_channel) +
|
|
" out of range for " + std::to_string(n_channels) + " channels");
|
|
}
|
|
// Non-active entries take nquant/2, which dequantizes to exactly zero.
|
|
for (int ch = 0; ch < n_channels; ++ch) {
|
|
for (int pb = 0; pb < symbols.n_bands; ++pb) {
|
|
q[dp][ch][pb] = nquant / 2;
|
|
}
|
|
}
|
|
int active = initial_channel;
|
|
std::int64_t coefficient = offset;
|
|
for (int pb = 0; pb < symbols.n_bands; ++pb) {
|
|
if (pb != 0) {
|
|
active = static_cast<int>(
|
|
floored_mod(static_cast<std::int64_t>(active) + symbols.idx[dp][pb], n_channels));
|
|
}
|
|
coefficient = floored_mod(coefficient + static_cast<std::int64_t>(symbols.vec[dp][pb]), nquant);
|
|
q[dp][active][pb] = coefficient;
|
|
}
|
|
return Status::success();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::int16_t num_channels_for_config(std::uint32_t dmx_config_idx) {
|
|
for (const NumChannelsEntry& entry : kNumChannels) {
|
|
if (entry.config == dmx_config_idx) {
|
|
return entry.channels;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
std::int16_t num_bands_for_index(std::uint32_t num_bands_idx) {
|
|
for (const NumBandsEntry& entry : kNumBands) {
|
|
if (entry.index == num_bands_idx) {
|
|
return entry.bands;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Status parse_id14(const std::uint8_t* payload, std::size_t payload_size, joc_frame_params* out,
|
|
FrameSymbols* symbols) {
|
|
if (payload == nullptr || out == nullptr) {
|
|
return Status::fail(JOC_ERR_INVALID_ARGUMENT, stage::kJoc, "null payload or output");
|
|
}
|
|
std::memset(out, 0, sizeof(*out));
|
|
out->struct_size = sizeof(joc_frame_params);
|
|
out->struct_version = JOC_FRAME_PARAMS_VERSION;
|
|
// capture is purely additive and never changes the parse result.
|
|
FrameSymbols local_symbols{};
|
|
FrameSymbols& capture = (symbols != nullptr) ? *symbols : local_symbols;
|
|
std::memset(&capture, 0, sizeof(capture));
|
|
|
|
bits::BitReader reader(payload, payload_size);
|
|
|
|
out->dmx_config_idx = static_cast<std::uint8_t>(reader.read(3));
|
|
out->num_objects_bits = static_cast<std::uint8_t>(reader.read(6));
|
|
out->ext_config_idx = static_cast<std::uint8_t>(reader.read(3));
|
|
|
|
const std::uint32_t n_objects = static_cast<std::uint32_t>(out->num_objects_bits) + 1u;
|
|
const std::int16_t n_channels = num_channels_for_config(out->dmx_config_idx);
|
|
if (n_channels < 0) {
|
|
return syntax_fail("unknown JOC downmix configuration " +
|
|
std::to_string(out->dmx_config_idx));
|
|
}
|
|
out->n_channels = static_cast<std::uint8_t>(n_channels);
|
|
if (n_objects > JOC_MAX_OBJECTS) {
|
|
// The reference implementation has no check here and fails later inside
|
|
// NumPy; the port reports it explicitly (plan 28.2).
|
|
return Status::fail(JOC_ERR_JOC_UNSUPPORTED_VARIANT, stage::kJoc,
|
|
"JOC frame declares " + std::to_string(n_objects) +
|
|
" objects, the ABI supports at most " +
|
|
std::to_string(static_cast<int>(JOC_MAX_OBJECTS)));
|
|
}
|
|
out->n_objects = static_cast<std::uint8_t>(n_objects);
|
|
|
|
out->clipgain_x_bits = static_cast<std::uint8_t>(reader.read(3));
|
|
out->clipgain_y_bits = static_cast<std::uint8_t>(reader.read(5));
|
|
out->seq_count = reader.read(10);
|
|
// clipgain = 1 + (y/32) * 2^(x-4). The reference multiplies by an exact
|
|
// exactly for the whole legal range.
|
|
out->clipgain = 1.0 + static_cast<double>(out->clipgain_y_bits) / 32.0 *
|
|
std::ldexp(1.0, static_cast<int>(out->clipgain_x_bits) - 4);
|
|
|
|
for (std::uint32_t obj = 0; obj < n_objects; ++obj) {
|
|
joc_object_params& info = out->objects[obj];
|
|
info.present = static_cast<std::uint8_t>(reader.read(1));
|
|
if (info.present == 0u) {
|
|
continue;
|
|
}
|
|
info.num_bands_idx = static_cast<std::uint8_t>(reader.read(3));
|
|
const std::int16_t bands = num_bands_for_index(info.num_bands_idx);
|
|
if (bands < 0) {
|
|
return syntax_fail("unknown JOC num_bands index " +
|
|
std::to_string(info.num_bands_idx));
|
|
}
|
|
info.n_bands = static_cast<std::uint8_t>(bands);
|
|
info.sparse = static_cast<std::uint8_t>(reader.read(1));
|
|
info.quant_idx = static_cast<std::uint8_t>(reader.read(1));
|
|
info.slope_idx = static_cast<std::uint8_t>(reader.read(1));
|
|
info.num_dpoints_bits = static_cast<std::uint8_t>(reader.read(1));
|
|
info.n_dpoints = static_cast<std::uint8_t>(info.num_dpoints_bits + 1u);
|
|
if (info.slope_idx == 1u) {
|
|
for (std::uint32_t dp = 0; dp < info.n_dpoints; ++dp) {
|
|
info.offset_ts[dp] = static_cast<std::uint8_t>(reader.read(5) + 1u);
|
|
}
|
|
}
|
|
}
|
|
if (reader.failed()) {
|
|
return truncated_fail(reader);
|
|
}
|
|
|
|
for (std::uint32_t obj = 0; obj < n_objects; ++obj) {
|
|
const joc_object_params& info = out->objects[obj];
|
|
if (info.present == 0u) {
|
|
continue;
|
|
}
|
|
ObjectSymbols* symbol = &capture.objects[obj];
|
|
symbol->present = 1;
|
|
symbol->sparse = info.sparse;
|
|
symbol->n_bands = info.n_bands;
|
|
symbol->n_dpoints = info.n_dpoints;
|
|
symbol->n_channels = out->n_channels;
|
|
for (std::uint32_t dp = 0; dp < info.n_dpoints; ++dp) {
|
|
if (info.sparse == 1u) {
|
|
const Tree idx_tree = select_tree(info.quant_idx, SymbolKind::Idx, n_channels);
|
|
const std::uint32_t first = reader.read(3);
|
|
if (reader.failed()) {
|
|
return truncated_fail(reader);
|
|
}
|
|
symbol->idx[dp][0] = static_cast<std::uint8_t>(first);
|
|
for (int pb = 1; pb < info.n_bands; ++pb) {
|
|
std::int16_t value = 0;
|
|
if (!huff_decode(idx_tree, reader, &value)) {
|
|
return truncated_fail(reader);
|
|
}
|
|
symbol->idx[dp][pb] = static_cast<std::uint8_t>(value);
|
|
}
|
|
const Tree vec_tree = select_tree(info.quant_idx, SymbolKind::Vec, n_channels);
|
|
for (int pb = 0; pb < info.n_bands; ++pb) {
|
|
std::int16_t value = 0;
|
|
if (!huff_decode(vec_tree, reader, &value)) {
|
|
return truncated_fail(reader);
|
|
}
|
|
symbol->vec[dp][pb] = value;
|
|
}
|
|
} else {
|
|
const Tree mtx_tree = select_tree(info.quant_idx, SymbolKind::Mtx, n_channels);
|
|
for (int ch = 0; ch < n_channels; ++ch) {
|
|
for (int pb = 0; pb < info.n_bands; ++pb) {
|
|
std::int16_t value = 0;
|
|
if (!huff_decode(mtx_tree, reader, &value)) {
|
|
return truncated_fail(reader);
|
|
}
|
|
symbol->mtx[dp][ch][pb] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
out->data_end_bits = static_cast<std::uint32_t>(reader.position());
|
|
out->trailing_bits = static_cast<std::uint32_t>(payload_size * 8u - reader.position());
|
|
const std::size_t tail_offset = reader.position() / 8u;
|
|
if (tail_offset < payload_size) {
|
|
const std::size_t tail_bytes = std::min<std::size_t>(8u, payload_size - tail_offset);
|
|
std::memcpy(out->tail_bytes, payload + tail_offset, tail_bytes);
|
|
}
|
|
|
|
for (std::uint32_t obj = 0; obj < n_objects; ++obj) {
|
|
const joc_object_params& info = out->objects[obj];
|
|
if (info.present == 0u) {
|
|
continue;
|
|
}
|
|
std::uint32_t mask_bit = 1u << obj;
|
|
out->present_mask |= mask_bit;
|
|
|
|
const std::int64_t nquant = (info.quant_idx == 0u) ? 96 : 192;
|
|
std::int64_t q[JOC_MAX_DPOINTS][JOC_MAX_CORE_CHANNELS][JOC_MAX_PARAMETER_BANDS] = {};
|
|
ObjectSymbols* symbol = &capture.objects[obj];
|
|
|
|
for (std::uint32_t dp = 0; dp < info.n_dpoints; ++dp) {
|
|
if (info.sparse == 1u) {
|
|
const std::int64_t offset = (info.quant_idx == 0u) ? 50 : 100;
|
|
const Status status =
|
|
reconstruct_sparse(*symbol, dp, n_channels, nquant, offset, q);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
} else {
|
|
const std::int64_t offset = (info.quant_idx == 0u) ? 48 : 96;
|
|
reconstruct_dense(*symbol, dp, n_channels, nquant, offset, q);
|
|
}
|
|
}
|
|
|
|
// Operand order and types are kept identical to the reference so the
|
|
// result is bit-exact, not merely close.
|
|
const double nquant_half = static_cast<double>(nquant) / 2.0;
|
|
const double denominator = 4096.0 * static_cast<double>(1 + static_cast<int>(info.quant_idx));
|
|
for (std::uint32_t dp = 0; dp < info.n_dpoints; ++dp) {
|
|
for (int ch = 0; ch < n_channels; ++ch) {
|
|
for (int pb = 0; pb < info.n_bands; ++pb) {
|
|
const double value = static_cast<double>(q[dp][ch][pb]) - nquant_half;
|
|
out->objects[obj].dq[dp][ch][pb] = value * 820.0 / denominator;
|
|
}
|
|
}
|
|
}
|
|
for (std::uint32_t dp = 0; dp < info.n_dpoints; ++dp) {
|
|
for (int ch = 0; ch < n_channels; ++ch) {
|
|
for (int pb = 0; pb < info.n_bands; ++pb) {
|
|
symbol->q[dp][ch][pb] = q[dp][ch][pb];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
capture.n_objects = out->n_objects;
|
|
capture.n_channels = out->n_channels;
|
|
return Status::success();
|
|
}
|
|
|
|
Status parse_eac3_frame(const std::uint8_t* frame, std::size_t frame_size, joc_frame_params* out,
|
|
emdf::Container* container, FrameSymbols* symbols) {
|
|
if (frame == nullptr || out == nullptr) {
|
|
return Status::fail(JOC_ERR_INVALID_ARGUMENT, stage::kJoc, "null frame or output");
|
|
}
|
|
emdf::Container local;
|
|
const Status status = emdf::find_joc_emdf(frame, frame_size, &local);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
if (container != nullptr) {
|
|
*container = local;
|
|
}
|
|
const emdf::Payload* payload = local.find(emdf::kIdJoc);
|
|
if (payload == nullptr) {
|
|
return Status::fail(JOC_ERR_EMDF_TRANSPORT, stage::kEmdf,
|
|
"EMDF container has no ID14 (JOC) payload");
|
|
}
|
|
std::vector<std::uint8_t> bytes;
|
|
const Status extract = emdf::extract_payload_bytes(frame, frame_size, *payload, &bytes);
|
|
if (!extract.ok()) {
|
|
return extract;
|
|
}
|
|
return parse_id14(bytes.data(), bytes.size(), out, symbols);
|
|
}
|
|
|
|
Status check_id14_padding(const std::uint8_t* payload, std::size_t payload_size,
|
|
std::uint32_t* out_trailing_bits) {
|
|
joc_frame_params params;
|
|
FrameSymbols symbols;
|
|
const Status status = parse_id14(payload, payload_size, ¶ms, &symbols);
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
if (out_trailing_bits != nullptr) {
|
|
*out_trailing_bits = params.trailing_bits;
|
|
}
|
|
if (params.trailing_bits > 7u) {
|
|
return Status::fail(JOC_ERR_BITSTREAM_PADDING, stage::kJoc,
|
|
"more than 7 bits left after joc_data (" +
|
|
std::to_string(params.trailing_bits) + ")");
|
|
}
|
|
for (std::size_t bit = params.data_end_bits; bit < payload_size * 8u; ++bit) {
|
|
if (((payload[bit >> 3] >> (7u - (bit & 7u))) & 1u) != 0u) {
|
|
return Status::fail(JOC_ERR_BITSTREAM_PADDING, stage::kJoc,
|
|
"non-zero trailing padding bit at " + std::to_string(bit));
|
|
}
|
|
}
|
|
return Status::success();
|
|
}
|
|
|
|
} // namespace joc::joc
|