593 lines
24 KiB
Python
593 lines
24 KiB
Python
"""OAMD 位载荷 → 16 个对象槽的 q1/q2/q3 增量状态。
|
||
|
||
依据 ETSI TS 103 420 V1.2.1 clause 5。槽 0 为 bed/LFE,槽 1..15 为输出
|
||
ch1..15;bed/ISF/未激活对象没有位置字段,保持上一帧位置。element 目录按声明长度
|
||
驱动,未知 element 按边界跳过;个别编码器的 ``oa_element_size`` 比实际内容短时以
|
||
结构解析为准,差异记入 ``diagnostics``,不算变体错误。
|
||
"""
|
||
import numpy as np
|
||
|
||
from variant_error import UnsupportedVariantError, bytes_descriptor
|
||
|
||
N_Q12 = 62
|
||
N_Q3 = 15
|
||
SAMPLE_OFFSET_INDEX = (8, 16, 18, 24)
|
||
RAMP_DURATIONS = (0, 512, 1536)
|
||
RAMP_DURATION_INDEX = (
|
||
32, 64, 128, 256, 320, 480, 1000, 1001,
|
||
1024, 1600, 1601, 1602, 1920, 2000, 2002, 2048,
|
||
)
|
||
OBJECT_ELEMENT_ID = 1
|
||
# 5.6.0.11 Table 11b:ISF 类型 → 对象数。
|
||
ISF_OBJECT_COUNTS = (4, 8, 10, 14, 15, 30)
|
||
# 5.6.1.1.4 Table 12:10 bit 标准 bed 掩码按位对应的声道(LSB = RC_L/RC_R)。
|
||
BED_CHANNEL_LABELS = (
|
||
"RC_L/RC_R", "RC_C", "RC_LFE", "RC_LS/RC_RS", "RC_LB/RC_RB",
|
||
"RC_TFL/RC_TFR", "RC_TSL/RC_TSR", "RC_TBL/RC_TBR", "RC_LW/RC_RW", "RC_LFE2",
|
||
)
|
||
# 5.6.1.1.5 Table 13:17 bit 非标准 bed 掩码按位对应的声道标签。
|
||
NONSTD_BED_LABELS = (
|
||
"RC_LFE2", "RC_RW", "RC_LW", "RC_TBR", "RC_TBL", "RC_TSR", "RC_TSL",
|
||
"RC_TFR", "RC_TFL", "RC_RB", "RC_LB", "RC_RS", "RC_LS", "RC_LFE", "RC_C",
|
||
"RC_R", "RC_L",
|
||
)
|
||
MAX_SLOTS = 16
|
||
|
||
|
||
def q_of(k, n):
|
||
q = int(np.floor(32768.0 * k / n + 0.5))
|
||
return min(32767, q)
|
||
|
||
|
||
def _payload_bits(bits_one):
|
||
if isinstance(bits_one, (bytes, bytearray, memoryview)):
|
||
raw_payload = bytes(bits_one)
|
||
bits = np.unpackbits(
|
||
np.frombuffer(raw_payload, dtype=np.uint8), bitorder="big")
|
||
else:
|
||
src = np.asarray(bits_one)
|
||
if src.ndim != 1:
|
||
raw = np.asarray(bits_one, dtype=np.uint8).tobytes()
|
||
raise UnsupportedVariantError(
|
||
"oamd", "payload_shape",
|
||
"OAMD 载荷必须是一维 byte 或 bit 序列",
|
||
details={"shape": list(src.shape), "payload": bytes_descriptor(raw)})
|
||
is_bit_vector = bool(src.size) and bool(np.all((src == 0) | (src == 1)))
|
||
if is_bit_vector:
|
||
if src.size % 8:
|
||
raw = np.packbits(src.astype(np.uint8), bitorder="big").tobytes()
|
||
raise UnsupportedVariantError(
|
||
"oamd", "payload_bit_alignment",
|
||
"OAMD bit 载荷没有按整字节对齐",
|
||
details={
|
||
"payload_bits": int(src.size),
|
||
"payload": bytes_descriptor(raw),
|
||
})
|
||
bits = src.astype(np.uint8, copy=False)
|
||
raw_payload = np.packbits(bits, bitorder="big").tobytes()
|
||
else:
|
||
try:
|
||
values = src.astype(np.int64, copy=False)
|
||
except (TypeError, ValueError, OverflowError) as exc:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "payload_type",
|
||
"OAMD 载荷不能转换为 byte 序列",
|
||
details={"dtype": str(src.dtype), "parser_error": str(exc)}) from exc
|
||
if np.any(values < 0) or np.any(values > 255):
|
||
raise UnsupportedVariantError(
|
||
"oamd", "payload_byte_range",
|
||
"OAMD byte 载荷包含 0..255 之外的值",
|
||
details={"dtype": str(src.dtype), "payload_values": int(src.size)})
|
||
raw_payload = values.astype(np.uint8).tobytes()
|
||
bits = np.unpackbits(
|
||
np.frombuffer(raw_payload, dtype=np.uint8), bitorder="big")
|
||
return bits, raw_payload
|
||
|
||
|
||
class _BitReader:
|
||
def __init__(self, bits, position=0, limit=None):
|
||
self.bits = bits
|
||
self.position = int(position)
|
||
self.limit = len(bits) if limit is None else int(limit)
|
||
|
||
def read(self, count):
|
||
end = self.position + count
|
||
if end > self.limit:
|
||
raise ValueError(f"OAMD 位流越界: bit={self.position}, need={count}")
|
||
value = 0
|
||
for bit in self.bits[self.position:end]:
|
||
value = (value << 1) | int(bit)
|
||
self.position = end
|
||
return value
|
||
|
||
def skip(self, count):
|
||
self.read(count)
|
||
|
||
|
||
def _variable_bits_max(reader, width, max_groups):
|
||
"""5.5.1 ``variable_bits_max(n, max_num_groups)``。"""
|
||
value = reader.read(width)
|
||
more = reader.read(1)
|
||
num_group = 1
|
||
if max_groups > num_group:
|
||
if more:
|
||
value = (value + 1) << width
|
||
while more:
|
||
value += reader.read(width)
|
||
more = reader.read(1)
|
||
if num_group >= max_groups:
|
||
break
|
||
if more:
|
||
value = (value + 1) << width
|
||
num_group += 1
|
||
return value
|
||
|
||
|
||
def _element_details(elements):
|
||
return [{
|
||
"ordinal": element["ordinal"],
|
||
"element_id": element["element_id"],
|
||
"size_bytes": element["size_bytes"],
|
||
"header_start_bit": element["header_start_bit"],
|
||
"body_start_bit": element["body_start_bit"],
|
||
"body_end_bit": element["body_end_bit"],
|
||
"parsed_end_bit": element.get("parsed_end_bit"),
|
||
"discard_unknown": element["discard_unknown"],
|
||
"alternate_data_id": element["alternate_data_id"],
|
||
} for element in elements]
|
||
|
||
|
||
def _syntax_error(variant, message, raw_payload, exc=None, **details):
|
||
payload = {"payload": bytes_descriptor(raw_payload)}
|
||
if exc is not None:
|
||
payload["parser_error"] = str(exc)
|
||
payload.update(details)
|
||
return UnsupportedVariantError("oamd", variant, message, details=payload)
|
||
|
||
|
||
def _parse_program_assignment(reader, raw_payload):
|
||
"""5.5.3 ``program_assignment()``:bed / ISF / dynamic 对象数量。"""
|
||
program = {
|
||
"dynamic_object_only": bool(reader.read(1)),
|
||
"lfe_present": False,
|
||
"content_description": None,
|
||
"bed_assignments": [],
|
||
"num_bed_objects": 0,
|
||
"isf_idx": None,
|
||
"num_isf_objects": 0,
|
||
"num_dynamic_objects": None,
|
||
}
|
||
if program["dynamic_object_only"]:
|
||
program["lfe_present"] = bool(reader.read(1))
|
||
# 5.6.4.8:对象顺序为 bed → ISF → dynamic,LFE 属于 bed,排在最前。
|
||
program["num_bed_objects"] = 1 if program["lfe_present"] else 0
|
||
return program
|
||
|
||
mask = reader.read(4)
|
||
program["content_description"] = {
|
||
"reserved": bool(mask & 0x8),
|
||
"dynamic": bool(mask & 0x4),
|
||
"isf": bool(mask & 0x2),
|
||
"bed": bool(mask & 0x1),
|
||
}
|
||
if mask & 0x1:
|
||
program["b_bed_chan_distribute"] = bool(reader.read(1))
|
||
num_instances = (reader.read(3) + 2) if reader.read(1) else 1
|
||
for instance in range(num_instances):
|
||
if reader.read(1): # b_lfe_only
|
||
program["bed_assignments"].append({
|
||
"instance": instance, "lfe_only": True, "channels": ["RC_LFE"],
|
||
})
|
||
continue
|
||
if reader.read(1): # b_standard_chan_assign
|
||
bed_mask = reader.read(10)
|
||
channels = []
|
||
for bit in range(10):
|
||
if (bed_mask >> bit) & 1:
|
||
channels.extend(BED_CHANNEL_LABELS[bit].split("/"))
|
||
program["bed_assignments"].append({
|
||
"instance": instance, "lfe_only": False, "standard": True,
|
||
"mask": bed_mask, "channels": channels,
|
||
})
|
||
else:
|
||
bed_mask = reader.read(17)
|
||
channels = [NONSTD_BED_LABELS[bit]
|
||
for bit in range(17) if (bed_mask >> bit) & 1]
|
||
program["bed_assignments"].append({
|
||
"instance": instance, "lfe_only": False, "standard": False,
|
||
"mask": bed_mask, "channels": channels,
|
||
})
|
||
program["num_bed_objects"] = sum(
|
||
len(assignment["channels"]) for assignment in program["bed_assignments"])
|
||
if mask & 0x2:
|
||
isf_idx = reader.read(3)
|
||
program["isf_idx"] = isf_idx
|
||
program["num_isf_objects"] = (ISF_OBJECT_COUNTS[isf_idx]
|
||
if isf_idx < len(ISF_OBJECT_COUNTS) else 0)
|
||
if mask & 0x4:
|
||
count = reader.read(5)
|
||
if count == 0x1F:
|
||
count += reader.read(7)
|
||
program["num_dynamic_objects"] = count + 1
|
||
if mask & 0x8:
|
||
# 5.6.4.1:reserved_data_size = reserved_data_size_bits + 1(字节)。
|
||
reader.skip((reader.read(4) + 1) * 8)
|
||
return program
|
||
|
||
|
||
def _parse_object_info_block(reader, object_index, in_bed_or_isf, raw_payload):
|
||
"""5.5.9 ``object_info_block(0)``:一个对象的属性更新。"""
|
||
info = {
|
||
"object": object_index,
|
||
"in_bed_or_isf": bool(in_bed_or_isf),
|
||
"not_active": bool(reader.read(1)),
|
||
}
|
||
# blk == 0 且对象激活时 basic info 恒为 full update(status 0b01)。
|
||
basic_status = 0 if info["not_active"] else 1
|
||
info["basic_status"] = basic_status
|
||
if basic_status in (1, 3):
|
||
if basic_status == 1:
|
||
# 5.5.10:object_basic_info[] = {true, true};
|
||
# 5.6.4.12:array[1] = object_gain_idx,array[0] = b_default_object_priority。
|
||
gain_present = priority_present = True
|
||
else:
|
||
flags = reader.read(2)
|
||
gain_present = bool(flags & 1)
|
||
priority_present = bool(flags & 2)
|
||
if gain_present:
|
||
gain_idx = reader.read(2)
|
||
info["gain_idx"] = gain_idx
|
||
if gain_idx == 2:
|
||
info["gain_bits"] = reader.read(6)
|
||
if priority_present:
|
||
default_priority = bool(reader.read(1))
|
||
info["default_priority"] = default_priority
|
||
if not default_priority:
|
||
info["priority_bits"] = reader.read(5)
|
||
render_status = 0 if (info["not_active"] or info["in_bed_or_isf"]) else 1
|
||
info["render_status"] = render_status
|
||
if render_status in (1, 3):
|
||
if render_status == 1:
|
||
# 5.5.11:obj_render_info[] = {true, true, true, true}。
|
||
position_present = zone_present = size_present = screen_present = True
|
||
else:
|
||
flags = reader.read(4)
|
||
position_present = bool(flags & 0x1)
|
||
zone_present = bool(flags & 0x2)
|
||
size_present = bool(flags & 0x4)
|
||
screen_present = bool(flags & 0x8)
|
||
if position_present:
|
||
# blk == 0 时 b_differential_position_specified 恒为 FALSE。
|
||
x = reader.read(6)
|
||
y = reader.read(6)
|
||
z_sign = reader.read(1)
|
||
z = reader.read(4)
|
||
info["position"] = (x, y, z if z_sign else -z)
|
||
if reader.read(1): # b_object_distance_specified
|
||
if not reader.read(1): # b_object_at_infinity
|
||
reader.read(4) # distance_factor_idx
|
||
if zone_present:
|
||
info["zone_constraints_idx"] = reader.read(3)
|
||
info["enable_elevation"] = bool(reader.read(1))
|
||
if size_present:
|
||
size_idx = reader.read(2)
|
||
info["object_size_idx"] = size_idx
|
||
if size_idx == 1:
|
||
reader.read(5)
|
||
elif size_idx == 2:
|
||
reader.read(15)
|
||
if screen_present:
|
||
if reader.read(1): # b_object_use_screen_ref
|
||
reader.read(3) # screen_factor_bits
|
||
reader.read(2) # depth_factor_idx
|
||
info["snap"] = bool(reader.read(1))
|
||
if reader.read(1): # b_additional_table_data_exists
|
||
info["additional_table_bytes"] = reader.read(4) + 1
|
||
reader.skip(info["additional_table_bytes"] * 8)
|
||
return info
|
||
|
||
|
||
def _parse_object_element(reader, element, raw_payload):
|
||
"""5.5.5 ``object_element()``:时间信息 + 每个对象一条 object_info_block。"""
|
||
object_count = element["object_count"]
|
||
try:
|
||
sample_offset_code = reader.read(2)
|
||
if sample_offset_code == 0:
|
||
sample_offset = 0
|
||
elif sample_offset_code == 1:
|
||
sample_offset = SAMPLE_OFFSET_INDEX[reader.read(2)]
|
||
elif sample_offset_code == 2:
|
||
sample_offset = reader.read(5)
|
||
else:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "md_sample_offset_mode",
|
||
"OAMD 使用了当前未覆盖的 MD sample-offset 模式",
|
||
details={
|
||
"sample_offset_code": sample_offset_code,
|
||
"payload": bytes_descriptor(raw_payload),
|
||
})
|
||
block_count = reader.read(3) + 1
|
||
blocks = []
|
||
for _block in range(block_count):
|
||
block_offset_factor = reader.read(6)
|
||
ramp_code = reader.read(2)
|
||
if ramp_code == 3:
|
||
if reader.read(1):
|
||
ramp_duration = RAMP_DURATION_INDEX[reader.read(4)]
|
||
else:
|
||
ramp_duration = reader.read(11)
|
||
else:
|
||
ramp_duration = RAMP_DURATIONS[ramp_code]
|
||
blocks.append({
|
||
"block_offset_samples": sample_offset + block_offset_factor * 32,
|
||
"ramp_duration_samples": ramp_duration,
|
||
})
|
||
reserved_data_not_present = bool(reader.read(1))
|
||
if not reserved_data_not_present:
|
||
reader.read(5)
|
||
objects = [
|
||
_parse_object_info_block(
|
||
reader, index, index < element["bed_isf_objects"], raw_payload)
|
||
for index in range(object_count)
|
||
]
|
||
except UnsupportedVariantError:
|
||
raise
|
||
except ValueError as exc:
|
||
raise _syntax_error(
|
||
"object_element_syntax",
|
||
"OAMD object element 字段越界或不完整",
|
||
raw_payload, exc,
|
||
element=_element_details([element])[0],
|
||
object_count=object_count,
|
||
) from exc
|
||
return {
|
||
"sample_offset": sample_offset,
|
||
"blocks": blocks,
|
||
"reserved_data_not_present": reserved_data_not_present,
|
||
"objects": objects,
|
||
}
|
||
|
||
|
||
def _parse_elements(reader, alternate_present, object_count, program, raw_payload):
|
||
"""5.5.4 ``oa_element_md()`` 目录;对象 element 按结构解析,其余按声明长度跳过。"""
|
||
try:
|
||
element_count = reader.read(4)
|
||
if element_count == 0xF:
|
||
element_count += reader.read(5)
|
||
except ValueError as exc:
|
||
raise _syntax_error(
|
||
"element_count_truncated", "OAMD element 数量字段不完整",
|
||
raw_payload, exc) from exc
|
||
if element_count == 0:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "missing_object_element",
|
||
"OAMD 没有声明任何 element",
|
||
details={"payload": bytes_descriptor(raw_payload)})
|
||
|
||
bed_isf_objects = program["num_bed_objects"] + program["num_isf_objects"]
|
||
elements = []
|
||
object_element = None
|
||
for ordinal in range(element_count):
|
||
header_start = reader.position
|
||
try:
|
||
element_id = reader.read(4)
|
||
size_bytes = _variable_bits_max(reader, 4, 4) + 1
|
||
except ValueError as exc:
|
||
raise _syntax_error(
|
||
"element_header_truncated", "OAMD element header 不完整",
|
||
raw_payload, exc,
|
||
element_ordinal=ordinal, header_start_bit=header_start) from exc
|
||
region_start = reader.position
|
||
region_end = region_start + size_bytes * 8
|
||
if region_end > reader.limit:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "element_bounds",
|
||
"OAMD element 声明长度超过 payload 边界",
|
||
details={
|
||
"element_ordinal": ordinal,
|
||
"element_id": element_id,
|
||
"size_bytes": size_bytes,
|
||
"body_start_bit": region_start,
|
||
"body_end_bit": region_end,
|
||
"payload_bits": reader.limit,
|
||
"payload": bytes_descriptor(raw_payload),
|
||
})
|
||
try:
|
||
alternate_data_id = (reader.read(4)
|
||
if alternate_present else None)
|
||
discard_unknown = bool(reader.read(1))
|
||
except ValueError as exc:
|
||
raise _syntax_error(
|
||
"element_control_bounds", "OAMD element 太短,无法容纳控制字段",
|
||
raw_payload, exc,
|
||
element_ordinal=ordinal, element_id=element_id,
|
||
size_bytes=size_bytes) from exc
|
||
element = {
|
||
"ordinal": ordinal,
|
||
"element_id": element_id,
|
||
"size_bytes": size_bytes,
|
||
"header_start_bit": header_start,
|
||
"body_start_bit": region_start,
|
||
"body_end_bit": region_end,
|
||
"data_start_bit": reader.position,
|
||
"alternate_data_id": alternate_data_id,
|
||
"discard_unknown": discard_unknown,
|
||
"object_count": object_count,
|
||
"bed_isf_objects": bed_isf_objects,
|
||
}
|
||
if element_id == OBJECT_ELEMENT_ID:
|
||
if object_element is not None:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "multiple_object_elements",
|
||
"OAMD 含多个 object element",
|
||
details={
|
||
"elements": _element_details(elements + [element]),
|
||
"payload": bytes_descriptor(raw_payload),
|
||
})
|
||
object_element = _parse_object_element(
|
||
reader, element, raw_payload)
|
||
element["parsed_end_bit"] = reader.position
|
||
element["object_element"] = object_element
|
||
# 个别编码器声明的 oa_element_size 比实际内容短;以结构解析结果为准。
|
||
reader.position = max(region_end, reader.position)
|
||
else:
|
||
# trim / extended / 未知 element:按声明长度整体跳过(5.5.4)。
|
||
element["parsed_end_bit"] = region_end
|
||
reader.position = region_end
|
||
elements.append(element)
|
||
|
||
if object_element is None:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "missing_object_element",
|
||
"OAMD 缺少 object element",
|
||
details={
|
||
"elements": _element_details(elements),
|
||
"payload": bytes_descriptor(raw_payload),
|
||
})
|
||
padding = reader.bits[reader.position:]
|
||
if np.any(padding):
|
||
raise UnsupportedVariantError(
|
||
"oamd", "nonzero_padding",
|
||
"OAMD payload 尾部 padding 含非零位",
|
||
details={
|
||
"elements": _element_details(elements),
|
||
"padding_start_bit": reader.position,
|
||
"padding_bits": "".join(str(int(bit)) for bit in padding[:64]),
|
||
"payload": bytes_descriptor(raw_payload),
|
||
})
|
||
return object_element, elements
|
||
|
||
|
||
def frame_update(bits_one):
|
||
"""单帧 OAMD → 位置字段增量及其 sample offset/ramp duration。"""
|
||
bits, raw_payload = _payload_bits(bits_one)
|
||
try:
|
||
version = bits[0] << 1 | bits[1]
|
||
except IndexError:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "header_truncated",
|
||
"OAMD payload 不足以容纳 header",
|
||
details={"payload_bits": len(bits),
|
||
"payload": bytes_descriptor(raw_payload)}) from None
|
||
reader = _BitReader(bits, 2)
|
||
try:
|
||
if version == 3:
|
||
version += reader.read(3)
|
||
object_count_bits = reader.read(5)
|
||
if object_count_bits == 0x1F:
|
||
object_count_bits += reader.read(7)
|
||
object_count = object_count_bits + 1
|
||
program = _parse_program_assignment(reader, raw_payload)
|
||
alternate_present = bool(reader.read(1))
|
||
object_element, elements = _parse_elements(
|
||
reader, alternate_present, object_count, program, raw_payload)
|
||
except UnsupportedVariantError:
|
||
raise
|
||
except ValueError as exc:
|
||
raise _syntax_error(
|
||
"header_truncated", "OAMD header 字段越界或不完整",
|
||
raw_payload, exc, payload_bits=len(bits)) from exc
|
||
|
||
if version != 0:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "oamd_version",
|
||
"OAMD 使用了当前未覆盖的 syntax version",
|
||
details={
|
||
"version": version,
|
||
"payload": bytes_descriptor(raw_payload),
|
||
})
|
||
if object_count > MAX_SLOTS:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "object_count",
|
||
"OAMD 对象数超过当前 16 槽对象模型",
|
||
details={
|
||
"object_count": object_count,
|
||
"max_slots": MAX_SLOTS,
|
||
"payload": bytes_descriptor(raw_payload),
|
||
})
|
||
blocks = object_element["blocks"]
|
||
if len(blocks) != 1:
|
||
raise UnsupportedVariantError(
|
||
"oamd", "multiple_position_blocks",
|
||
"OAMD 一帧含多个对象位置更新块,单次 frame_update 无法表达",
|
||
details={
|
||
"block_count": len(blocks),
|
||
"blocks": blocks,
|
||
"payload": bytes_descriptor(raw_payload),
|
||
"repair_hint": "按 ObjectInfoBlock 顺序逐块解析坐标,再生成分段 ADM ramp",
|
||
})
|
||
|
||
# 对外契约:始终给出 16 个槽;本帧没有覆盖到的槽保持上一帧位置。
|
||
out = {(slot, field): None for slot in range(MAX_SLOTS)
|
||
for field in ("q1", "q2", "q3")}
|
||
for info in object_element["objects"]:
|
||
slot = info["object"]
|
||
position = info.get("position")
|
||
if position is None:
|
||
# bed/ISF 对象与未激活对象没有位置字段:保持上一帧位置。
|
||
out[(slot, "q1")] = None
|
||
out[(slot, "q2")] = None
|
||
out[(slot, "q3")] = None
|
||
continue
|
||
x, y, z = position
|
||
out[(slot, "q1")] = q_of(x, N_Q12) if 0 <= x <= N_Q12 else None
|
||
out[(slot, "q2")] = q_of(y, N_Q12) if 0 <= y <= N_Q12 else None
|
||
# 5.6.1.1.10/5.6.1.1.11:pos3D_Z 带符号;当前 16 槽模型只表达非负高度。
|
||
out[(slot, "q3")] = q_of(z, N_Q3) if 0 <= z <= N_Q3 else (
|
||
0 if z < 0 else None)
|
||
|
||
size_mismatch = [
|
||
{
|
||
"element_ordinal": element["ordinal"],
|
||
"element_id": element["element_id"],
|
||
"declared_size_bytes": element["size_bytes"],
|
||
"declared_end_bit": element["body_end_bit"],
|
||
"parsed_end_bit": element["parsed_end_bit"],
|
||
}
|
||
for element in elements
|
||
if element["parsed_end_bit"] > element["body_end_bit"]
|
||
]
|
||
return {
|
||
"values": out,
|
||
"block_offset_samples": blocks[0]["block_offset_samples"],
|
||
"ramp_duration_samples": blocks[0]["ramp_duration_samples"],
|
||
"object_count": object_count,
|
||
"program": {
|
||
"dynamic_object_only": program["dynamic_object_only"],
|
||
"lfe_present": program["lfe_present"],
|
||
"content_description": program["content_description"],
|
||
"num_bed_objects": program["num_bed_objects"],
|
||
"num_isf_objects": program["num_isf_objects"],
|
||
"num_dynamic_objects": program["num_dynamic_objects"],
|
||
"bed_channels": [list(assignment["channels"])
|
||
for assignment in program["bed_assignments"]],
|
||
},
|
||
"elements": _element_details(elements),
|
||
"objects": object_element["objects"],
|
||
"size_mismatch": size_mismatch,
|
||
}
|
||
|
||
|
||
def frame_update_values(bits_one):
|
||
"""兼容接口:只返回 ``{(slot, field): q|None}``。"""
|
||
return frame_update(bits_one)["values"]
|
||
|
||
|
||
class JocFieldState:
|
||
"""未更新/非法窗保持旧值;slot0 q1/q2 的 DLL 初值为中心 16384。"""
|
||
|
||
def __init__(self):
|
||
self.q = {(obj, field): 0 for obj in range(16)
|
||
for field in ("q1", "q2", "q3")}
|
||
self.q[(0, "q1")] = 16384
|
||
self.q[(0, "q2")] = 16384
|
||
|
||
def apply(self, updates):
|
||
for key, value in updates.items():
|
||
if value is not None:
|
||
self.q[key] = value
|
||
return self
|
||
|
||
def snapshot(self):
|
||
return dict(self.q)
|