"""生成 25 声道 RF64 ADM BWF 及其 axml、chna、dbmd 元数据。 输出由 10 声道 7.1.2 bed 和 15 路对象组成;RF64 尺寸字段在写入完成后回填。 """ import operator 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 JOC_BINAURAL_MODES = { "off": 0, "near": 1, "far": 2, "mid": 3, "unspecified": 4, } JOC_BINAURAL_MODE_DEFAULT = "unspecified" 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("= 0: self.fp.seek(m + 4); self.fp.write(struct.pack("= 0: self.fp.seek(m + 8) self.fp.write(struct.pack(" {out_path} ({duration_sec:.2f}s, 25ch, axml={len(axml)}B, " f"chna={len(chna)}B, dbmd={len(dbmd)}B)")