Initial public release of JustOneCacophony

This commit is contained in:
TheM14
2026-09-01 16:31:38 +08:00
commit 6536782a0f
38 changed files with 9073 additions and 0 deletions
+273
View File
@@ -0,0 +1,273 @@
"""生成 25 声道 RF64 ADM BWF 及其 axml、chna、dbmd 元数据。
输出由 10 声道 7.1.2 bed 和 15 路对象组成;RF64 尺寸字段在写入完成后回填。
"""
import struct
import numpy as np
import xml.etree.ElementTree as ET
NS = "urn:ebu:metadata-schema:ebuCore_2016"
XSI = "http://www.w3.org/2001/XMLSchema-instance"
BED_NAMES = ["RoomCentricLeft", "RoomCentricRight", "RoomCentricCenter",
"RoomCentricLFE", "RoomCentricLeftSideSurround",
"RoomCentricRightSideSurround", "RoomCentricLeftRearSurround",
"RoomCentricRightRearSurround", "RoomCentricLeftTopSurround",
"RoomCentricRightTopSurround"]
BED_LABELS = ["RC_L", "RC_R", "RC_C", "RC_LFE", "RC_Lss", "RC_Rss",
"RC_Lrs", "RC_Rrs", "RC_Lts", "RC_Rts"]
BED_POS = [(-1.0, 1.0, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0),
(-1.0, 1.0, -1.0), (-1.0, 0.0, 0.0), (1.0, 0.0, 0.0),
(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (-1.0, 0.0, 1.0), (1.0, 0.0, 1.0)]
N_OBJ = 15
def q_to_adm_xyz(q1, q2, q3):
posX = min(1.0, round(q1 * 62 / 32767.0) / 62.0)
posY = min(1.0, round(q2 * 62 / 32767.0) / 62.0)
posZ = round(q3 * 15 / 32767.0) / 15.0
posZ = max(-1.0, min(1.0, posZ))
return posX * 2 - 1, 1 - posY * 2, posZ
def ts(seconds):
s = int(seconds)
frac = int(round((seconds - s) * 100000))
if frac >= 100000:
s += 1; frac = 0
return f"{s // 3600:02d}:{s % 3600 // 60:02d}:{s % 60:02d}.{frac:05d}"
def sub(parent, tag, attrib=None, text=None):
e = ET.SubElement(parent, tag)
if attrib:
for k, v in attrib.items():
e.set(k, v)
if text is not None:
e.text = text
return e
def add_refs(parent, tag, ids):
for i in ids:
sub(parent, tag, text=i)
def obj_block(cf, bid, t, x, y, z, dur, interpolation=0.0):
b = sub(cf, "audioBlockFormat", {
"audioBlockFormatID": bid, "rtime": ts(t), "duration": ts(dur)})
sub(b, "cartesian", text="1")
for c, v in (("X", x), ("Y", y), ("Z", z)):
if c == "Z" and v == 0:
continue
p = sub(b, "position", {"coordinate": c})
p.text = f"{v:.10f}"
sub(b, "jumpPosition", {"interpolationLength": f"{interpolation:.5f}"}, text="1")
def build_axml(obj_tracks, duration_sec):
adm = ET.Element("ebuCoreMain", {
"xmlns": NS, "xmlns:xsi": XSI,
"xsi:schemaLocation": f"{NS} ebucore.xsd", "lang": "en"})
core = sub(adm, "coreMetadata")
fmt = sub(core, "format")
af = sub(fmt, "audioFormatExtended")
prog = sub(af, "audioProgramme", {
"audioProgrammeID": "APR_1001", "audioProgrammeName": "EAC3JOC_Export",
"start": ts(0), "end": ts(duration_sec)})
add_refs(prog, "audioContentIDRef", ("ACO_1001", "ACO_1002"))
bc = sub(af, "audioContent", {"audioContentID": "ACO_1001",
"audioContentName": "EAC3JOC_Master_Content"})
add_refs(bc, "audioObjectIDRef", ["AO_1001"])
sub(bc, "dialogue", {"mixedContentKind": "0"})
oc = sub(af, "audioContent", {"audioContentID": "ACO_1002",
"audioContentName": "Objects"})
add_refs(oc, "audioObjectIDRef", ["AO_%04x" % (0x100b + i) for i in range(N_OBJ)])
sub(oc, "dialogue", {"mixedContentKind": "0"})
bed_o = sub(af, "audioObject", {"audioObjectID": "AO_1001", "audioObjectName": "Bed",
"start": ts(0), "duration": ts(duration_sec)})
sub(bed_o, "audioPackFormatIDRef", text="AP_00011001")
add_refs(bed_o, "audioTrackUIDRef", ["ATU_%08x" % (i + 1) for i in range(10)])
for i in range(N_OBJ):
o = sub(af, "audioObject", {"audioObjectID": "AO_%04x" % (0x100b + i),
"audioObjectName": f"Audio Object {i+1}",
"start": ts(0), "duration": ts(duration_sec)})
sub(o, "audioPackFormatIDRef", text="AP_0003%04x" % (0x1001 + i))
add_refs(o, "audioTrackUIDRef", ["ATU_%08x" % (i + 11)])
bp = sub(af, "audioPackFormat", {"audioPackFormatID": "AP_00011001",
"audioPackFormatName": "EAC3JOCBedPack",
"typeDefinition": "DirectSpeakers", "typeLabel": "0001"})
add_refs(bp, "audioChannelFormatIDRef", ["AC_0001%04x" % (0x1001 + i) for i in range(10)])
for i in range(N_OBJ):
pk = sub(af, "audioPackFormat", {"audioPackFormatID": "AP_0003%04x" % (0x1001 + i),
"audioPackFormatName": f"JOC_Object_{i+1}",
"typeDefinition": "Objects", "typeLabel": "0003"})
add_refs(pk, "audioChannelFormatIDRef", ["AC_0003%04x" % (0x1001 + i)])
for i in range(10):
cf = sub(af, "audioChannelFormat", {"audioChannelFormatID": "AC_0001%04x" % (0x1001 + i),
"audioChannelFormatName": BED_NAMES[i],
"typeDefinition": "DirectSpeakers", "typeLabel": "0001"})
b = sub(cf, "audioBlockFormat", {"audioBlockFormatID": "AB_0001%04x_00000001" % (0x1001 + i)})
sub(b, "cartesian", text="1")
x, y, z = BED_POS[i]
for c, v in (("X", x), ("Y", y), ("Z", z)):
if c == "Z" and v == 0:
continue
p = sub(b, "position", {"coordinate": c})
p.text = f"{v:.10f}"
sub(b, "speakerLabel", text=BED_LABELS[i])
for i, (oname, kfs) in enumerate(obj_tracks):
cf = sub(af, "audioChannelFormat", {"audioChannelFormatID": "AC_0003%04x" % (0x1001 + i),
"audioChannelFormatName": oname,
"typeDefinition": "Objects", "typeLabel": "0003"})
for k, keyframe in enumerate(kfs):
t, x, y, z, dur = keyframe[:5]
interpolation = keyframe[5] if len(keyframe) > 5 else 0.0
obj_block(cf, "AB_0003%04x_%08x" % (0x1001 + i, k + 1),
t, x, y, z, dur, interpolation)
for i in range(10):
t = sub(af, "audioTrackUID", {"UID": "ATU_%08x" % (i + 1),
"bitDepth": "24", "sampleRate": "48000"})
sub(t, "audioTrackFormatIDRef", text="AT_0001%04x_01" % (0x1001 + i))
sub(t, "audioPackFormatIDRef", text="AP_00011001")
for i in range(N_OBJ):
t = sub(af, "audioTrackUID", {"UID": "ATU_%08x" % (i + 11),
"bitDepth": "24", "sampleRate": "48000"})
sub(t, "audioTrackFormatIDRef", text="AT_0003%04x_01" % (0x1001 + i))
sub(t, "audioPackFormatIDRef", text="AP_0003%04x" % (0x1001 + i))
for i in range(10):
tf = sub(af, "audioTrackFormat", {"audioTrackFormatID": "AT_0001%04x_01" % (0x1001 + i),
"audioTrackFormatName": "PCM_" + BED_NAMES[i],
"formatDefinition": "PCM", "formatLabel": "0001"})
sub(tf, "audioStreamFormatIDRef", text="AS_0001%04x" % (0x1001 + i))
for i in range(N_OBJ):
tf = sub(af, "audioTrackFormat", {"audioTrackFormatID": "AT_0003%04x_01" % (0x1001 + i),
"audioTrackFormatName": "PCM_JOC_Object_%d" % (i + 1),
"formatDefinition": "PCM", "formatLabel": "0001"})
sub(tf, "audioStreamFormatIDRef", text="AS_0003%04x" % (0x1001 + i))
for i in range(10):
sf = sub(af, "audioStreamFormat", {"audioStreamFormatID": "AS_0001%04x" % (0x1001 + i),
"audioStreamFormatName": "PCM_" + BED_NAMES[i],
"formatDefinition": "PCM", "formatLabel": "0001"})
sub(sf, "audioChannelFormatIDRef", text="AC_0001%04x" % (0x1001 + i))
sub(sf, "audioPackFormatIDRef", text="AP_00011001")
sub(sf, "audioTrackFormatIDRef", text="AT_0001%04x_01" % (0x1001 + i))
for i in range(N_OBJ):
sf = sub(af, "audioStreamFormat", {"audioStreamFormatID": "AS_0003%04x" % (0x1001 + i),
"audioStreamFormatName": "PCM_JOC_Object_%d" % (i + 1),
"formatDefinition": "PCM", "formatLabel": "0001"})
sub(sf, "audioChannelFormatIDRef", text="AC_0003%04x" % (0x1001 + i))
sub(sf, "audioPackFormatIDRef", text="AP_0003%04x" % (0x1001 + i))
sub(sf, "audioTrackFormatIDRef", text="AT_0003%04x_01" % (0x1001 + i))
return ET.tostring(adm, encoding="utf-8", xml_declaration=True)
def build_chna():
out = bytearray()
out += struct.pack("<HH", 25, 25)
for i in range(10):
out += struct.pack("<H", i + 1)
out += ("ATU_%08x" % (i + 1)).encode()
out += ("AT_0001%04x_01" % (0x1001 + i)).encode()
out += b"AP_00011001" + b"\x00"
for i in range(N_OBJ):
out += struct.pack("<H", i + 11)
out += ("ATU_%08x" % (i + 11)).encode()
out += ("AT_0003%04x_01" % (0x1001 + i)).encode()
out += ("AP_0003%04x" % (0x1001 + i)).encode() + b"\x00"
return bytes(out)
def _checksum(seg):
s = len(seg)
for b in seg:
s += b
return (~s + 1) & 0xFF
def build_dbmd(object_count=25):
out = bytearray(struct.pack("<I", 0x01000006))
dd = bytearray(96)
dd[1] = 0x47
dd[5] = 0x60
dd[8] = 0x24; dd[9] = 0x24
out.append(7); out += struct.pack("<H", 96); out += bytes(dd)
out.append(_checksum(dd))
at = bytearray(248)
c0 = b"Created with EAC3JOC"; c1 = b"EAC3JOC Python Renderer"
at[0:len(c0)] = c0
at[32:32 + len(c1)] = c1
at[96], at[97], at[98] = 2, 1, 0
at[103] = 0x03
at[106] = 0x01
at[111] = 0x22; at[112] = 0xFF
out.append(9); out += struct.pack("<H", 248); out += bytes(at)
out.append(_checksum(at))
ob = bytearray(5 + 262 + object_count)
ob[0:4] = struct.pack("<I", 0xF8726FBD)
ob[4] = object_count
for i in range(5 + 262, len(ob)):
ob[i] = 0x84
out.append(10); out += struct.pack("<H", len(ob)); out += bytes(ob)
out.append(_checksum(ob))
out += b"\x00\x00"
return bytes(out)
class Sink25:
def __init__(self, path, channels, rate):
self.ch = channels; self.rate = rate; self.frames = 0
self.fp = open(path, "wb+")
self.fp.write(b"RF64" + struct.pack("<I", 0xFFFFFFFF) + b"WAVE")
self._chunk(b"ds64", b"\x00" * 64)
self._chunk(b"fmt ", self._fmt())
self._chunk(b"data", b"")
def _chunk(self, cid, body):
self.fp.write(cid + struct.pack("<I", len(body)) + body)
if len(body) & 1:
self.fp.write(b"\x00")
def _fmt(self):
return struct.pack("<HHIIHH", 1, self.ch, self.rate,
self.rate * self.ch * 3, self.ch * 3, 24)
def write_block(self, arr):
arr = arr.reshape(-1, self.ch)
i24 = (np.clip(arr, -1.0, 1.0) * 8388607.0).astype(np.int32)
self.fp.write(i24.view(np.uint8).reshape(-1, 4)[:, :3].tobytes())
self.frames += arr.shape[0]
def finalize(self, axml_bytes, chna_bytes, dbmd_bytes):
data_len = self.frames * self.ch * 3
self._chunk(b"axml", axml_bytes)
self._chunk(b"chna", chna_bytes)
self._chunk(b"dbmd", dbmd_bytes)
self.fp.seek(0, 2); total = self.fp.tell()
self.fp.seek(0); head = self.fp.read()
m = head.find(b"data")
if m >= 0:
self.fp.seek(m + 4); self.fp.write(struct.pack("<I", data_len))
m = head.find(b"ds64")
if m >= 0:
self.fp.seek(m + 8)
self.fp.write(struct.pack("<QQQI", total - 8, data_len, self.frames, 0))
self.fp.flush()
self.fp.close()
def build_master(out_path, bed_mm, obj_mm, kf_tracks, duration_sec, rate=48000,
block=480000):
n = min(bed_mm.shape[0], obj_mm.shape[0])
try:
from . import adm_serializer
except ImportError:
import adm_serializer
serial_axml = adm_serializer.build_axml
axml = serial_axml(kf_tracks, duration_sec)
chna = build_chna()
dbmd = build_dbmd(25)
sink = Sink25(out_path, 25, rate)
for st in range(0, n, block):
en = min(n, st + block)
blk = np.hstack((np.asarray(bed_mm[st:en], dtype=np.float32),
np.asarray(obj_mm[st:en, 1:16], dtype=np.float32)))
sink.write_block(blk)
sink.finalize(axml, chna, dbmd)
print(f"master25 -> {out_path} ({duration_sec:.2f}s, 25ch, axml={len(axml)}B, "
f"chna={len(chna)}B, dbmd={len(dbmd)}B)")