From 7720af9c414797c16c75663ecb29f4f7432283f3 Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Mon, 15 Jul 2024 15:24:37 -0400 Subject: [PATCH 01/13] Attempt to decode DBMD chunk of wav --- format/riff/dbmd.go | 112 ++++++++++++++++++++++++++++++++++++++++++++ format/riff/wav.go | 4 ++ 2 files changed, 116 insertions(+) create mode 100644 format/riff/dbmd.go diff --git a/format/riff/dbmd.go b/format/riff/dbmd.go new file mode 100644 index 000000000..1e073e5dd --- /dev/null +++ b/format/riff/dbmd.go @@ -0,0 +1,112 @@ +package riff + +import ( + "fmt" + + "github.com/wader/fq/pkg/decode" +) + +func dbmdDecode(d *decode.D) any { + // TODO(jmarnell): Read as string (and assert?) + // d.FieldUTF8("chunk_id", 4, d.StrAssert("dbmd")) + d.FieldU32("chunk_id") + + d.FieldU32("chunk_size") + // TODO(jmarnell): Should be string formatted to: "'1.15.2.0' corresponds to 0x010F0200" + d.FieldU32("version") + + d.FieldArray("metadata_segments", func(d *decode.D) { + for { + d.FieldStruct("metadata_segment", func(d *decode.D) { + segmentID := d.FieldU8("metadata_segment_id") + fmt.Println("segmentID: ", segmentID) + if segmentID == 0 { + return + } + + switch segmentID { + case 1: + parseDolbyE(d) + case 3: + parseDolbyDigital(d) + case 7: + parseDolbyDigitalPlus(d) + case 8: + parseAudioInfo(d) + default: + d.FieldRawLen("unknown_segment_raw", int64(d.BitsLeft())) + } + }) + } + }) + + return nil +} + +func parseDolbyE(d *decode.D) { + d.FieldU8("program_config") + d.FieldU8("frame_rate_code") + d.FieldRawLen("e_SMPTE_time_code", 8*8) + d.FieldRawLen("e_reserved", 1*8) + d.FieldRawLen("e_reserved2", 25*8) + d.FieldRawLen("reserved_for_future_use", 80*8) +} + +func parseDolbyDigital(d *decode.D) { + d.FieldU8("ac3_program_id") + d.FieldU8("program_info") + d.FieldU8("datarate_info") + d.FieldRawLen("reserved", 1*8) + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("ac3_langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + d.FieldRawLen("reserved2", 3*8) + d.FieldU8("ac3_compr1") + d.FieldU8("ac3_dynrng1") + d.FieldRawLen("reserved_for_future_use", 21*8) + d.FieldRawLen("program_description_text", 32*8) +} + +func parseDolbyDigitalPlus(d *decode.D) { + d.FieldU8("program_id") + d.FieldU8("program_info") + d.FieldRawLen("ddplus_reserved1", 2*8) + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + d.FieldRawLen("ddplus_reserved2", 1*8) + d.FieldRawLen("ddplus_reserved3", 1*8) + d.FieldRawLen("ddplus_reserved4", 1*8) + d.FieldU8("compr1") + d.FieldU8("dynrng1") + d.FieldRawLen("ddplus_reserved5", 3*8) + d.FieldU8("ddplus_info1") + d.FieldRawLen("ddplus_reserved6", 5*8) + d.FieldU16LE("datarate") + d.FieldRawLen("reserved_for_future_use", 69*8) +} + +func parseAudioInfo(d *decode.D) { + d.FieldU8("program_id") + d.FieldUTF8("audio_origin", 32) + d.FieldU32LE("largest_sample_value") + d.FieldU32LE("largest_sample_value_2") + d.FieldU32LE("largest_true_peak_value") + d.FieldU32LE("largest_true_peak_value_2") + d.FieldU32LE("dialogue_loudness") + d.FieldU32LE("dialogue_loudness_2") + d.FieldU32LE("speech_content") + d.FieldU32LE("speech_content_2") + d.FieldUTF8("last_processed_by", 32) + d.FieldUTF8("last_operation", 32) + d.FieldUTF8("segment_creation_date", 32) + d.FieldUTF8("segment_modified_date", 32) +} diff --git a/format/riff/wav.go b/format/riff/wav.go index 6237089b3..734391a1a 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -158,6 +158,10 @@ func wavDecode(d *decode.D) any { d.FieldRawLen("coding_history", d.BitsLeft()) return false, nil + case "dbmd": + dbmdDecode(d) + return false, nil + default: if riffIsStringChunkID(id) { d.FieldUTF8NullFixedLen("value", int(d.BitsLeft())/8) From 42cce6612bfbab245f0f7af9b6eecac13d2039d2 Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Thu, 15 Aug 2024 23:24:41 -0400 Subject: [PATCH 02/13] Split and implement dolby metadata --- format/riff/adm.go | 31 +++++ format/riff/aiff.go | 2 +- format/riff/avi.go | 2 +- format/riff/common.go | 8 +- format/riff/dbmd.go | 112 ---------------- format/riff/dolby.go | 303 ++++++++++++++++++++++++++++++++++++++++++ format/riff/wav.go | 10 +- format/riff/webp.go | 2 +- 8 files changed, 351 insertions(+), 119 deletions(-) create mode 100644 format/riff/adm.go delete mode 100644 format/riff/dbmd.go create mode 100644 format/riff/dolby.go diff --git a/format/riff/adm.go b/format/riff/adm.go new file mode 100644 index 000000000..21cd0a1e8 --- /dev/null +++ b/format/riff/adm.go @@ -0,0 +1,31 @@ +package riff + +// Audio Definition Model +// https://adm.ebu.io/background/what_is_the_adm.html +// https://tech.ebu.ch/publications/tech3285s7 +// https://tech.ebu.ch/publications/tech3285s5 + +import ( + "github.com/wader/fq/pkg/decode" +) + +func chnaDecode(d *decode.D, size int64) { + d.FieldU16("num_tracks") + d.FieldU16("num_uids") + + audioIdLen := (size - 4) / 40 + d.FieldStructNArray("audio_ids", "audio_id", int64(audioIdLen), func(d *decode.D) { + d.FieldU16("track_index") + d.FieldUTF8("uid", 12) + d.FieldUTF8("track_format_id_reference", 14) + d.FieldUTF8("pack_format_id_reference", 11) + // Skip padding single byte + d.U8() + }) +} + +func axmlDecode(d *decode.D, size int64) { + // fmt.Println("test axml") + // TODO(jmarnell): this chunk is all variable xml, so leave as is? + d.FieldRawLen("xml", size*8) +} diff --git a/format/riff/aiff.go b/format/riff/aiff.go index 6efcd68c7..d02346176 100644 --- a/format/riff/aiff.go +++ b/format/riff/aiff.go @@ -54,7 +54,7 @@ func aiffDecode(d *decode.D) any { } return id, size }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "FORM": riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType)) diff --git a/format/riff/avi.go b/format/riff/avi.go index d35ae3a85..f2c57406b 100644 --- a/format/riff/avi.go +++ b/format/riff/avi.go @@ -238,7 +238,7 @@ func aviDecodeEx(d *decode.D, ai format.AVI_In, extendedChunk bool) { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "RIFF": foundRiffType = d.FieldUTF8("type", 4, d.StrAssert(requiredRiffType)) diff --git a/format/riff/common.go b/format/riff/common.go index f15a821db..eeffe6222 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -19,11 +19,11 @@ func (p path) topData() any { return p[len(p)-1].data } -func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path) (bool, any)) { +func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path, size int64) (bool, any)) { id, size := headFn(d, path) d.FramedFn(size*8, func(d *decode.D) { - hasChildren, data := chunkFn(d, id, path) + hasChildren, data := chunkFn(d, id, path, size) if hasChildren { np := append(path, pathEntry{id: id, data: data}) d.FieldArray("chunks", func(d *decode.D) { @@ -58,6 +58,10 @@ var chunkIDDescriptions = scalar.StrMapDescription{ "dmlh": "Extended AVI header", + "chna": " Chunk, Track UIDs of Audio Definition Model", + "axml": " Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements", + "dbmd": "Dolby Metadata", + "ISMP": "SMPTE timecode", "IDIT": "Time and date digitizing commenced", "IARL": "Archival Location. Indicates where the subject of the file is archived.", diff --git a/format/riff/dbmd.go b/format/riff/dbmd.go deleted file mode 100644 index 1e073e5dd..000000000 --- a/format/riff/dbmd.go +++ /dev/null @@ -1,112 +0,0 @@ -package riff - -import ( - "fmt" - - "github.com/wader/fq/pkg/decode" -) - -func dbmdDecode(d *decode.D) any { - // TODO(jmarnell): Read as string (and assert?) - // d.FieldUTF8("chunk_id", 4, d.StrAssert("dbmd")) - d.FieldU32("chunk_id") - - d.FieldU32("chunk_size") - // TODO(jmarnell): Should be string formatted to: "'1.15.2.0' corresponds to 0x010F0200" - d.FieldU32("version") - - d.FieldArray("metadata_segments", func(d *decode.D) { - for { - d.FieldStruct("metadata_segment", func(d *decode.D) { - segmentID := d.FieldU8("metadata_segment_id") - fmt.Println("segmentID: ", segmentID) - if segmentID == 0 { - return - } - - switch segmentID { - case 1: - parseDolbyE(d) - case 3: - parseDolbyDigital(d) - case 7: - parseDolbyDigitalPlus(d) - case 8: - parseAudioInfo(d) - default: - d.FieldRawLen("unknown_segment_raw", int64(d.BitsLeft())) - } - }) - } - }) - - return nil -} - -func parseDolbyE(d *decode.D) { - d.FieldU8("program_config") - d.FieldU8("frame_rate_code") - d.FieldRawLen("e_SMPTE_time_code", 8*8) - d.FieldRawLen("e_reserved", 1*8) - d.FieldRawLen("e_reserved2", 25*8) - d.FieldRawLen("reserved_for_future_use", 80*8) -} - -func parseDolbyDigital(d *decode.D) { - d.FieldU8("ac3_program_id") - d.FieldU8("program_info") - d.FieldU8("datarate_info") - d.FieldRawLen("reserved", 1*8) - d.FieldU8("surround_config") - d.FieldU8("dialnorm_info") - d.FieldU8("ac3_langcod") - d.FieldU8("audio_prod_info") - d.FieldU8("ext_bsi1_word1") - d.FieldU8("ext_bsi1_word2") - d.FieldU8("ext_bsi2_word1") - d.FieldRawLen("reserved2", 3*8) - d.FieldU8("ac3_compr1") - d.FieldU8("ac3_dynrng1") - d.FieldRawLen("reserved_for_future_use", 21*8) - d.FieldRawLen("program_description_text", 32*8) -} - -func parseDolbyDigitalPlus(d *decode.D) { - d.FieldU8("program_id") - d.FieldU8("program_info") - d.FieldRawLen("ddplus_reserved1", 2*8) - d.FieldU8("surround_config") - d.FieldU8("dialnorm_info") - d.FieldU8("langcod") - d.FieldU8("audio_prod_info") - d.FieldU8("ext_bsi1_word1") - d.FieldU8("ext_bsi1_word2") - d.FieldU8("ext_bsi2_word1") - d.FieldRawLen("ddplus_reserved2", 1*8) - d.FieldRawLen("ddplus_reserved3", 1*8) - d.FieldRawLen("ddplus_reserved4", 1*8) - d.FieldU8("compr1") - d.FieldU8("dynrng1") - d.FieldRawLen("ddplus_reserved5", 3*8) - d.FieldU8("ddplus_info1") - d.FieldRawLen("ddplus_reserved6", 5*8) - d.FieldU16LE("datarate") - d.FieldRawLen("reserved_for_future_use", 69*8) -} - -func parseAudioInfo(d *decode.D) { - d.FieldU8("program_id") - d.FieldUTF8("audio_origin", 32) - d.FieldU32LE("largest_sample_value") - d.FieldU32LE("largest_sample_value_2") - d.FieldU32LE("largest_true_peak_value") - d.FieldU32LE("largest_true_peak_value_2") - d.FieldU32LE("dialogue_loudness") - d.FieldU32LE("dialogue_loudness_2") - d.FieldU32LE("speech_content") - d.FieldU32LE("speech_content_2") - d.FieldUTF8("last_processed_by", 32) - d.FieldUTF8("last_operation", 32) - d.FieldUTF8("segment_creation_date", 32) - d.FieldUTF8("segment_modified_date", 32) -} diff --git a/format/riff/dolby.go b/format/riff/dolby.go new file mode 100644 index 000000000..af2cb3801 --- /dev/null +++ b/format/riff/dolby.go @@ -0,0 +1,303 @@ +package riff + +// Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus] +// https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf +// https://github.com/DolbyLaboratories/dbmd-atmos-parser + +import ( + "fmt" + "strings" + + "github.com/wader/fq/pkg/decode" + "github.com/wader/fq/pkg/scalar" +) + +func dbmdDecode(d *decode.D, size int64) any { + version := d.U32() + major := (version >> 24) & 0xFF + minor := (version >> 16) & 0xFF + patch := (version >> 8) & 0xFF + build := version & 0xFF + d.FieldValueStr("version", fmt.Sprintf("%d.%d.%d.%d", major, minor, patch, build)) + + d.FieldArray("metadata_segments", func(d *decode.D) { + for { + d.FieldStruct("metadata_segment", func(d *decode.D) { + segmentID := d.FieldU8("metadata_segment_id") + + // TODO(jmarnell): I think I need a loop until, but not creating these empty segments + // spec says we're done with 0 ID, so I'd like to not make the empty segment(s) + if segmentID == 0 { + if d.BitsLeft() > 0 { + d.SeekRel(d.BitsLeft() * 8) + } + return + } + + segmentSize := d.FieldU16("metadata_segment_size") + bitsLeft := d.BitsLeft() + + switch segmentID { + case 1: + parseDolbyE(d) + case 3: + parseDolbyDigital(d) + case 7: + parseDolbyDigitalPlus(d) + case 8: + parseAudioInfo(d) + case 9: + parseDolbyAtmos(d, segmentSize) + case 10: + parseDolbyAtmosSupplemental(d, segmentSize) + default: + d.FieldRawLen("unknown_segment_raw", int64(segmentSize*8)) + } + + fmt.Println("bytesRead: ", (bitsLeft-d.BitsLeft())/8, " segment size:", segmentSize) + + bytesRemaining := (bitsLeft-d.BitsLeft())/8 - int64(segmentSize) + if bytesRemaining < 0 { + d.Fatalf("Read too many bytes for segment %d, read %d over, expected %d", segmentID, -bytesRemaining, segmentSize) + } else if bytesRemaining > 0 { + d.FieldValueUint("SKIPPED_BYTES", uint64(bytesRemaining)) + d.SeekRel((int64(segmentSize) - bytesRemaining) * 8) + } + d.FieldU8("metadata_segment_checksum") + }) + } + }) + + return nil +} + +var compressionDesc = scalar.UintMapDescription{ + 0: "none", + 1: "Film, Standard", + 2: "Film, Light", + 3: "Music, Standard", + 4: "Music, Light", + 5: "Speech", + // TODO(jmarnell): Can I handle rest is "Reserved"? +} + +// TODO(jmarnell): Better way to handle "Reserved"? +func mapWithReserved(m map[uint64]string, key uint64) string { + if val, ok := m[key]; ok { + return val + } + return "Reserved" +} + +var bitstreamMode = scalar.UintMapDescription{ + 0b000: "main audio service: complete main (CM)", + 0b001: "main audio service: music and effects (ME)", + 0b010: "associated service: visually impaired (VI)", + 0b011: "associated service: hearing impaired (HI)", + 0b100: "associated service: dialogue (D)", + 0b101: "associated service: commentary (C)", + 0b110: "associated service: emergency (E)", + 0b111: "associated service: voice over (VO)", + + 0b1000: "associated service: karaoke (K)", +} + +var binaural = scalar.UintMapDescription{ + 0: "bypass", + 1: "near", + 2: "far", + 3: "mid", + 4: "not indicated", +} + +var warpMode = scalar.UintMapDescription{ + 0: "normal", + 1: "warping", + 2: "downmix Dolby Pro Logic IIx", + 3: "downmix LoRo", + 4: "not indicated (Default warping will be applied.)", +} + +var trimConfigName = scalar.UintMapDescription{ + 0: "2.0", + 1: "5.1", + 2: "7.1", + 3: "2.1.2", + 4: "5.1.2", + 5: "7.1.2", + 6: "2.1.4", + 7: "5.1.4", + 8: "7.1.4", +} + +var trimType = scalar.UintMapDescription{ + 0: "manual", + 1: "automatic", +} + +func parseDolbyE(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "dolby_e") + + d.FieldU8("program_config") + d.FieldU8("frame_rate_code") + d.FieldRawLen("e_SMPTE_time_code", 8*8) + d.FieldRawLen("e_reserved", 1*8) + d.FieldRawLen("e_reserved2", 25*8) + d.FieldRawLen("reserved_for_future_use", 80*8) +} + +func parseDolbyDigital(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "dolby_digital") + + d.FieldU8("ac3_program_id") + d.FieldU8("program_info") + d.FieldU8("datarate_info") + d.FieldRawLen("reserved", 1*8) + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("ac3_langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + d.FieldRawLen("reserved2", 3*8) + d.FieldU8("ac3_compr1") + d.FieldU8("ac3_dynrng1") + d.FieldRawLen("reserved_for_future_use", 21*8) + d.FieldRawLen("program_description_text", 32*8) +} + +func parseDolbyDigitalPlus(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "dolby_digital_plus") + + d.FieldU8("program_id") + programInfo := d.FieldU8("program_info") + lfeon := programInfo & 0b1_000_000 + bsmod := programInfo & 0b0_111_000 + acmod := programInfo & 0b0_000_111 + d.FieldValueBool("lfe_on", lfeon != 0) + if bsmod == 0b111 && acmod != 0b001 { + bsmod = 0b1000 + } + d.FieldValueStr("bitstream_mode", bitstreamMode[bsmod]) + + d.FieldU16LE("ddplus_reserved_a") + + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + + d.FieldU24LE("ddplus_reserved_b") + + d.FieldValueStr("compr1_type", mapWithReserved(compressionDesc, d.FieldU8("compr1"))) + d.FieldValueStr("dynrng1_type", mapWithReserved(compressionDesc, d.FieldU8("dynrng1"))) + + d.FieldU24LE("ddplus_reserved_c") + + d.FieldU8("ddplus_info1") + + d.FieldU40LE("ddplus_reserved_d") + + d.FieldU16LE("datarate") + d.FieldRawLen("reserved_for_future_use", 69*8) +} + +func parseAudioInfo(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "audio_info") + + d.FieldU8("program_id") + d.FieldUTF8("audio_origin", 32) + d.FieldU32LE("largest_sample_value") + d.FieldU32LE("largest_sample_value_2") + d.FieldU32LE("largest_true_peak_value") + d.FieldU32LE("largest_true_peak_value_2") + d.FieldU32LE("dialogue_loudness") + d.FieldU32LE("dialogue_loudness_2") + d.FieldU32LE("speech_content") + d.FieldU32LE("speech_content_2") + d.FieldUTF8("last_processed_by", 32) + d.FieldUTF8("last_operation", 32) + d.FieldUTF8("segment_creation_date", 32) + d.FieldUTF8("segment_modified_date", 32) +} + +func parseDolbyAtmos(d *decode.D, size uint64) { + d.FieldValueStr("metadata_segment_type", "dolby_atmos") + bitsLeft := d.BitsLeft() + + // d.SeekRel(32 * 8) + str := d.FieldUTF8Null("atmos_dbmd_content_creation_preamble") + d.SeekRel(int64(32-len(str)-1) * 8) + + str = d.FieldUTF8Null("atmos_dbmd_content_creation_tool") + d.SeekRel(int64(64-len(str)-1) * 8) + + major := d.U8() + minor := d.U8() + micro := d.U8() + d.FieldValueStr("version", fmt.Sprintf("%d.%d.%d", major, minor, micro)) + d.SeekRel(53 * 8) + + warpBedReserved := d.U8() + d.FieldValueUint("warp_mode", warpBedReserved&0x7) + d.FieldValueStr("warp_mode_type", warpMode[warpBedReserved&0x7]) + + d.SeekRel(15 * 8) + d.SeekRel(80 * 8) + + fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) + d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) +} + +func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { + d.FieldValueStr("metadata_segment_type", "dolby_atmos_supplemental") + bitsLeft := d.BitsLeft() + + sync := d.FieldU32LE("dasms_sync") + d.FieldValueBool("dasms_sync_valid", sync == 0xf8726fbd) + + objectCount := int64(d.FieldU16LE("object_count")) + d.FieldU8LE("reserved") + + i := 0 + d.FieldStructNArray("trim_configs", "trim_config", 9, func(d *decode.D) { + autoTrimReserved := d.FieldU8LE("auto_trim_reserved") + autoTrim := autoTrimReserved & 0x01 + d.FieldValueBool("auto_trim", autoTrim == 1) + d.FieldValueStr("trim_type", trimType[autoTrim]) + d.FieldValueStr("trim_config_name", trimConfigName[uint64(i)]) + + //d.SeekRel(14 * 8) + // d.FieldUTF8("raw", 14) + str := d.UTF8(14) + bytes := []byte(str) + var nonZeroBytes []string + for _, b := range bytes { + if b != 0 { + nonZeroBytes = append(nonZeroBytes, fmt.Sprintf("%d", b)) + } + } + // TODO(jmarnell): I think the +3dB trim settings are here. Would like this as an array of numbers + // at least, instead of CSV string + d.FieldValueStr("trim_defs", strings.Join(nonZeroBytes, ", ")) + + i++ + }) + + d.FieldStructNArray("objects", "object", objectCount, func(d *decode.D) { + d.FieldU8LE("object_value") + }) + + d.FieldStructNArray("binaural_render_modes", "binaural_render_mode", objectCount, func(d *decode.D) { + mode := d.U8LE() & 0x7 + d.FieldValueUint("render_mode", mode) + d.FieldValueStr("render_mode_type", binaural[mode]) + }) + + fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) + d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) +} diff --git a/format/riff/wav.go b/format/riff/wav.go index 734391a1a..b1a0eecee 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -76,7 +76,7 @@ func wavDecode(d *decode.D) any { return id, size }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(wavRiffType)) @@ -158,8 +158,14 @@ func wavDecode(d *decode.D) any { d.FieldRawLen("coding_history", d.BitsLeft()) return false, nil + case "chna": + chnaDecode(d, size) + return false, nil + case "axml": + axmlDecode(d, size) + return false, nil case "dbmd": - dbmdDecode(d) + dbmdDecode(d, size) return false, nil default: diff --git a/format/riff/webp.go b/format/riff/webp.go index ebc0bbc59..de69d6238 100644 --- a/format/riff/webp.go +++ b/format/riff/webp.go @@ -44,7 +44,7 @@ func webpDecode(d *decode.D) any { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(webpRiffType)) From d2c84e1bd5a7e8b9e1587c2c8d970dbd4c8f7899 Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Thu, 15 Aug 2024 23:58:19 -0400 Subject: [PATCH 03/13] Docs and minor cleanup --- README.md | 4 ++++ doc/formats.md | 43 ++++++++++++++++++++++++++++++++++++++++++- format/riff/adm.go | 1 - format/riff/common.go | 2 +- format/riff/dolby.go | 15 +++------------ 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 41e572e93..a2f056dc6 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ It was originally designed to query, inspect and debug media codecs and containe [aac_frame](doc/formats.md#aac_frame), adts, adts_frame, +[adm](doc/formats.md#adm), aiff, amf0, apev2, @@ -53,6 +54,7 @@ avc_sei, avc_sps, [avi](doc/formats.md#avi), [avro_ocf](doc/formats.md#avro_ocf), +[axml](doc/formats.md#adm), [bencode](doc/formats.md#bencode), bitcoin_blkdat, [bitcoin_block](doc/formats.md#bitcoin_block), @@ -66,7 +68,9 @@ bsd_loopback_frame, bzip2, [caff](doc/formats.md#caff), [cbor](doc/formats.md#cbor), +[chna](doc/formats.md#adm), [csv](doc/formats.md#csv), +[dolby_metadata](doc/formats.md#dolby_metadata), dns, dns_tcp, elf, diff --git a/doc/formats.md b/doc/formats.md index 448dab81d..61186ae23 100644 --- a/doc/formats.md +++ b/doc/formats.md @@ -7,6 +7,7 @@ |[`aac_frame`](#aac_frame) |Advanced Audio Coding frame || |`adts` |Audio Data Transport Stream |`adts_frame`| |`adts_frame` |Audio Data Transport Stream frame |`aac_frame`| +|[`adm`](#adm) |Audio Definition Model |`riff`| |`aiff` |Audio Interchange File Format || |`amf0` |Action Message Format 0 || |`apev2` |APEv2 metadata tag |`image`| @@ -25,6 +26,7 @@ |`avc_sps` |H.264/AVC Sequence Parameter Set || |[`avi`](#avi) |Audio Video Interleaved |`avc_au` `hevc_au` `mp3_frame` `flac_frame`| |[`avro_ocf`](#avro_ocf) |Avro object container file || +|[`axml`](#adm) |Audio Definition Model  Chunk |`riff`| |[`bencode`](#bencode) |BitTorrent bencoding || |`bitcoin_blkdat` |Bitcoin blk.dat |`bitcoin_block`| |[`bitcoin_block`](#bitcoin_block) |Bitcoin block |`bitcoin_transaction`| @@ -38,7 +40,9 @@ |`bzip2` |bzip2 compression |`probe`| |[`caff`](#caff) |Live2D Cubism archive |`probe`| |[`cbor`](#cbor) |Concise Binary Object Representation || +|[`chna`](#adm) |Audio Definition Model  Chunk |`riff`| |[`csv`](#csv) |Comma separated values || +|[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Digital Plus) |`riff`| |`dns` |DNS packet || |`dns_tcp` |DNS packet (TCP) || |`elf` |Executable and Linkable Format || @@ -177,6 +181,27 @@ Decode value as aac_frame ... | aac_frame({object_type:1}) ``` +## adm +[Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) including 3D Audio. + +RIFF / WAV / Broadcast Wave Format (BWF) chunks: +- `` Chunk, Track UIDs of Audio Definition Model +- `` Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements + +### Examples +Decode ADM configuration from `` and `` chunks: +``` +$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' bwf.wav +``` + +### Authors +- [@johnnymarnell](https://johnnymarnell.github.io), original author + +### References +- https://adm.ebu.io/background/what_is_the_adm.html +- https://tech.ebu.ch/publications/tech3285s7 +- https://tech.ebu.ch/publications/tech3285s5 + ## apple_bookmark Apple BookmarkData. @@ -588,6 +613,23 @@ $ fq -d csv -o comma="\t" to_csv file.tsv $ fq -d csv '.[0] as $t | .[1:] | map(with_entries(.key = $t[.key]))' file.csv ``` +## dolby_metadata +Dolby Metadata from `` chunk of RIFF / WAV / Broadcast Wave Format (BWF), +including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. LUFS, True Peak). + +### Examples +Decode Dolby metadata from `` chunk: +``` +$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav +``` + +### References +- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf +- https://github.com/DolbyLaboratories/dbmd-atmos-parser + +### Authors +- [@johnnymarnell](https://johnnymarnell.github.io), original author + ## fit Garmin Flexible and Interoperable Data Transfer. @@ -780,7 +822,6 @@ LevelDB Table. - Zstandard uncompression is not implemented yet. ### Authors - - [@mikez](https://github.com/mikez), original author ### References diff --git a/format/riff/adm.go b/format/riff/adm.go index 21cd0a1e8..399fef0ee 100644 --- a/format/riff/adm.go +++ b/format/riff/adm.go @@ -25,7 +25,6 @@ func chnaDecode(d *decode.D, size int64) { } func axmlDecode(d *decode.D, size int64) { - // fmt.Println("test axml") // TODO(jmarnell): this chunk is all variable xml, so leave as is? d.FieldRawLen("xml", size*8) } diff --git a/format/riff/common.go b/format/riff/common.go index eeffe6222..6915b9f22 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -60,7 +60,7 @@ var chunkIDDescriptions = scalar.StrMapDescription{ "chna": " Chunk, Track UIDs of Audio Definition Model", "axml": " Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements", - "dbmd": "Dolby Metadata", + "dbmd": "Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus]", "ISMP": "SMPTE timecode", "IDIT": "Time and date digitizing commenced", diff --git a/format/riff/dolby.go b/format/riff/dolby.go index af2cb3801..5ec16f95c 100644 --- a/format/riff/dolby.go +++ b/format/riff/dolby.go @@ -54,8 +54,6 @@ func dbmdDecode(d *decode.D, size int64) any { d.FieldRawLen("unknown_segment_raw", int64(segmentSize*8)) } - fmt.Println("bytesRead: ", (bitsLeft-d.BitsLeft())/8, " segment size:", segmentSize) - bytesRemaining := (bitsLeft-d.BitsLeft())/8 - int64(segmentSize) if bytesRemaining < 0 { d.Fatalf("Read too many bytes for segment %d, read %d over, expected %d", segmentID, -bytesRemaining, segmentSize) @@ -63,6 +61,7 @@ func dbmdDecode(d *decode.D, size int64) any { d.FieldValueUint("SKIPPED_BYTES", uint64(bytesRemaining)) d.SeekRel((int64(segmentSize) - bytesRemaining) * 8) } + d.FieldU8("metadata_segment_checksum") }) } @@ -227,7 +226,6 @@ func parseAudioInfo(d *decode.D) { func parseDolbyAtmos(d *decode.D, size uint64) { d.FieldValueStr("metadata_segment_type", "dolby_atmos") - bitsLeft := d.BitsLeft() // d.SeekRel(32 * 8) str := d.FieldUTF8Null("atmos_dbmd_content_creation_preamble") @@ -248,14 +246,10 @@ func parseDolbyAtmos(d *decode.D, size uint64) { d.SeekRel(15 * 8) d.SeekRel(80 * 8) - - fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) - d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) } func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { d.FieldValueStr("metadata_segment_type", "dolby_atmos_supplemental") - bitsLeft := d.BitsLeft() sync := d.FieldU32LE("dasms_sync") d.FieldValueBool("dasms_sync_valid", sync == 0xf8726fbd) @@ -281,8 +275,8 @@ func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { nonZeroBytes = append(nonZeroBytes, fmt.Sprintf("%d", b)) } } - // TODO(jmarnell): I think the +3dB trim settings are here. Would like this as an array of numbers - // at least, instead of CSV string + // TODO(jmarnell): I think the +3dB trim settings are here. + // Would like this at least as an array of numbers, instead of this CSV string d.FieldValueStr("trim_defs", strings.Join(nonZeroBytes, ", ")) i++ @@ -297,7 +291,4 @@ func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { d.FieldValueUint("render_mode", mode) d.FieldValueStr("render_mode_type", binaural[mode]) }) - - fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) - d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) } From b55a837d7f0f4b9eb9d0d67613acc8ecdd03d91f Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Mon, 15 Jul 2024 15:24:37 -0400 Subject: [PATCH 04/13] Attempt to decode DBMD chunk of wav --- format/riff/dbmd.go | 112 ++++++++++++++++++++++++++++++++++++++++++++ format/riff/wav.go | 4 ++ 2 files changed, 116 insertions(+) create mode 100644 format/riff/dbmd.go diff --git a/format/riff/dbmd.go b/format/riff/dbmd.go new file mode 100644 index 000000000..1e073e5dd --- /dev/null +++ b/format/riff/dbmd.go @@ -0,0 +1,112 @@ +package riff + +import ( + "fmt" + + "github.com/wader/fq/pkg/decode" +) + +func dbmdDecode(d *decode.D) any { + // TODO(jmarnell): Read as string (and assert?) + // d.FieldUTF8("chunk_id", 4, d.StrAssert("dbmd")) + d.FieldU32("chunk_id") + + d.FieldU32("chunk_size") + // TODO(jmarnell): Should be string formatted to: "'1.15.2.0' corresponds to 0x010F0200" + d.FieldU32("version") + + d.FieldArray("metadata_segments", func(d *decode.D) { + for { + d.FieldStruct("metadata_segment", func(d *decode.D) { + segmentID := d.FieldU8("metadata_segment_id") + fmt.Println("segmentID: ", segmentID) + if segmentID == 0 { + return + } + + switch segmentID { + case 1: + parseDolbyE(d) + case 3: + parseDolbyDigital(d) + case 7: + parseDolbyDigitalPlus(d) + case 8: + parseAudioInfo(d) + default: + d.FieldRawLen("unknown_segment_raw", int64(d.BitsLeft())) + } + }) + } + }) + + return nil +} + +func parseDolbyE(d *decode.D) { + d.FieldU8("program_config") + d.FieldU8("frame_rate_code") + d.FieldRawLen("e_SMPTE_time_code", 8*8) + d.FieldRawLen("e_reserved", 1*8) + d.FieldRawLen("e_reserved2", 25*8) + d.FieldRawLen("reserved_for_future_use", 80*8) +} + +func parseDolbyDigital(d *decode.D) { + d.FieldU8("ac3_program_id") + d.FieldU8("program_info") + d.FieldU8("datarate_info") + d.FieldRawLen("reserved", 1*8) + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("ac3_langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + d.FieldRawLen("reserved2", 3*8) + d.FieldU8("ac3_compr1") + d.FieldU8("ac3_dynrng1") + d.FieldRawLen("reserved_for_future_use", 21*8) + d.FieldRawLen("program_description_text", 32*8) +} + +func parseDolbyDigitalPlus(d *decode.D) { + d.FieldU8("program_id") + d.FieldU8("program_info") + d.FieldRawLen("ddplus_reserved1", 2*8) + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + d.FieldRawLen("ddplus_reserved2", 1*8) + d.FieldRawLen("ddplus_reserved3", 1*8) + d.FieldRawLen("ddplus_reserved4", 1*8) + d.FieldU8("compr1") + d.FieldU8("dynrng1") + d.FieldRawLen("ddplus_reserved5", 3*8) + d.FieldU8("ddplus_info1") + d.FieldRawLen("ddplus_reserved6", 5*8) + d.FieldU16LE("datarate") + d.FieldRawLen("reserved_for_future_use", 69*8) +} + +func parseAudioInfo(d *decode.D) { + d.FieldU8("program_id") + d.FieldUTF8("audio_origin", 32) + d.FieldU32LE("largest_sample_value") + d.FieldU32LE("largest_sample_value_2") + d.FieldU32LE("largest_true_peak_value") + d.FieldU32LE("largest_true_peak_value_2") + d.FieldU32LE("dialogue_loudness") + d.FieldU32LE("dialogue_loudness_2") + d.FieldU32LE("speech_content") + d.FieldU32LE("speech_content_2") + d.FieldUTF8("last_processed_by", 32) + d.FieldUTF8("last_operation", 32) + d.FieldUTF8("segment_creation_date", 32) + d.FieldUTF8("segment_modified_date", 32) +} diff --git a/format/riff/wav.go b/format/riff/wav.go index 6237089b3..734391a1a 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -158,6 +158,10 @@ func wavDecode(d *decode.D) any { d.FieldRawLen("coding_history", d.BitsLeft()) return false, nil + case "dbmd": + dbmdDecode(d) + return false, nil + default: if riffIsStringChunkID(id) { d.FieldUTF8NullFixedLen("value", int(d.BitsLeft())/8) From 1e0ba551de49e1de25d8f854d61a5e63acf2c66b Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Thu, 15 Aug 2024 23:24:41 -0400 Subject: [PATCH 05/13] Split and implement dolby metadata --- format/riff/adm.go | 31 +++++ format/riff/aiff.go | 2 +- format/riff/avi.go | 2 +- format/riff/common.go | 8 +- format/riff/dbmd.go | 112 ---------------- format/riff/dolby.go | 303 ++++++++++++++++++++++++++++++++++++++++++ format/riff/wav.go | 10 +- format/riff/webp.go | 2 +- 8 files changed, 351 insertions(+), 119 deletions(-) create mode 100644 format/riff/adm.go delete mode 100644 format/riff/dbmd.go create mode 100644 format/riff/dolby.go diff --git a/format/riff/adm.go b/format/riff/adm.go new file mode 100644 index 000000000..21cd0a1e8 --- /dev/null +++ b/format/riff/adm.go @@ -0,0 +1,31 @@ +package riff + +// Audio Definition Model +// https://adm.ebu.io/background/what_is_the_adm.html +// https://tech.ebu.ch/publications/tech3285s7 +// https://tech.ebu.ch/publications/tech3285s5 + +import ( + "github.com/wader/fq/pkg/decode" +) + +func chnaDecode(d *decode.D, size int64) { + d.FieldU16("num_tracks") + d.FieldU16("num_uids") + + audioIdLen := (size - 4) / 40 + d.FieldStructNArray("audio_ids", "audio_id", int64(audioIdLen), func(d *decode.D) { + d.FieldU16("track_index") + d.FieldUTF8("uid", 12) + d.FieldUTF8("track_format_id_reference", 14) + d.FieldUTF8("pack_format_id_reference", 11) + // Skip padding single byte + d.U8() + }) +} + +func axmlDecode(d *decode.D, size int64) { + // fmt.Println("test axml") + // TODO(jmarnell): this chunk is all variable xml, so leave as is? + d.FieldRawLen("xml", size*8) +} diff --git a/format/riff/aiff.go b/format/riff/aiff.go index 6efcd68c7..d02346176 100644 --- a/format/riff/aiff.go +++ b/format/riff/aiff.go @@ -54,7 +54,7 @@ func aiffDecode(d *decode.D) any { } return id, size }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "FORM": riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType)) diff --git a/format/riff/avi.go b/format/riff/avi.go index d35ae3a85..f2c57406b 100644 --- a/format/riff/avi.go +++ b/format/riff/avi.go @@ -238,7 +238,7 @@ func aviDecodeEx(d *decode.D, ai format.AVI_In, extendedChunk bool) { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "RIFF": foundRiffType = d.FieldUTF8("type", 4, d.StrAssert(requiredRiffType)) diff --git a/format/riff/common.go b/format/riff/common.go index f15a821db..eeffe6222 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -19,11 +19,11 @@ func (p path) topData() any { return p[len(p)-1].data } -func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path) (bool, any)) { +func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path, size int64) (bool, any)) { id, size := headFn(d, path) d.FramedFn(size*8, func(d *decode.D) { - hasChildren, data := chunkFn(d, id, path) + hasChildren, data := chunkFn(d, id, path, size) if hasChildren { np := append(path, pathEntry{id: id, data: data}) d.FieldArray("chunks", func(d *decode.D) { @@ -58,6 +58,10 @@ var chunkIDDescriptions = scalar.StrMapDescription{ "dmlh": "Extended AVI header", + "chna": " Chunk, Track UIDs of Audio Definition Model", + "axml": " Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements", + "dbmd": "Dolby Metadata", + "ISMP": "SMPTE timecode", "IDIT": "Time and date digitizing commenced", "IARL": "Archival Location. Indicates where the subject of the file is archived.", diff --git a/format/riff/dbmd.go b/format/riff/dbmd.go deleted file mode 100644 index 1e073e5dd..000000000 --- a/format/riff/dbmd.go +++ /dev/null @@ -1,112 +0,0 @@ -package riff - -import ( - "fmt" - - "github.com/wader/fq/pkg/decode" -) - -func dbmdDecode(d *decode.D) any { - // TODO(jmarnell): Read as string (and assert?) - // d.FieldUTF8("chunk_id", 4, d.StrAssert("dbmd")) - d.FieldU32("chunk_id") - - d.FieldU32("chunk_size") - // TODO(jmarnell): Should be string formatted to: "'1.15.2.0' corresponds to 0x010F0200" - d.FieldU32("version") - - d.FieldArray("metadata_segments", func(d *decode.D) { - for { - d.FieldStruct("metadata_segment", func(d *decode.D) { - segmentID := d.FieldU8("metadata_segment_id") - fmt.Println("segmentID: ", segmentID) - if segmentID == 0 { - return - } - - switch segmentID { - case 1: - parseDolbyE(d) - case 3: - parseDolbyDigital(d) - case 7: - parseDolbyDigitalPlus(d) - case 8: - parseAudioInfo(d) - default: - d.FieldRawLen("unknown_segment_raw", int64(d.BitsLeft())) - } - }) - } - }) - - return nil -} - -func parseDolbyE(d *decode.D) { - d.FieldU8("program_config") - d.FieldU8("frame_rate_code") - d.FieldRawLen("e_SMPTE_time_code", 8*8) - d.FieldRawLen("e_reserved", 1*8) - d.FieldRawLen("e_reserved2", 25*8) - d.FieldRawLen("reserved_for_future_use", 80*8) -} - -func parseDolbyDigital(d *decode.D) { - d.FieldU8("ac3_program_id") - d.FieldU8("program_info") - d.FieldU8("datarate_info") - d.FieldRawLen("reserved", 1*8) - d.FieldU8("surround_config") - d.FieldU8("dialnorm_info") - d.FieldU8("ac3_langcod") - d.FieldU8("audio_prod_info") - d.FieldU8("ext_bsi1_word1") - d.FieldU8("ext_bsi1_word2") - d.FieldU8("ext_bsi2_word1") - d.FieldRawLen("reserved2", 3*8) - d.FieldU8("ac3_compr1") - d.FieldU8("ac3_dynrng1") - d.FieldRawLen("reserved_for_future_use", 21*8) - d.FieldRawLen("program_description_text", 32*8) -} - -func parseDolbyDigitalPlus(d *decode.D) { - d.FieldU8("program_id") - d.FieldU8("program_info") - d.FieldRawLen("ddplus_reserved1", 2*8) - d.FieldU8("surround_config") - d.FieldU8("dialnorm_info") - d.FieldU8("langcod") - d.FieldU8("audio_prod_info") - d.FieldU8("ext_bsi1_word1") - d.FieldU8("ext_bsi1_word2") - d.FieldU8("ext_bsi2_word1") - d.FieldRawLen("ddplus_reserved2", 1*8) - d.FieldRawLen("ddplus_reserved3", 1*8) - d.FieldRawLen("ddplus_reserved4", 1*8) - d.FieldU8("compr1") - d.FieldU8("dynrng1") - d.FieldRawLen("ddplus_reserved5", 3*8) - d.FieldU8("ddplus_info1") - d.FieldRawLen("ddplus_reserved6", 5*8) - d.FieldU16LE("datarate") - d.FieldRawLen("reserved_for_future_use", 69*8) -} - -func parseAudioInfo(d *decode.D) { - d.FieldU8("program_id") - d.FieldUTF8("audio_origin", 32) - d.FieldU32LE("largest_sample_value") - d.FieldU32LE("largest_sample_value_2") - d.FieldU32LE("largest_true_peak_value") - d.FieldU32LE("largest_true_peak_value_2") - d.FieldU32LE("dialogue_loudness") - d.FieldU32LE("dialogue_loudness_2") - d.FieldU32LE("speech_content") - d.FieldU32LE("speech_content_2") - d.FieldUTF8("last_processed_by", 32) - d.FieldUTF8("last_operation", 32) - d.FieldUTF8("segment_creation_date", 32) - d.FieldUTF8("segment_modified_date", 32) -} diff --git a/format/riff/dolby.go b/format/riff/dolby.go new file mode 100644 index 000000000..af2cb3801 --- /dev/null +++ b/format/riff/dolby.go @@ -0,0 +1,303 @@ +package riff + +// Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus] +// https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf +// https://github.com/DolbyLaboratories/dbmd-atmos-parser + +import ( + "fmt" + "strings" + + "github.com/wader/fq/pkg/decode" + "github.com/wader/fq/pkg/scalar" +) + +func dbmdDecode(d *decode.D, size int64) any { + version := d.U32() + major := (version >> 24) & 0xFF + minor := (version >> 16) & 0xFF + patch := (version >> 8) & 0xFF + build := version & 0xFF + d.FieldValueStr("version", fmt.Sprintf("%d.%d.%d.%d", major, minor, patch, build)) + + d.FieldArray("metadata_segments", func(d *decode.D) { + for { + d.FieldStruct("metadata_segment", func(d *decode.D) { + segmentID := d.FieldU8("metadata_segment_id") + + // TODO(jmarnell): I think I need a loop until, but not creating these empty segments + // spec says we're done with 0 ID, so I'd like to not make the empty segment(s) + if segmentID == 0 { + if d.BitsLeft() > 0 { + d.SeekRel(d.BitsLeft() * 8) + } + return + } + + segmentSize := d.FieldU16("metadata_segment_size") + bitsLeft := d.BitsLeft() + + switch segmentID { + case 1: + parseDolbyE(d) + case 3: + parseDolbyDigital(d) + case 7: + parseDolbyDigitalPlus(d) + case 8: + parseAudioInfo(d) + case 9: + parseDolbyAtmos(d, segmentSize) + case 10: + parseDolbyAtmosSupplemental(d, segmentSize) + default: + d.FieldRawLen("unknown_segment_raw", int64(segmentSize*8)) + } + + fmt.Println("bytesRead: ", (bitsLeft-d.BitsLeft())/8, " segment size:", segmentSize) + + bytesRemaining := (bitsLeft-d.BitsLeft())/8 - int64(segmentSize) + if bytesRemaining < 0 { + d.Fatalf("Read too many bytes for segment %d, read %d over, expected %d", segmentID, -bytesRemaining, segmentSize) + } else if bytesRemaining > 0 { + d.FieldValueUint("SKIPPED_BYTES", uint64(bytesRemaining)) + d.SeekRel((int64(segmentSize) - bytesRemaining) * 8) + } + d.FieldU8("metadata_segment_checksum") + }) + } + }) + + return nil +} + +var compressionDesc = scalar.UintMapDescription{ + 0: "none", + 1: "Film, Standard", + 2: "Film, Light", + 3: "Music, Standard", + 4: "Music, Light", + 5: "Speech", + // TODO(jmarnell): Can I handle rest is "Reserved"? +} + +// TODO(jmarnell): Better way to handle "Reserved"? +func mapWithReserved(m map[uint64]string, key uint64) string { + if val, ok := m[key]; ok { + return val + } + return "Reserved" +} + +var bitstreamMode = scalar.UintMapDescription{ + 0b000: "main audio service: complete main (CM)", + 0b001: "main audio service: music and effects (ME)", + 0b010: "associated service: visually impaired (VI)", + 0b011: "associated service: hearing impaired (HI)", + 0b100: "associated service: dialogue (D)", + 0b101: "associated service: commentary (C)", + 0b110: "associated service: emergency (E)", + 0b111: "associated service: voice over (VO)", + + 0b1000: "associated service: karaoke (K)", +} + +var binaural = scalar.UintMapDescription{ + 0: "bypass", + 1: "near", + 2: "far", + 3: "mid", + 4: "not indicated", +} + +var warpMode = scalar.UintMapDescription{ + 0: "normal", + 1: "warping", + 2: "downmix Dolby Pro Logic IIx", + 3: "downmix LoRo", + 4: "not indicated (Default warping will be applied.)", +} + +var trimConfigName = scalar.UintMapDescription{ + 0: "2.0", + 1: "5.1", + 2: "7.1", + 3: "2.1.2", + 4: "5.1.2", + 5: "7.1.2", + 6: "2.1.4", + 7: "5.1.4", + 8: "7.1.4", +} + +var trimType = scalar.UintMapDescription{ + 0: "manual", + 1: "automatic", +} + +func parseDolbyE(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "dolby_e") + + d.FieldU8("program_config") + d.FieldU8("frame_rate_code") + d.FieldRawLen("e_SMPTE_time_code", 8*8) + d.FieldRawLen("e_reserved", 1*8) + d.FieldRawLen("e_reserved2", 25*8) + d.FieldRawLen("reserved_for_future_use", 80*8) +} + +func parseDolbyDigital(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "dolby_digital") + + d.FieldU8("ac3_program_id") + d.FieldU8("program_info") + d.FieldU8("datarate_info") + d.FieldRawLen("reserved", 1*8) + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("ac3_langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + d.FieldRawLen("reserved2", 3*8) + d.FieldU8("ac3_compr1") + d.FieldU8("ac3_dynrng1") + d.FieldRawLen("reserved_for_future_use", 21*8) + d.FieldRawLen("program_description_text", 32*8) +} + +func parseDolbyDigitalPlus(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "dolby_digital_plus") + + d.FieldU8("program_id") + programInfo := d.FieldU8("program_info") + lfeon := programInfo & 0b1_000_000 + bsmod := programInfo & 0b0_111_000 + acmod := programInfo & 0b0_000_111 + d.FieldValueBool("lfe_on", lfeon != 0) + if bsmod == 0b111 && acmod != 0b001 { + bsmod = 0b1000 + } + d.FieldValueStr("bitstream_mode", bitstreamMode[bsmod]) + + d.FieldU16LE("ddplus_reserved_a") + + d.FieldU8("surround_config") + d.FieldU8("dialnorm_info") + d.FieldU8("langcod") + d.FieldU8("audio_prod_info") + d.FieldU8("ext_bsi1_word1") + d.FieldU8("ext_bsi1_word2") + d.FieldU8("ext_bsi2_word1") + + d.FieldU24LE("ddplus_reserved_b") + + d.FieldValueStr("compr1_type", mapWithReserved(compressionDesc, d.FieldU8("compr1"))) + d.FieldValueStr("dynrng1_type", mapWithReserved(compressionDesc, d.FieldU8("dynrng1"))) + + d.FieldU24LE("ddplus_reserved_c") + + d.FieldU8("ddplus_info1") + + d.FieldU40LE("ddplus_reserved_d") + + d.FieldU16LE("datarate") + d.FieldRawLen("reserved_for_future_use", 69*8) +} + +func parseAudioInfo(d *decode.D) { + d.FieldValueStr("metadata_segment_type", "audio_info") + + d.FieldU8("program_id") + d.FieldUTF8("audio_origin", 32) + d.FieldU32LE("largest_sample_value") + d.FieldU32LE("largest_sample_value_2") + d.FieldU32LE("largest_true_peak_value") + d.FieldU32LE("largest_true_peak_value_2") + d.FieldU32LE("dialogue_loudness") + d.FieldU32LE("dialogue_loudness_2") + d.FieldU32LE("speech_content") + d.FieldU32LE("speech_content_2") + d.FieldUTF8("last_processed_by", 32) + d.FieldUTF8("last_operation", 32) + d.FieldUTF8("segment_creation_date", 32) + d.FieldUTF8("segment_modified_date", 32) +} + +func parseDolbyAtmos(d *decode.D, size uint64) { + d.FieldValueStr("metadata_segment_type", "dolby_atmos") + bitsLeft := d.BitsLeft() + + // d.SeekRel(32 * 8) + str := d.FieldUTF8Null("atmos_dbmd_content_creation_preamble") + d.SeekRel(int64(32-len(str)-1) * 8) + + str = d.FieldUTF8Null("atmos_dbmd_content_creation_tool") + d.SeekRel(int64(64-len(str)-1) * 8) + + major := d.U8() + minor := d.U8() + micro := d.U8() + d.FieldValueStr("version", fmt.Sprintf("%d.%d.%d", major, minor, micro)) + d.SeekRel(53 * 8) + + warpBedReserved := d.U8() + d.FieldValueUint("warp_mode", warpBedReserved&0x7) + d.FieldValueStr("warp_mode_type", warpMode[warpBedReserved&0x7]) + + d.SeekRel(15 * 8) + d.SeekRel(80 * 8) + + fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) + d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) +} + +func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { + d.FieldValueStr("metadata_segment_type", "dolby_atmos_supplemental") + bitsLeft := d.BitsLeft() + + sync := d.FieldU32LE("dasms_sync") + d.FieldValueBool("dasms_sync_valid", sync == 0xf8726fbd) + + objectCount := int64(d.FieldU16LE("object_count")) + d.FieldU8LE("reserved") + + i := 0 + d.FieldStructNArray("trim_configs", "trim_config", 9, func(d *decode.D) { + autoTrimReserved := d.FieldU8LE("auto_trim_reserved") + autoTrim := autoTrimReserved & 0x01 + d.FieldValueBool("auto_trim", autoTrim == 1) + d.FieldValueStr("trim_type", trimType[autoTrim]) + d.FieldValueStr("trim_config_name", trimConfigName[uint64(i)]) + + //d.SeekRel(14 * 8) + // d.FieldUTF8("raw", 14) + str := d.UTF8(14) + bytes := []byte(str) + var nonZeroBytes []string + for _, b := range bytes { + if b != 0 { + nonZeroBytes = append(nonZeroBytes, fmt.Sprintf("%d", b)) + } + } + // TODO(jmarnell): I think the +3dB trim settings are here. Would like this as an array of numbers + // at least, instead of CSV string + d.FieldValueStr("trim_defs", strings.Join(nonZeroBytes, ", ")) + + i++ + }) + + d.FieldStructNArray("objects", "object", objectCount, func(d *decode.D) { + d.FieldU8LE("object_value") + }) + + d.FieldStructNArray("binaural_render_modes", "binaural_render_mode", objectCount, func(d *decode.D) { + mode := d.U8LE() & 0x7 + d.FieldValueUint("render_mode", mode) + d.FieldValueStr("render_mode_type", binaural[mode]) + }) + + fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) + d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) +} diff --git a/format/riff/wav.go b/format/riff/wav.go index 734391a1a..b1a0eecee 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -76,7 +76,7 @@ func wavDecode(d *decode.D) any { return id, size }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(wavRiffType)) @@ -158,8 +158,14 @@ func wavDecode(d *decode.D) any { d.FieldRawLen("coding_history", d.BitsLeft()) return false, nil + case "chna": + chnaDecode(d, size) + return false, nil + case "axml": + axmlDecode(d, size) + return false, nil case "dbmd": - dbmdDecode(d) + dbmdDecode(d, size) return false, nil default: diff --git a/format/riff/webp.go b/format/riff/webp.go index ebc0bbc59..de69d6238 100644 --- a/format/riff/webp.go +++ b/format/riff/webp.go @@ -44,7 +44,7 @@ func webpDecode(d *decode.D) any { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path) (bool, any) { + func(d *decode.D, id string, path path, size int64) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(webpRiffType)) From 83abddab6583e26a78c870c6001f9f41385c0b6a Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Thu, 15 Aug 2024 23:58:19 -0400 Subject: [PATCH 06/13] Docs and minor cleanup --- README.md | 4 ++++ doc/formats.md | 43 ++++++++++++++++++++++++++++++++++++++++++- format/riff/adm.go | 1 - format/riff/common.go | 2 +- format/riff/dolby.go | 15 +++------------ 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5ab49fc62..2957a56ff 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ It was originally designed to query, inspect and debug media codecs and containe [aac_frame](doc/formats.md#aac_frame), adts, adts_frame, +[adm](doc/formats.md#adm), aiff, amf0, apev2, @@ -53,6 +54,7 @@ avc_sei, avc_sps, [avi](doc/formats.md#avi), [avro_ocf](doc/formats.md#avro_ocf), +[axml](doc/formats.md#adm), [bencode](doc/formats.md#bencode), bitcoin_blkdat, [bitcoin_block](doc/formats.md#bitcoin_block), @@ -66,7 +68,9 @@ bsd_loopback_frame, bzip2, [caff](doc/formats.md#caff), [cbor](doc/formats.md#cbor), +[chna](doc/formats.md#adm), [csv](doc/formats.md#csv), +[dolby_metadata](doc/formats.md#dolby_metadata), dns, dns_tcp, elf, diff --git a/doc/formats.md b/doc/formats.md index 80b7aea4b..ab6e3c03e 100644 --- a/doc/formats.md +++ b/doc/formats.md @@ -7,6 +7,7 @@ |[`aac_frame`](#aac_frame) |Advanced Audio Coding frame || |`adts` |Audio Data Transport Stream |`adts_frame`| |`adts_frame` |Audio Data Transport Stream frame |`aac_frame`| +|[`adm`](#adm) |Audio Definition Model |`riff`| |`aiff` |Audio Interchange File Format || |`amf0` |Action Message Format 0 || |`apev2` |APEv2 metadata tag |`image`| @@ -25,6 +26,7 @@ |`avc_sps` |H.264/AVC Sequence Parameter Set || |[`avi`](#avi) |Audio Video Interleaved |`avc_au` `hevc_au` `mp3_frame` `flac_frame`| |[`avro_ocf`](#avro_ocf) |Avro object container file || +|[`axml`](#adm) |Audio Definition Model  Chunk |`riff`| |[`bencode`](#bencode) |BitTorrent bencoding || |`bitcoin_blkdat` |Bitcoin blk.dat |`bitcoin_block`| |[`bitcoin_block`](#bitcoin_block) |Bitcoin block |`bitcoin_transaction`| @@ -38,7 +40,9 @@ |`bzip2` |bzip2 compression |`probe`| |[`caff`](#caff) |Live2D Cubism archive |`probe`| |[`cbor`](#cbor) |Concise Binary Object Representation || +|[`chna`](#adm) |Audio Definition Model  Chunk |`riff`| |[`csv`](#csv) |Comma separated values || +|[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Digital Plus) |`riff`| |`dns` |DNS packet || |`dns_tcp` |DNS packet (TCP) || |`elf` |Executable and Linkable Format || @@ -179,6 +183,27 @@ Decode value as aac_frame ... | aac_frame({object_type:1}) ``` +## adm +[Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) including 3D Audio. + +RIFF / WAV / Broadcast Wave Format (BWF) chunks: +- `` Chunk, Track UIDs of Audio Definition Model +- `` Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements + +### Examples +Decode ADM configuration from `` and `` chunks: +``` +$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' bwf.wav +``` + +### Authors +- [@johnnymarnell](https://johnnymarnell.github.io), original author + +### References +- https://adm.ebu.io/background/what_is_the_adm.html +- https://tech.ebu.ch/publications/tech3285s7 +- https://tech.ebu.ch/publications/tech3285s5 + ## apple_bookmark Apple BookmarkData. @@ -590,6 +615,23 @@ $ fq -d csv -o comma="\t" to_csv file.tsv $ fq -d csv '.[0] as $t | .[1:] | map(with_entries(.key = $t[.key]))' file.csv ``` +## dolby_metadata +Dolby Metadata from `` chunk of RIFF / WAV / Broadcast Wave Format (BWF), +including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. LUFS, True Peak). + +### Examples +Decode Dolby metadata from `` chunk: +``` +$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav +``` + +### References +- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf +- https://github.com/DolbyLaboratories/dbmd-atmos-parser + +### Authors +- [@johnnymarnell](https://johnnymarnell.github.io), original author + ## fit Garmin Flexible and Interoperable Data Transfer. @@ -782,7 +824,6 @@ LevelDB Table. - Zstandard uncompression is not implemented yet. ### Authors - - [@mikez](https://github.com/mikez), original author ### References diff --git a/format/riff/adm.go b/format/riff/adm.go index 21cd0a1e8..399fef0ee 100644 --- a/format/riff/adm.go +++ b/format/riff/adm.go @@ -25,7 +25,6 @@ func chnaDecode(d *decode.D, size int64) { } func axmlDecode(d *decode.D, size int64) { - // fmt.Println("test axml") // TODO(jmarnell): this chunk is all variable xml, so leave as is? d.FieldRawLen("xml", size*8) } diff --git a/format/riff/common.go b/format/riff/common.go index eeffe6222..6915b9f22 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -60,7 +60,7 @@ var chunkIDDescriptions = scalar.StrMapDescription{ "chna": " Chunk, Track UIDs of Audio Definition Model", "axml": " Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements", - "dbmd": "Dolby Metadata", + "dbmd": "Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus]", "ISMP": "SMPTE timecode", "IDIT": "Time and date digitizing commenced", diff --git a/format/riff/dolby.go b/format/riff/dolby.go index af2cb3801..5ec16f95c 100644 --- a/format/riff/dolby.go +++ b/format/riff/dolby.go @@ -54,8 +54,6 @@ func dbmdDecode(d *decode.D, size int64) any { d.FieldRawLen("unknown_segment_raw", int64(segmentSize*8)) } - fmt.Println("bytesRead: ", (bitsLeft-d.BitsLeft())/8, " segment size:", segmentSize) - bytesRemaining := (bitsLeft-d.BitsLeft())/8 - int64(segmentSize) if bytesRemaining < 0 { d.Fatalf("Read too many bytes for segment %d, read %d over, expected %d", segmentID, -bytesRemaining, segmentSize) @@ -63,6 +61,7 @@ func dbmdDecode(d *decode.D, size int64) any { d.FieldValueUint("SKIPPED_BYTES", uint64(bytesRemaining)) d.SeekRel((int64(segmentSize) - bytesRemaining) * 8) } + d.FieldU8("metadata_segment_checksum") }) } @@ -227,7 +226,6 @@ func parseAudioInfo(d *decode.D) { func parseDolbyAtmos(d *decode.D, size uint64) { d.FieldValueStr("metadata_segment_type", "dolby_atmos") - bitsLeft := d.BitsLeft() // d.SeekRel(32 * 8) str := d.FieldUTF8Null("atmos_dbmd_content_creation_preamble") @@ -248,14 +246,10 @@ func parseDolbyAtmos(d *decode.D, size uint64) { d.SeekRel(15 * 8) d.SeekRel(80 * 8) - - fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) - d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) } func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { d.FieldValueStr("metadata_segment_type", "dolby_atmos_supplemental") - bitsLeft := d.BitsLeft() sync := d.FieldU32LE("dasms_sync") d.FieldValueBool("dasms_sync_valid", sync == 0xf8726fbd) @@ -281,8 +275,8 @@ func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { nonZeroBytes = append(nonZeroBytes, fmt.Sprintf("%d", b)) } } - // TODO(jmarnell): I think the +3dB trim settings are here. Would like this as an array of numbers - // at least, instead of CSV string + // TODO(jmarnell): I think the +3dB trim settings are here. + // Would like this at least as an array of numbers, instead of this CSV string d.FieldValueStr("trim_defs", strings.Join(nonZeroBytes, ", ")) i++ @@ -297,7 +291,4 @@ func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { d.FieldValueUint("render_mode", mode) d.FieldValueStr("render_mode_type", binaural[mode]) }) - - fmt.Println("test", int64(size)*8-(bitsLeft-d.BitsLeft())) - d.SeekRel(int64(size)*8 - (bitsLeft - d.BitsLeft())) } From f2a6987077145ff3fc3b4d56118687ad96a13c11 Mon Sep 17 00:00:00 2001 From: Mattias Wadman Date: Tue, 20 Aug 2024 15:05:07 +0200 Subject: [PATCH 07/13] Various fix and cleanup --- format/riff/adm.go | 5 +- format/riff/common.go | 4 +- format/riff/dolby.go | 217 ++++++++++++++++++++---------------------- format/riff/wav.go | 2 +- 4 files changed, 110 insertions(+), 118 deletions(-) diff --git a/format/riff/adm.go b/format/riff/adm.go index 399fef0ee..57b4193f0 100644 --- a/format/riff/adm.go +++ b/format/riff/adm.go @@ -19,12 +19,11 @@ func chnaDecode(d *decode.D, size int64) { d.FieldUTF8("uid", 12) d.FieldUTF8("track_format_id_reference", 14) d.FieldUTF8("pack_format_id_reference", 11) - // Skip padding single byte - d.U8() + d.FieldRawLen("padding", 8) }) } func axmlDecode(d *decode.D, size int64) { // TODO(jmarnell): this chunk is all variable xml, so leave as is? - d.FieldRawLen("xml", size*8) + d.FieldUTF8("xml", int(size)) } diff --git a/format/riff/common.go b/format/riff/common.go index 6915b9f22..5415390a3 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -58,8 +58,8 @@ var chunkIDDescriptions = scalar.StrMapDescription{ "dmlh": "Extended AVI header", - "chna": " Chunk, Track UIDs of Audio Definition Model", - "axml": " Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements", + "chna": "Track UIDs of Audio Definition Model", + "axml": "Audio Definition Model ambisonics and elements", "dbmd": "Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus]", "ISMP": "SMPTE timecode", diff --git a/format/riff/dolby.go b/format/riff/dolby.go index 5ec16f95c..2f27e5372 100644 --- a/format/riff/dolby.go +++ b/format/riff/dolby.go @@ -5,64 +5,48 @@ package riff // https://github.com/DolbyLaboratories/dbmd-atmos-parser import ( - "fmt" - "strings" - "github.com/wader/fq/pkg/decode" "github.com/wader/fq/pkg/scalar" ) -func dbmdDecode(d *decode.D, size int64) any { - version := d.U32() - major := (version >> 24) & 0xFF - minor := (version >> 16) & 0xFF - patch := (version >> 8) & 0xFF - build := version & 0xFF - d.FieldValueStr("version", fmt.Sprintf("%d.%d.%d.%d", major, minor, patch, build)) +func dbmdDecode(d *decode.D) any { + d.FieldStruct("version", func(d *decode.D) { + d.FieldU8("major") + d.FieldU8("minor") + d.FieldU8("patch") + d.FieldU8("build") + }) d.FieldArray("metadata_segments", func(d *decode.D) { - for { + seenEnd := false + for !seenEnd { d.FieldStruct("metadata_segment", func(d *decode.D) { - segmentID := d.FieldU8("metadata_segment_id") - - // TODO(jmarnell): I think I need a loop until, but not creating these empty segments - // spec says we're done with 0 ID, so I'd like to not make the empty segment(s) + segmentID := d.FieldU8("id", metadataSegmentTypeMap) if segmentID == 0 { - if d.BitsLeft() > 0 { - d.SeekRel(d.BitsLeft() * 8) - } + seenEnd = true return } - segmentSize := d.FieldU16("metadata_segment_size") - bitsLeft := d.BitsLeft() + segmentSize := d.FieldU16("size") switch segmentID { - case 1: + case metadataSegmentTypeDolyEMetadata: parseDolbyE(d) - case 3: + case metadataSegmentTypeDolyEDigitaletadata: parseDolbyDigital(d) - case 7: + case metadataSegmentTypeDolyDigitalPlusMetadata: parseDolbyDigitalPlus(d) - case 8: + case metadataSegmentTypeAudioInfo: parseAudioInfo(d) - case 9: - parseDolbyAtmos(d, segmentSize) - case 10: - parseDolbyAtmosSupplemental(d, segmentSize) + case metadataSegmentTypeDolyAtmos: + parseDolbyAtmos(d) + case metadataSegmentTypeDolbyAtmosSupplemental: + parseDolbyAtmosSupplemental(d) default: - d.FieldRawLen("unknown_segment_raw", int64(segmentSize*8)) + d.FieldRawLen("unknown", int64(segmentSize*8)) } - bytesRemaining := (bitsLeft-d.BitsLeft())/8 - int64(segmentSize) - if bytesRemaining < 0 { - d.Fatalf("Read too many bytes for segment %d, read %d over, expected %d", segmentID, -bytesRemaining, segmentSize) - } else if bytesRemaining > 0 { - d.FieldValueUint("SKIPPED_BYTES", uint64(bytesRemaining)) - d.SeekRel((int64(segmentSize) - bytesRemaining) * 8) - } - - d.FieldU8("metadata_segment_checksum") + d.FieldU8("checksum", scalar.UintHex) }) } }) @@ -70,22 +54,13 @@ func dbmdDecode(d *decode.D, size int64) any { return nil } -var compressionDesc = scalar.UintMapDescription{ +var compressionDescMap = scalar.UintMapSymStr{ 0: "none", - 1: "Film, Standard", - 2: "Film, Light", - 3: "Music, Standard", - 4: "Music, Light", - 5: "Speech", - // TODO(jmarnell): Can I handle rest is "Reserved"? -} - -// TODO(jmarnell): Better way to handle "Reserved"? -func mapWithReserved(m map[uint64]string, key uint64) string { - if val, ok := m[key]; ok { - return val - } - return "Reserved" + 1: "film_standard", + 2: "film_light", + 3: "music_standard", + 4: "music_light", + 5: "speech", } var bitstreamMode = scalar.UintMapDescription{ @@ -101,20 +76,20 @@ var bitstreamMode = scalar.UintMapDescription{ 0b1000: "associated service: karaoke (K)", } -var binaural = scalar.UintMapDescription{ +var binauralRenderModeMap = scalar.UintMapSymStr{ 0: "bypass", 1: "near", 2: "far", 3: "mid", - 4: "not indicated", + 4: "not_indicated", } -var warpMode = scalar.UintMapDescription{ - 0: "normal", - 1: "warping", - 2: "downmix Dolby Pro Logic IIx", - 3: "downmix LoRo", - 4: "not indicated (Default warping will be applied.)", +var warpModeMap = scalar.UintMap{ + 0: {Sym: "normal"}, + 1: {Sym: "warping"}, + 2: {Sym: "downmix_dolby_pro_logic_iix"}, + 3: {Sym: "downmix_loro"}, + 4: {Sym: "not_indicated", Description: "Default warping will be applied"}, } var trimConfigName = scalar.UintMapDescription{ @@ -134,9 +109,35 @@ var trimType = scalar.UintMapDescription{ 1: "automatic", } -func parseDolbyE(d *decode.D) { - d.FieldValueStr("metadata_segment_type", "dolby_e") +const ( + metadataSegmentTypeEnd = 0 + metadataSegmentTypeDolyEMetadata = 1 + metadataSegmentTypeDolyReserved2 = 2 + metadataSegmentTypeDolyEDigitaletadata = 3 + metadataSegmentTypeDolyReserved4 = 4 + metadataSegmentTypeDolyReserved5 = 5 + metadataSegmentTypeDolyReserved6 = 6 + metadataSegmentTypeDolyDigitalPlusMetadata = 7 + metadataSegmentTypeAudioInfo = 8 + metadataSegmentTypeDolyAtmos = 9 + metadataSegmentTypeDolbyAtmosSupplemental = 10 +) + +var metadataSegmentTypeMap = scalar.UintMapSymStr{ + metadataSegmentTypeEnd: "end", + metadataSegmentTypeDolyEMetadata: "doly_e_metadata", + metadataSegmentTypeDolyReserved2: "reserved2", + metadataSegmentTypeDolyEDigitaletadata: "doly_e_digitale_tadata", + metadataSegmentTypeDolyReserved4: "reserved4", + metadataSegmentTypeDolyReserved5: "reserved5", + metadataSegmentTypeDolyReserved6: "reserved6", + metadataSegmentTypeDolyDigitalPlusMetadata: "doly_digital_plus_metadata", + metadataSegmentTypeAudioInfo: "audio_info", + metadataSegmentTypeDolyAtmos: "doly_atmos", + metadataSegmentTypeDolbyAtmosSupplemental: "dolby_atmos_supplemental", +} +func parseDolbyE(d *decode.D) { d.FieldU8("program_config") d.FieldU8("frame_rate_code") d.FieldRawLen("e_SMPTE_time_code", 8*8) @@ -146,8 +147,6 @@ func parseDolbyE(d *decode.D) { } func parseDolbyDigital(d *decode.D) { - d.FieldValueStr("metadata_segment_type", "dolby_digital") - d.FieldU8("ac3_program_id") d.FieldU8("program_info") d.FieldU8("datarate_info") @@ -167,9 +166,8 @@ func parseDolbyDigital(d *decode.D) { } func parseDolbyDigitalPlus(d *decode.D) { - d.FieldValueStr("metadata_segment_type", "dolby_digital_plus") - d.FieldU8("program_id") + // TODO: make struct and read U1(?) U1 (lfeon) U3 (bsmod) U3(acmod) fields? programInfo := d.FieldU8("program_info") lfeon := programInfo & 0b1_000_000 bsmod := programInfo & 0b0_111_000 @@ -192,8 +190,8 @@ func parseDolbyDigitalPlus(d *decode.D) { d.FieldU24LE("ddplus_reserved_b") - d.FieldValueStr("compr1_type", mapWithReserved(compressionDesc, d.FieldU8("compr1"))) - d.FieldValueStr("dynrng1_type", mapWithReserved(compressionDesc, d.FieldU8("dynrng1"))) + d.FieldU8("compr1", scalar.UintSym("reserved"), compressionDescMap) + d.FieldU8("dynrng1", scalar.UintSym("reserved"), compressionDescMap) d.FieldU24LE("ddplus_reserved_c") @@ -206,8 +204,6 @@ func parseDolbyDigitalPlus(d *decode.D) { } func parseAudioInfo(d *decode.D) { - d.FieldValueStr("metadata_segment_type", "audio_info") - d.FieldU8("program_id") d.FieldUTF8("audio_origin", 32) d.FieldU32LE("largest_sample_value") @@ -224,36 +220,30 @@ func parseAudioInfo(d *decode.D) { d.FieldUTF8("segment_modified_date", 32) } -func parseDolbyAtmos(d *decode.D, size uint64) { - d.FieldValueStr("metadata_segment_type", "dolby_atmos") - - // d.SeekRel(32 * 8) - str := d.FieldUTF8Null("atmos_dbmd_content_creation_preamble") - d.SeekRel(int64(32-len(str)-1) * 8) - - str = d.FieldUTF8Null("atmos_dbmd_content_creation_tool") - d.SeekRel(int64(64-len(str)-1) * 8) +func parseDolbyAtmos(d *decode.D) { + // TODO: both these are fixed size null terminated strings? + d.FieldUTF8NullFixedLen("atmos_dbmd_content_creation_preamble", 32) + d.FieldUTF8NullFixedLen("atmos_dbmd_content_creation_tool", 64) + d.FieldStruct("version", func(d *decode.D) { + d.FieldU8("major") + d.FieldU8("minor") + d.FieldU8("micro") + }) - major := d.U8() - minor := d.U8() - micro := d.U8() - d.FieldValueStr("version", fmt.Sprintf("%d.%d.%d", major, minor, micro)) - d.SeekRel(53 * 8) + // TODO: what is this? + d.FieldRawLen("unknown0", 53*8) - warpBedReserved := d.U8() - d.FieldValueUint("warp_mode", warpBedReserved&0x7) - d.FieldValueStr("warp_mode_type", warpMode[warpBedReserved&0x7]) + d.FieldU8("warp_mode", warpModeMap) - d.SeekRel(15 * 8) - d.SeekRel(80 * 8) + // TODO: what is this? + d.FieldRawLen("unknown1", 15*8) + d.FieldRawLen("unknown2", 80*8) } -func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { - d.FieldValueStr("metadata_segment_type", "dolby_atmos_supplemental") - - sync := d.FieldU32LE("dasms_sync") - d.FieldValueBool("dasms_sync_valid", sync == 0xf8726fbd) +func parseDolbyAtmosSupplemental(d *decode.D) { + d.FieldU32LE("dasms_sync", d.UintAssert(0xf8726fbd), scalar.UintHex) + // TODO: wav.go sets LE default i think? objectCount := int64(d.FieldU16LE("object_count")) d.FieldU8LE("reserved") @@ -265,30 +255,33 @@ func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { d.FieldValueStr("trim_type", trimType[autoTrim]) d.FieldValueStr("trim_config_name", trimConfigName[uint64(i)]) - //d.SeekRel(14 * 8) - // d.FieldUTF8("raw", 14) - str := d.UTF8(14) - bytes := []byte(str) - var nonZeroBytes []string - for _, b := range bytes { - if b != 0 { - nonZeroBytes = append(nonZeroBytes, fmt.Sprintf("%d", b)) - } - } + // TODO: this is null separted list of def strings? + d.FieldUTF8("raw", 14) + // str := d.UTF8(14) + // bytes := []byte(str) + // var nonZeroBytes []string + // for _, b := range bytes { + // if b != 0 { + // nonZeroBytes = append(nonZeroBytes, fmt.Sprintf("%d", b)) + // } + // } // TODO(jmarnell): I think the +3dB trim settings are here. // Would like this at least as an array of numbers, instead of this CSV string - d.FieldValueStr("trim_defs", strings.Join(nonZeroBytes, ", ")) + // d.FieldValueStr("trim_defs", strings.Join(nonZeroBytes, ", ")) i++ }) - d.FieldStructNArray("objects", "object", objectCount, func(d *decode.D) { - d.FieldU8LE("object_value") + d.FieldArray("objects", func(d *decode.D) { + for i := int64(0); i < objectCount; i++ { + d.FieldU8("object_value") + } }) - d.FieldStructNArray("binaural_render_modes", "binaural_render_mode", objectCount, func(d *decode.D) { - mode := d.U8LE() & 0x7 - d.FieldValueUint("render_mode", mode) - d.FieldValueStr("render_mode_type", binaural[mode]) + d.FieldArray("binaural_render_modes", func(d *decode.D) { + // TODO: 0x7 mask needed? + for i := int64(0); i < objectCount; i++ { + d.FieldU8("render_mode", scalar.UintActualFn(func(a uint64) uint64 { return a & 0x7 }), binauralRenderModeMap) + } }) } diff --git a/format/riff/wav.go b/format/riff/wav.go index b1a0eecee..98329d9ff 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -165,7 +165,7 @@ func wavDecode(d *decode.D) any { axmlDecode(d, size) return false, nil case "dbmd": - dbmdDecode(d, size) + dbmdDecode(d) return false, nil default: From 04539fe3dc7a1e737649c5cade39fe53be123563 Mon Sep 17 00:00:00 2001 From: Mattias Wadman Date: Wed, 21 Aug 2024 09:27:10 +0200 Subject: [PATCH 08/13] dolby_metadata: Add as own format and more cleanup --- README.md | 5 +- doc/formats.md | 44 +++---- format/format.go | 1 + format/riff/adm.go | 25 ++-- format/riff/aiff.go | 2 +- format/riff/avi.go | 2 +- format/riff/common.go | 4 +- format/riff/{dolby.go => dolby_metadata.go} | 126 +++++++++++--------- format/riff/dolby_metadata.md | 22 ++++ format/riff/wav.go | 10 +- format/riff/webp.go | 2 +- 11 files changed, 134 insertions(+), 109 deletions(-) rename format/riff/{dolby.go => dolby_metadata.go} (68%) create mode 100644 format/riff/dolby_metadata.md diff --git a/README.md b/README.md index 2957a56ff..4277eec52 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ It was originally designed to query, inspect and debug media codecs and containe [aac_frame](doc/formats.md#aac_frame), adts, adts_frame, -[adm](doc/formats.md#adm), aiff, amf0, apev2, @@ -54,7 +53,6 @@ avc_sei, avc_sps, [avi](doc/formats.md#avi), [avro_ocf](doc/formats.md#avro_ocf), -[axml](doc/formats.md#adm), [bencode](doc/formats.md#bencode), bitcoin_blkdat, [bitcoin_block](doc/formats.md#bitcoin_block), @@ -68,11 +66,10 @@ bsd_loopback_frame, bzip2, [caff](doc/formats.md#caff), [cbor](doc/formats.md#cbor), -[chna](doc/formats.md#adm), [csv](doc/formats.md#csv), -[dolby_metadata](doc/formats.md#dolby_metadata), dns, dns_tcp, +[dolby_metadata](doc/formats.md#dolby_metadata), elf, ether8023_frame, exif, diff --git a/doc/formats.md b/doc/formats.md index ab6e3c03e..34587fab7 100644 --- a/doc/formats.md +++ b/doc/formats.md @@ -7,7 +7,6 @@ |[`aac_frame`](#aac_frame) |Advanced Audio Coding frame || |`adts` |Audio Data Transport Stream |`adts_frame`| |`adts_frame` |Audio Data Transport Stream frame |`aac_frame`| -|[`adm`](#adm) |Audio Definition Model |`riff`| |`aiff` |Audio Interchange File Format || |`amf0` |Action Message Format 0 || |`apev2` |APEv2 metadata tag |`image`| @@ -26,7 +25,6 @@ |`avc_sps` |H.264/AVC Sequence Parameter Set || |[`avi`](#avi) |Audio Video Interleaved |`avc_au` `hevc_au` `mp3_frame` `flac_frame`| |[`avro_ocf`](#avro_ocf) |Avro object container file || -|[`axml`](#adm) |Audio Definition Model  Chunk |`riff`| |[`bencode`](#bencode) |BitTorrent bencoding || |`bitcoin_blkdat` |Bitcoin blk.dat |`bitcoin_block`| |[`bitcoin_block`](#bitcoin_block) |Bitcoin block |`bitcoin_transaction`| @@ -40,11 +38,10 @@ |`bzip2` |bzip2 compression |`probe`| |[`caff`](#caff) |Live2D Cubism archive |`probe`| |[`cbor`](#cbor) |Concise Binary Object Representation || -|[`chna`](#adm) |Audio Definition Model  Chunk |`riff`| |[`csv`](#csv) |Comma separated values || -|[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Digital Plus) |`riff`| |`dns` |DNS packet || |`dns_tcp` |DNS packet (TCP) || +|[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Dolby Digital) || |`elf` |Executable and Linkable Format || |`ether8023_frame` |Ethernet 802.3 frame |`inet_packet`| |`exif` |Exchangeable Image File Format || @@ -133,7 +130,7 @@ |`vp9_frame` |VP9 frame || |`vpx_ccr` |VPX Codec Configuration Record || |[`wasm`](#wasm) |WebAssembly Binary Format || -|`wav` |WAV file |`id3v2` `id3v1` `id3v11`| +|`wav` |WAV file |`id3v2` `id3v1` `id3v11` `dolby_metadata`| |`webp` |WebP image |`exif` `vp8_frame` `icc_profile` `xml`| |[`xml`](#xml) |Extensible Markup Language || |`yaml` |YAML Ain't Markup Language || @@ -183,27 +180,6 @@ Decode value as aac_frame ... | aac_frame({object_type:1}) ``` -## adm -[Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) including 3D Audio. - -RIFF / WAV / Broadcast Wave Format (BWF) chunks: -- `` Chunk, Track UIDs of Audio Definition Model -- `` Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements - -### Examples -Decode ADM configuration from `` and `` chunks: -``` -$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' bwf.wav -``` - -### Authors -- [@johnnymarnell](https://johnnymarnell.github.io), original author - -### References -- https://adm.ebu.io/background/what_is_the_adm.html -- https://tech.ebu.ch/publications/tech3285s7 -- https://tech.ebu.ch/publications/tech3285s5 - ## apple_bookmark Apple BookmarkData. @@ -616,6 +592,8 @@ $ fq -d csv '.[0] as $t | .[1:] | map(with_entries(.key = $t[.key]))' file.csv ``` ## dolby_metadata +Dolby Metadata (Atmos, AC3, Dolby Digital). + Dolby Metadata from `` chunk of RIFF / WAV / Broadcast Wave Format (BWF), including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. LUFS, True Peak). @@ -625,13 +603,20 @@ Decode Dolby metadata from `` chunk: $ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav ``` -### References -- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf -- https://github.com/DolbyLaboratories/dbmd-atmos-parser +RIFF / WAV / Broadcast Wave Format (BWF) chunks: +- `` Track UIDs of Audio Definition Model +- `` BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements ### Authors - [@johnnymarnell](https://johnnymarnell.github.io), original author +### References +- https://adm.ebu.io/background/what_is_the_adm.html +- https://tech.ebu.ch/publications/tech3285s7 +- https://tech.ebu.ch/publications/tech3285s5 +- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf +- https://github.com/DolbyLaboratories/dbmd-atmos-parser + ## fit Garmin Flexible and Interoperable Data Transfer. @@ -824,6 +809,7 @@ LevelDB Table. - Zstandard uncompression is not implemented yet. ### Authors + - [@mikez](https://github.com/mikez), original author ### References diff --git a/format/format.go b/format/format.go index a87ff0e02..006e6878f 100644 --- a/format/format.go +++ b/format/format.go @@ -92,6 +92,7 @@ var ( CSV = &decode.Group{Name: "csv"} DNS = &decode.Group{Name: "dns"} DNS_TCP = &decode.Group{Name: "dns_tcp"} + Dolby_Metadata = &decode.Group{Name: "dolby_metadata"} ELF = &decode.Group{Name: "elf"} Ether_8023_Frame = &decode.Group{Name: "ether8023_frame"} Exif = &decode.Group{Name: "exif"} diff --git a/format/riff/adm.go b/format/riff/adm.go index 57b4193f0..b8955263c 100644 --- a/format/riff/adm.go +++ b/format/riff/adm.go @@ -9,21 +9,22 @@ import ( "github.com/wader/fq/pkg/decode" ) -func chnaDecode(d *decode.D, size int64) { +func chnaDecode(d *decode.D) { d.FieldU16("num_tracks") d.FieldU16("num_uids") - - audioIdLen := (size - 4) / 40 - d.FieldStructNArray("audio_ids", "audio_id", int64(audioIdLen), func(d *decode.D) { - d.FieldU16("track_index") - d.FieldUTF8("uid", 12) - d.FieldUTF8("track_format_id_reference", 14) - d.FieldUTF8("pack_format_id_reference", 11) - d.FieldRawLen("padding", 8) + d.FieldArray("audio_ids", func(d *decode.D) { + for !d.End() { + d.FieldStruct("audio_id", func(d *decode.D) { + d.FieldU16("track_index") + d.FieldUTF8("uid", 12) + d.FieldUTF8("track_format_id_reference", 14) + d.FieldUTF8("pack_format_id_reference", 11) + d.FieldRawLen("padding", 8) + }) + } }) } -func axmlDecode(d *decode.D, size int64) { - // TODO(jmarnell): this chunk is all variable xml, so leave as is? - d.FieldUTF8("xml", int(size)) +func axmlDecode(d *decode.D) { + d.FieldUTF8("xml", int(d.BitsLeft())/8) } diff --git a/format/riff/aiff.go b/format/riff/aiff.go index d02346176..6efcd68c7 100644 --- a/format/riff/aiff.go +++ b/format/riff/aiff.go @@ -54,7 +54,7 @@ func aiffDecode(d *decode.D) any { } return id, size }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "FORM": riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType)) diff --git a/format/riff/avi.go b/format/riff/avi.go index f2c57406b..d35ae3a85 100644 --- a/format/riff/avi.go +++ b/format/riff/avi.go @@ -238,7 +238,7 @@ func aviDecodeEx(d *decode.D, ai format.AVI_In, extendedChunk bool) { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "RIFF": foundRiffType = d.FieldUTF8("type", 4, d.StrAssert(requiredRiffType)) diff --git a/format/riff/common.go b/format/riff/common.go index 5415390a3..f00d125dc 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -19,11 +19,11 @@ func (p path) topData() any { return p[len(p)-1].data } -func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path, size int64) (bool, any)) { +func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path) (bool, any)) { id, size := headFn(d, path) d.FramedFn(size*8, func(d *decode.D) { - hasChildren, data := chunkFn(d, id, path, size) + hasChildren, data := chunkFn(d, id, path) if hasChildren { np := append(path, pathEntry{id: id, data: data}) d.FieldArray("chunks", func(d *decode.D) { diff --git a/format/riff/dolby.go b/format/riff/dolby_metadata.go similarity index 68% rename from format/riff/dolby.go rename to format/riff/dolby_metadata.go index 2f27e5372..5d8cd4414 100644 --- a/format/riff/dolby.go +++ b/format/riff/dolby_metadata.go @@ -5,11 +5,31 @@ package riff // https://github.com/DolbyLaboratories/dbmd-atmos-parser import ( + "embed" + + "github.com/wader/fq/format" "github.com/wader/fq/pkg/decode" + "github.com/wader/fq/pkg/interp" "github.com/wader/fq/pkg/scalar" ) +//go:embed dolby_metadata.md +var dolbyMetadataFS embed.FS + +func init() { + interp.RegisterFormat( + format.Dolby_Metadata, + &decode.Format{ + Description: "Dolby Metadata (Atmos, AC3, Dolby Digital)", + DecodeFn: dbmdDecode, + }, + ) + interp.RegisterFS(dolbyMetadataFS) +} + func dbmdDecode(d *decode.D) any { + d.Endian = decode.LittleEndian + d.FieldStruct("version", func(d *decode.D) { d.FieldU8("major") d.FieldU8("minor") @@ -30,15 +50,15 @@ func dbmdDecode(d *decode.D) any { segmentSize := d.FieldU16("size") switch segmentID { - case metadataSegmentTypeDolyEMetadata: + case metadataSegmentTypeDolbyEMetadata: parseDolbyE(d) - case metadataSegmentTypeDolyEDigitaletadata: + case metadataSegmentTypeDolbyEDigitaletadata: parseDolbyDigital(d) - case metadataSegmentTypeDolyDigitalPlusMetadata: + case metadataSegmentTypeDolbyDigitalPlusMetadata: parseDolbyDigitalPlus(d) case metadataSegmentTypeAudioInfo: parseAudioInfo(d) - case metadataSegmentTypeDolyAtmos: + case metadataSegmentTypeDolbyAtmos: parseDolbyAtmos(d) case metadataSegmentTypeDolbyAtmosSupplemental: parseDolbyAtmosSupplemental(d) @@ -63,7 +83,7 @@ var compressionDescMap = scalar.UintMapSymStr{ 5: "speech", } -var bitstreamMode = scalar.UintMapDescription{ +var bitstreamModeMap = scalar.UintMapDescription{ 0b000: "main audio service: complete main (CM)", 0b001: "main audio service: music and effects (ME)", 0b010: "associated service: visually impaired (VI)", @@ -104,43 +124,38 @@ var trimConfigName = scalar.UintMapDescription{ 8: "7.1.4", } -var trimType = scalar.UintMapDescription{ - 0: "manual", - 1: "automatic", -} - const ( - metadataSegmentTypeEnd = 0 - metadataSegmentTypeDolyEMetadata = 1 - metadataSegmentTypeDolyReserved2 = 2 - metadataSegmentTypeDolyEDigitaletadata = 3 - metadataSegmentTypeDolyReserved4 = 4 - metadataSegmentTypeDolyReserved5 = 5 - metadataSegmentTypeDolyReserved6 = 6 - metadataSegmentTypeDolyDigitalPlusMetadata = 7 - metadataSegmentTypeAudioInfo = 8 - metadataSegmentTypeDolyAtmos = 9 - metadataSegmentTypeDolbyAtmosSupplemental = 10 + metadataSegmentTypeEnd = 0 + metadataSegmentTypeDolbyEMetadata = 1 + metadataSegmentTypeDolbyReserved2 = 2 + metadataSegmentTypeDolbyEDigitaletadata = 3 + metadataSegmentTypeDolbyReserved4 = 4 + metadataSegmentTypeDolbyReserved5 = 5 + metadataSegmentTypeDolbyReserved6 = 6 + metadataSegmentTypeDolbyDigitalPlusMetadata = 7 + metadataSegmentTypeAudioInfo = 8 + metadataSegmentTypeDolbyAtmos = 9 + metadataSegmentTypeDolbyAtmosSupplemental = 10 ) var metadataSegmentTypeMap = scalar.UintMapSymStr{ - metadataSegmentTypeEnd: "end", - metadataSegmentTypeDolyEMetadata: "doly_e_metadata", - metadataSegmentTypeDolyReserved2: "reserved2", - metadataSegmentTypeDolyEDigitaletadata: "doly_e_digitale_tadata", - metadataSegmentTypeDolyReserved4: "reserved4", - metadataSegmentTypeDolyReserved5: "reserved5", - metadataSegmentTypeDolyReserved6: "reserved6", - metadataSegmentTypeDolyDigitalPlusMetadata: "doly_digital_plus_metadata", - metadataSegmentTypeAudioInfo: "audio_info", - metadataSegmentTypeDolyAtmos: "doly_atmos", - metadataSegmentTypeDolbyAtmosSupplemental: "dolby_atmos_supplemental", + metadataSegmentTypeEnd: "end", + metadataSegmentTypeDolbyEMetadata: "dolby_e_metadata", + metadataSegmentTypeDolbyReserved2: "reserved2", + metadataSegmentTypeDolbyEDigitaletadata: "dolby_e_digitale_tadata", + metadataSegmentTypeDolbyReserved4: "reserved4", + metadataSegmentTypeDolbyReserved5: "reserved5", + metadataSegmentTypeDolbyReserved6: "reserved6", + metadataSegmentTypeDolbyDigitalPlusMetadata: "dolby_digital_plus_metadata", + metadataSegmentTypeAudioInfo: "audio_info", + metadataSegmentTypeDolbyAtmos: "dolby_atmos", + metadataSegmentTypeDolbyAtmosSupplemental: "dolby_atmos_supplemental", } func parseDolbyE(d *decode.D) { d.FieldU8("program_config") d.FieldU8("frame_rate_code") - d.FieldRawLen("e_SMPTE_time_code", 8*8) + d.FieldRawLen("e_smpte_time_code", 8*8) d.FieldRawLen("e_reserved", 1*8) d.FieldRawLen("e_reserved2", 25*8) d.FieldRawLen("reserved_for_future_use", 80*8) @@ -176,9 +191,9 @@ func parseDolbyDigitalPlus(d *decode.D) { if bsmod == 0b111 && acmod != 0b001 { bsmod = 0b1000 } - d.FieldValueStr("bitstream_mode", bitstreamMode[bsmod]) + d.FieldValueStr("bitstream_mode", bitstreamModeMap[bsmod]) - d.FieldU16LE("ddplus_reserved_a") + d.FieldU16("ddplus_reserved_a") d.FieldU8("surround_config") d.FieldU8("dialnorm_info") @@ -188,32 +203,32 @@ func parseDolbyDigitalPlus(d *decode.D) { d.FieldU8("ext_bsi1_word2") d.FieldU8("ext_bsi2_word1") - d.FieldU24LE("ddplus_reserved_b") + d.FieldU24("ddplus_reserved_b") d.FieldU8("compr1", scalar.UintSym("reserved"), compressionDescMap) d.FieldU8("dynrng1", scalar.UintSym("reserved"), compressionDescMap) - d.FieldU24LE("ddplus_reserved_c") + d.FieldU24("ddplus_reserved_c") d.FieldU8("ddplus_info1") - d.FieldU40LE("ddplus_reserved_d") + d.FieldU40("ddplus_reserved_d") - d.FieldU16LE("datarate") + d.FieldU16("datarate") d.FieldRawLen("reserved_for_future_use", 69*8) } func parseAudioInfo(d *decode.D) { d.FieldU8("program_id") d.FieldUTF8("audio_origin", 32) - d.FieldU32LE("largest_sample_value") - d.FieldU32LE("largest_sample_value_2") - d.FieldU32LE("largest_true_peak_value") - d.FieldU32LE("largest_true_peak_value_2") - d.FieldU32LE("dialogue_loudness") - d.FieldU32LE("dialogue_loudness_2") - d.FieldU32LE("speech_content") - d.FieldU32LE("speech_content_2") + d.FieldU32("largest_sample_value") + d.FieldU32("largest_sample_value_2") + d.FieldU32("largest_true_peak_value") + d.FieldU32("largest_true_peak_value_2") + d.FieldU32("dialogue_loudness") + d.FieldU32("dialogue_loudness_2") + d.FieldU32("speech_content") + d.FieldU32("speech_content_2") d.FieldUTF8("last_processed_by", 32) d.FieldUTF8("last_operation", 32) d.FieldUTF8("segment_creation_date", 32) @@ -241,19 +256,20 @@ func parseDolbyAtmos(d *decode.D) { } func parseDolbyAtmosSupplemental(d *decode.D) { - d.FieldU32LE("dasms_sync", d.UintAssert(0xf8726fbd), scalar.UintHex) + d.FieldU32("dasms_sync", d.UintAssert(0xf8726fbd), scalar.UintHex) // TODO: wav.go sets LE default i think? - objectCount := int64(d.FieldU16LE("object_count")) - d.FieldU8LE("reserved") + objectCount := int64(d.FieldU16("object_count")) + d.FieldU8("reserved") i := 0 d.FieldStructNArray("trim_configs", "trim_config", 9, func(d *decode.D) { - autoTrimReserved := d.FieldU8LE("auto_trim_reserved") - autoTrim := autoTrimReserved & 0x01 - d.FieldValueBool("auto_trim", autoTrim == 1) - d.FieldValueStr("trim_type", trimType[autoTrim]) - d.FieldValueStr("trim_config_name", trimConfigName[uint64(i)]) + d.FieldRawLen("reserved", 7) + d.FieldU1("type", scalar.UintMapSymStr{ + 0: "manual", + 1: "automatic", + }) + d.FieldValueStr("config_name", trimConfigName[uint64(i)]) // TODO: this is null separted list of def strings? d.FieldUTF8("raw", 14) diff --git a/format/riff/dolby_metadata.md b/format/riff/dolby_metadata.md new file mode 100644 index 000000000..61fea4242 --- /dev/null +++ b/format/riff/dolby_metadata.md @@ -0,0 +1,22 @@ +Dolby Metadata from `` chunk of RIFF / WAV / Broadcast Wave Format (BWF), +including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. LUFS, True Peak). + +### Examples +Decode Dolby metadata from `` chunk: +``` +$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav +``` + +RIFF / WAV / Broadcast Wave Format (BWF) chunks: +- `` Track UIDs of Audio Definition Model +- `` BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements + +### Authors +- [@johnnymarnell](https://johnnymarnell.github.io), original author + +### References +- https://adm.ebu.io/background/what_is_the_adm.html +- https://tech.ebu.ch/publications/tech3285s7 +- https://tech.ebu.ch/publications/tech3285s5 +- https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf +- https://github.com/DolbyLaboratories/dbmd-atmos-parser diff --git a/format/riff/wav.go b/format/riff/wav.go index 98329d9ff..4c87b59a7 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -16,6 +16,7 @@ import ( var wavHeaderGroup decode.Group var wavFooterGroup decode.Group +var wavDolbyMetadataGroup decode.Group func init() { interp.RegisterFormat( @@ -28,6 +29,7 @@ func init() { Dependencies: []decode.Dependency{ {Groups: []*decode.Group{format.ID3v2}, Out: &wavHeaderGroup}, {Groups: []*decode.Group{format.ID3v1, format.ID3v11}, Out: &wavFooterGroup}, + {Groups: []*decode.Group{format.Dolby_Metadata}, Out: &wavDolbyMetadataGroup}, }, }) } @@ -76,7 +78,7 @@ func wavDecode(d *decode.D) any { return id, size }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(wavRiffType)) @@ -159,13 +161,13 @@ func wavDecode(d *decode.D) any { return false, nil case "chna": - chnaDecode(d, size) + chnaDecode(d) return false, nil case "axml": - axmlDecode(d, size) + axmlDecode(d) return false, nil case "dbmd": - dbmdDecode(d) + d.Format(&wavDolbyMetadataGroup, nil) return false, nil default: diff --git a/format/riff/webp.go b/format/riff/webp.go index de69d6238..ebc0bbc59 100644 --- a/format/riff/webp.go +++ b/format/riff/webp.go @@ -44,7 +44,7 @@ func webpDecode(d *decode.D) any { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(webpRiffType)) From 5687b0852a422b3e09a76830643fc4b6fa106fbe Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Mon, 28 Oct 2024 09:45:53 -0400 Subject: [PATCH 09/13] Try to fix docs, add latest, add failing test --- doc/dev.md | 9 +- format/riff/adm.md | 22 +++ format/riff/common.go | 6 +- format/riff/dolby.go | 20 +-- format/riff/dolby_metadata.md | 2 +- format/riff/testdata/dolby_metadata.fqtest | 192 +++++++++++++++++++++ format/riff/testdata/dolby_metadata.wav | Bin 0 -> 10988 bytes 7 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 format/riff/adm.md create mode 100644 format/riff/testdata/dolby_metadata.fqtest create mode 100644 format/riff/testdata/dolby_metadata.wav diff --git a/doc/dev.md b/doc/dev.md index 5159056a2..508583c1d 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -38,16 +38,19 @@ Flags can be struct with bit-fields. - Use commit messages with a context prefix to make it easier to find and understand, ex:
`mp3: Validate sync correctly` - Tests: - - If possible use a pair of `testdata/file` and `testdata/file.fqtest` where `file.fqtest` is `$ fq dv file` or `$ fq 'dv,torepr' file` if there is `torepr` support. + - If possible, add one or more pairs of example input file and expected CLI output, with naming like: + - `./format//testdata/.`, e.g. [`./format/mp4/testdata/aac.mp4`](../format/mp4/testdata/aac.mp4) + - and `./format//testdata/.fqtest`, e.g. [`./format/mp4/testdata/aac.fqtest`](../format/mp4/testdata/aac.fqtest) + - The latter contents should be `$ go run . dv ` or `$ go run . 'dv,torepr' ` if there is `torepr` support. - If `dv` produces a lof of output maybe use `dv({array_truncate: 50})` etc - Run `go test ./format -run TestFormats/` to test expected output. - Run `go test ./format -run TestFormats/ -update` to update current output as expected output. - If you have format specific documentation: - Put it in `format/*/.md` and use `//go:embed .md`/`interp.RegisterFS(..)` to embed/register it. - Use simple markdown, just sections (depth starts at 3, `### Section`), paragraphs, lists and links. - - No heading section is needs with format name, will be added by `make doc` and fq cli help system. + - No heading section is needed with format name, will be added by `make doc` and fq cli help system. - Add a `testdata/_help.fqtest` with just `$ fq -h ` to test CLI help. - - If in doubt look at `mp4.md`/`mp4.go` etc. + - If in doubt look at [`mp4.md`](../format/mp4/mp4.md)/[`mp4.go`](../format/mp4/mp4.go) etc. - Run `make README.md doc/formats.md` to update md files. - Run linter `make lint` - Run fuzzer `make fuzz GROUP=`, see usage in Makefile diff --git a/format/riff/adm.md b/format/riff/adm.md new file mode 100644 index 000000000..1ae565e20 --- /dev/null +++ b/format/riff/adm.md @@ -0,0 +1,22 @@ +[Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) including 3D Audio. + +RIFF / WAV / Broadcast Wave Format (BWF) chunks: +- `` Chunk, Track UIDs of Audio Definition Model +- `` Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements + +### Examples +Decode ADM configuration from `` and `` chunks: +```bash +$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' amd-bwf.wav + +# Extract ADM chunk objects definitions xml content +$ fq -r -d wav '.chunks[] | select(.id | IN("axml")) | .xml | tovalue' amd-bwf.wav | tee axml-content.xml +``` + +### Authors +- [@johnnymarnell](https://johnnymarnell.github.io), original author + +### References +- https://adm.ebu.io/background/what_is_the_adm.html +- https://tech.ebu.ch/publications/tech3285s7 +- https://tech.ebu.ch/publications/tech3285s5 \ No newline at end of file diff --git a/format/riff/common.go b/format/riff/common.go index 5415390a3..bfea3a850 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -36,9 +36,9 @@ func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (str } }) - wordAlgin := d.AlignBits(16) - if wordAlgin != 0 { - d.FieldRawLen("align", int64(wordAlgin)) + wordAlign := d.AlignBits(16) + if wordAlign != 0 { + d.FieldRawLen("align", int64(wordAlign)) } } diff --git a/format/riff/dolby.go b/format/riff/dolby.go index 5ec16f95c..ce4d90cb7 100644 --- a/format/riff/dolby.go +++ b/format/riff/dolby.go @@ -12,7 +12,7 @@ import ( "github.com/wader/fq/pkg/scalar" ) -func dbmdDecode(d *decode.D, size int64) any { +func tmp_dbmdDecode(d *decode.D, size int64) any { version := d.U32() major := (version >> 24) & 0xFF minor := (version >> 16) & 0xFF @@ -47,9 +47,9 @@ func dbmdDecode(d *decode.D, size int64) any { case 8: parseAudioInfo(d) case 9: - parseDolbyAtmos(d, segmentSize) + parseDolbyAtmos(d) case 10: - parseDolbyAtmosSupplemental(d, segmentSize) + parseDolbyAtmosSupplemental(d) default: d.FieldRawLen("unknown_segment_raw", int64(segmentSize*8)) } @@ -117,7 +117,7 @@ var warpMode = scalar.UintMapDescription{ 4: "not indicated (Default warping will be applied.)", } -var trimConfigName = scalar.UintMapDescription{ +var tmp_trimConfigName = scalar.UintMapDescription{ 0: "2.0", 1: "5.1", 2: "7.1", @@ -134,7 +134,7 @@ var trimType = scalar.UintMapDescription{ 1: "automatic", } -func parseDolbyE(d *decode.D) { +func tmp_parseDolbyE(d *decode.D) { d.FieldValueStr("metadata_segment_type", "dolby_e") d.FieldU8("program_config") @@ -145,7 +145,7 @@ func parseDolbyE(d *decode.D) { d.FieldRawLen("reserved_for_future_use", 80*8) } -func parseDolbyDigital(d *decode.D) { +func tmp_parseDolbyDigital(d *decode.D) { d.FieldValueStr("metadata_segment_type", "dolby_digital") d.FieldU8("ac3_program_id") @@ -166,7 +166,7 @@ func parseDolbyDigital(d *decode.D) { d.FieldRawLen("program_description_text", 32*8) } -func parseDolbyDigitalPlus(d *decode.D) { +func tmp_parseDolbyDigitalPlus(d *decode.D) { d.FieldValueStr("metadata_segment_type", "dolby_digital_plus") d.FieldU8("program_id") @@ -205,7 +205,7 @@ func parseDolbyDigitalPlus(d *decode.D) { d.FieldRawLen("reserved_for_future_use", 69*8) } -func parseAudioInfo(d *decode.D) { +func tmp_parseAudioInfo(d *decode.D) { d.FieldValueStr("metadata_segment_type", "audio_info") d.FieldU8("program_id") @@ -224,7 +224,7 @@ func parseAudioInfo(d *decode.D) { d.FieldUTF8("segment_modified_date", 32) } -func parseDolbyAtmos(d *decode.D, size uint64) { +func tmp_parseDolbyAtmos(d *decode.D, size uint64) { d.FieldValueStr("metadata_segment_type", "dolby_atmos") // d.SeekRel(32 * 8) @@ -248,7 +248,7 @@ func parseDolbyAtmos(d *decode.D, size uint64) { d.SeekRel(80 * 8) } -func parseDolbyAtmosSupplemental(d *decode.D, size uint64) { +func tmp_parseDolbyAtmosSupplemental(d *decode.D, size uint64) { d.FieldValueStr("metadata_segment_type", "dolby_atmos_supplemental") sync := d.FieldU32LE("dasms_sync") diff --git a/format/riff/dolby_metadata.md b/format/riff/dolby_metadata.md index 61fea4242..f3e952e5f 100644 --- a/format/riff/dolby_metadata.md +++ b/format/riff/dolby_metadata.md @@ -4,7 +4,7 @@ including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. L ### Examples Decode Dolby metadata from `` chunk: ``` -$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav +$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' adm-bwf.wav ``` RIFF / WAV / Broadcast Wave Format (BWF) chunks: diff --git a/format/riff/testdata/dolby_metadata.fqtest b/format/riff/testdata/dolby_metadata.fqtest new file mode 100644 index 000000000..cc9bc6436 --- /dev/null +++ b/format/riff/testdata/dolby_metadata.fqtest @@ -0,0 +1,192 @@ + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: ./format/riff/testdata/dolby_metadata.wav (wav) 0x0-0x2aec (10988) +0x0000|52 49 46 46 |RIFF | id: "RIFF" 0x0-0x4 (4) +0x0000| e4 2a 00 00 | .*.. | size: 10980 0x4-0x8 (4) +0x0000| 57 41 56 45 | WAVE | format: "WAVE" (valid) 0x8-0xc (4) + | | | chunks[0:6]: 0xc-0x2aeb (10975) + | | | [0]{}: chunk 0xc-0x54 (72) +0x0000| 4a 55 4e 4b| JUNK| id: "JUNK" (Alignment) 0xc-0x10 (4) +0x0010|40 00 00 00 |@... | size: 64 0x10-0x14 (4) +0x0010| 00 00 00 00 00 00 00 00 00 00 00 00| ............| data: raw bits 0x14-0x54 (64) +0x0020|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| +* |until 0x53.7 (64) | | + | | | [1]{}: chunk 0x54-0x6c (24) +0x0050| 66 6d 74 20 | fmt | id: "fmt" 0x54-0x58 (4) +0x0050| 10 00 00 00 | .... | size: 16 0x58-0x5c (4) +0x0050| 01 00 | .. | audio_format: "pcm_s16le" (1) 0x5c-0x5e (2) +0x0050| 03 00| ..| num_channels: 3 0x5e-0x60 (2) +0x0060|80 bb 00 00 |.... | sample_rate: 48000 0x60-0x64 (4) +0x0060| 80 97 06 00 | .... | byte_rate: 432000 0x64-0x68 (4) +0x0060| 09 00 | .. | block_align: 9 0x68-0x6a (2) +0x0060| 18 00 | .. | bits_per_sample: 24 0x6a-0x6c (2) + | | | [2]{}: chunk 0x6c-0x1154 (4328) +0x0060| 64 61 74 61| data| id: "data" 0x6c-0x70 (4) +0x0070|e0 10 00 00 |.... | size: 4320 0x70-0x74 (4) +0x0070| 00 00 00 00 00 00 00 00 00 00 00 00| ............| samples: raw bits 0x74-0x1154 (4320) +0x0080|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| +* |until 0x1153.7 (4320) | | + | | | [3]{}: chunk 0x1154-0x2862 (5902) +0x1150| 61 78 6d 6c | axml | id: "axml" (Audio Definition Model ambisonics and elements) 0x1154-0x1158 (4) +0x1150| 06 17 00 00 | .... | size: 5894 0x1158-0x115c (4) +0x1150| 3c 3f 78 6d| \n\n\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tACO_1001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAO_1001\n\t\t\t\t\tAO_100b\n\t\t\t\t\t2\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tATU_00000001\n\t\t\t\t\tATU_00000002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAP_00031001\n\t\t\t\t\tATU_00000003\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011001\n\t\t\t\t\tAC_00011002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00031001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tRC_L\n\t\t\t\t\t\t1\n\t\t\t\t\t\t-1.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tRC_R\n\t\t\t\t\t\t1\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t1\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t1\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011001\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tAT_00011001_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011002\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tAT_00011002_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00031001\n\t\t\t\t\tAP_00031001\n\t\t\t\t\tAT_00031001_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00011002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00031001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00011001_01\n\t\t\t\t\tAP_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00011002_01\n\t\t\t\t\tAP_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00031001_01\n\t\t\t\t\tAP_00031001\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\n" 0x115c-0x2862 (5894) +0x1160|6c 20 76 65 72 73 69 6f 6e 3d 22 31 2e 30 22 20|l version="1.0" | +* |until 0x2861.7 (5894) | | + | | | [4]{}: chunk 0x2862-0x28e6 (132) +0x2860| 63 68 6e 61 | chna | id: "chna" (Track UIDs of Audio Definition Model) 0x2862-0x2866 (4) +0x2860| 7c 00 00 00 | |... | size: 124 0x2866-0x286a (4) +0x2860| 03 00 | .. | num_tracks: 3 0x286a-0x286c (2) +0x2860| 03 00 | .. | num_uids: 3 0x286c-0x286e (2) + | | | audio_ids[0:3]: 0x286e-0x28e6 (120) + | | | [0]{}: audio_id 0x286e-0x2896 (40) +0x2860| 01 00| ..| track_index: 1 0x286e-0x2870 (2) +0x2870|41 54 55 5f 30 30 30 30 30 30 30 31 |ATU_00000001 | uid: "ATU_00000001" 0x2870-0x287c (12) +0x2870| 41 54 5f 30| AT_0| track_format_id_reference: "AT_00011001_01" 0x287c-0x288a (14) +0x2880|30 30 31 31 30 30 31 5f 30 31 |0011001_01 | +0x2880| 41 50 5f 30 30 30| AP_000| pack_format_id_reference: "AP_00011001" 0x288a-0x2895 (11) +0x2890|31 31 30 30 31 |11001 | +0x2890| 00 | . | padding: raw bits 0x2895-0x2896 (1) + | | | [1]{}: audio_id 0x2896-0x28be (40) +0x2890| 02 00 | .. | track_index: 2 0x2896-0x2898 (2) +0x2890| 41 54 55 5f 30 30 30 30| ATU_0000| uid: "ATU_00000002" 0x2898-0x28a4 (12) +0x28a0|30 30 30 32 |0002 | +0x28a0| 41 54 5f 30 30 30 31 31 30 30 32 5f| AT_00011002_| track_format_id_reference: "AT_00011002_01" 0x28a4-0x28b2 (14) +0x28b0|30 31 |01 | +0x28b0| 41 50 5f 30 30 30 31 31 30 30 31 | AP_00011001 | pack_format_id_reference: "AP_00011001" 0x28b2-0x28bd (11) +0x28b0| 00 | . | padding: raw bits 0x28bd-0x28be (1) + | | | [2]{}: audio_id 0x28be-0x28e6 (40) +0x28b0| 03 00| ..| track_index: 3 0x28be-0x28c0 (2) +0x28c0|41 54 55 5f 30 30 30 30 30 30 30 33 |ATU_00000003 | uid: "ATU_00000003" 0x28c0-0x28cc (12) +0x28c0| 41 54 5f 30| AT_0| track_format_id_reference: "AT_00031001_01" 0x28cc-0x28da (14) +0x28d0|30 30 33 31 30 30 31 5f 30 31 |0031001_01 | +0x28d0| 41 50 5f 30 30 30| AP_000| pack_format_id_reference: "AP_00031001" 0x28da-0x28e5 (11) +0x28e0|33 31 30 30 31 |31001 | +0x28e0| 00 | . | padding: raw bits 0x28e5-0x28e6 (1) + | | | [5]{}: chunk 0x28e6-0x2aeb (517) +0x28e0| 64 62 6d 64 | dbmd | id: "dbmd" (Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus]) 0x28e6-0x28ea (4) +0x28e0| fe 01 00 00 | .... | size: 510 0x28ea-0x28ee (4) + | | | version{}: 0x28ee-0x28f2 (4) +0x28e0| 06 | . | major: 6 0x28ee-0x28ef (1) +0x28e0| 00| .| minor: 0 0x28ef-0x28f0 (1) +0x28f0|00 |. | patch: 0 0x28f0-0x28f1 (1) +0x28f0| 01 | . | build: 1 0x28f1-0x28f2 (1) + | | | metadata_segments[0:4]: 0x28f2-0x2aeb (505) + | | | [0]{}: metadata_segment 0x28f2-0x2956 (100) +0x28f0| 07 | . | id: "dolby_digital_plus_metadata" (7) 0x28f2-0x28f3 (1) +0x28f0| 60 00 | `. | size: 96 0x28f3-0x28f5 (2) +0x28f0| 00 | . | program_id: 0 0x28f5-0x28f6 (1) +0x28f0| 47 | G | program_info: 71 0x28f6-0x28f7 (1) + | | | lfe_on: true + | | | bitstream_mode: "main audio service: complete main (CM)" +0x28f0| 00 00 | .. | ddplus_reserved_a: 0 0x28f7-0x28f9 (2) +0x28f0| 00 | . | surround_config: 0 0x28f9-0x28fa (1) +0x28f0| 60 | ` | dialnorm_info: 96 0x28fa-0x28fb (1) +0x28f0| 00 | . | langcod: 0 0x28fb-0x28fc (1) +0x28f0| 00 | . | audio_prod_info: 0 0x28fc-0x28fd (1) +0x28f0| 24 | $ | ext_bsi1_word1: 36 0x28fd-0x28fe (1) +0x28f0| 24 | $ | ext_bsi1_word2: 36 0x28fe-0x28ff (1) +0x28f0| 00| .| ext_bsi2_word1: 0 0x28ff-0x2900 (1) +0x2900|00 00 00 |... | ddplus_reserved_b: 0 0x2900-0x2903 (3) +0x2900| 02 | . | compr1: "film_light" (2) 0x2903-0x2904 (1) +0x2900| 02 | . | dynrng1: "film_light" (2) 0x2904-0x2905 (1) +0x2900| 00 00 00 | ... | ddplus_reserved_c: 0 0x2905-0x2908 (3) +0x2900| 00 | . | ddplus_info1: 0 0x2908-0x2909 (1) +0x2900| 00 00 00 00 00 | ..... | ddplus_reserved_d: 0 0x2909-0x290e (5) +0x2900| 00 00| ..| datarate: 0 0x290e-0x2910 (2) +0x2910|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| reserved_for_future_use: raw bits 0x2910-0x2955 (69) +* |until 0x2954.7 (69) | | +0x2950| ad | . | checksum: 0xad 0x2955-0x2956 (1) + | | | [1]{}: metadata_segment 0x2956-0x2a52 (252) +0x2950| 09 | . | id: "dolby_atmos" (9) 0x2956-0x2957 (1) +0x2950| f8 00 | .. | size: 248 0x2957-0x2959 (2) +0x2950| 43 72 65 61 74 65 64| Created| atmos_dbmd_content_creation_preamble: "Created using Dolby equipment" 0x2959-0x2979 (32) +0x2960|20 75 73 69 6e 67 20 44 6f 6c 62 79 20 65 71 75| using Dolby equ| +0x2970|69 70 6d 65 6e 74 00 00 00 |ipment... | +0x2970| 44 6f 6c 62 79 20 41| Dolby A| atmos_dbmd_content_creation_tool: "Dolby Atmos Composer Essential (fiedler audio)" 0x2979-0x29b9 (64) +0x2980|74 6d 6f 73 20 43 6f 6d 70 6f 73 65 72 20 45 73|tmos Composer Es| +* |until 0x29b8.7 (64) | | + | | | version{}: 0x29b9-0x29bc (3) +0x29b0| 01 | . | major: 1 0x29b9-0x29ba (1) +0x29b0| 00 | . | minor: 0 0x29ba-0x29bb (1) +0x29b0| 01 | . | micro: 1 0x29bb-0x29bc (1) +0x29b0| 00 00 00 00| ....| unknown0: raw bits 0x29bc-0x29f1 (53) +0x29c0|03 00 00 00 00 00 00 00 22 ff 00 00 00 00 00 03|........".......| +* |until 0x29f0.7 (53) | | +0x29f0| 83 | . | warp_mode: 131 0x29f1-0x29f2 (1) +0x29f0| 00 00 00 00 00 00 00 00 00 00 00 00 00 00| ..............| unknown1: raw bits 0x29f2-0x2a01 (15) +0x2a00|00 |. | +0x2a00| f0 00 08 00 00 00 00 00 00 00 00 00 00 00 00| ...............| unknown2: raw bits 0x2a01-0x2a51 (80) +0x2a10|00 f0 00 08 00 00 00 00 00 00 00 00 00 00 00 00|................| +* |until 0x2a50.7 (80) | | +0x2a50| 90 | . | checksum: 0x90 0x2a51-0x2a52 (1) + | | | [2]{}: metadata_segment 0x2a52-0x2aea (152) +0x2a50| 0a | . | id: "dolby_atmos_supplemental" (10) 0x2a52-0x2a53 (1) +0x2a50| 94 00 | .. | size: 148 0x2a53-0x2a55 (2) +0x2a50| bd 6f 72 f8 | .or. | dasms_sync: 0xf8726fbd (valid) 0x2a55-0x2a59 (4) +0x2a50| 03 00 | .. | object_count: 3 0x2a59-0x2a5b (2) +0x2a50| 00 | . | reserved: 0 0x2a5b-0x2a5c (1) + | | | trim_configs[0:9]: 0x2a5c-0x2ae3 (135) + | | | [0]{}: trim_config 0x2a5c-0x2a6b (15) +0x2a50| 01 | . | reserved: raw bits 0x2a5c-0x2a5c.7 (0.7) +0x2a50| 01 | . | type: "automatic" (1) 0x2a5c.7-0x2a5d (0.1) + | | | config_name: "2.0" +0x2a50| 00 00 00| ...| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a5d-0x2a6b (14) +0x2a60|00 00 00 00 00 00 00 00 00 00 00 |........... | + | | | [1]{}: trim_config 0x2a6b-0x2a7a (15) +0x2a60| 01 | . | reserved: raw bits 0x2a6b-0x2a6b.7 (0.7) +0x2a60| 01 | . | type: "automatic" (1) 0x2a6b.7-0x2a6c (0.1) + | | | config_name: "5.1" +0x2a60| 00 00 00 00| ....| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a6c-0x2a7a (14) +0x2a70|00 00 00 00 00 00 00 00 00 00 |.......... | + | | | [2]{}: trim_config 0x2a7a-0x2a89 (15) +0x2a70| 01 | . | reserved: raw bits 0x2a7a-0x2a7a.7 (0.7) +0x2a70| 01 | . | type: "automatic" (1) 0x2a7a.7-0x2a7b (0.1) + | | | config_name: "7.1" +0x2a70| 00 00 00 00 00| .....| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a7b-0x2a89 (14) +0x2a80|00 00 00 00 00 00 00 00 00 |......... | + | | | [3]{}: trim_config 0x2a89-0x2a98 (15) +0x2a80| 01 | . | reserved: raw bits 0x2a89-0x2a89.7 (0.7) +0x2a80| 01 | . | type: "automatic" (1) 0x2a89.7-0x2a8a (0.1) + | | | config_name: "2.1.2" +0x2a80| 00 00 00 00 00 00| ......| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a8a-0x2a98 (14) +0x2a90|00 00 00 00 00 00 00 00 |........ | + | | | [4]{}: trim_config 0x2a98-0x2aa7 (15) +0x2a90| 01 | . | reserved: raw bits 0x2a98-0x2a98.7 (0.7) +0x2a90| 01 | . | type: "automatic" (1) 0x2a98.7-0x2a99 (0.1) + | | | config_name: "5.1.2" +0x2a90| 00 00 00 00 00 00 00| .......| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a99-0x2aa7 (14) +0x2aa0|00 00 00 00 00 00 00 |....... | + | | | [5]{}: trim_config 0x2aa7-0x2ab6 (15) +0x2aa0| 01 | . | reserved: raw bits 0x2aa7-0x2aa7.7 (0.7) +0x2aa0| 01 | . | type: "automatic" (1) 0x2aa7.7-0x2aa8 (0.1) + | | | config_name: "7.1.2" +0x2aa0| 00 00 00 00 00 00 00 00| ........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2aa8-0x2ab6 (14) +0x2ab0|00 00 00 00 00 00 |...... | + | | | [6]{}: trim_config 0x2ab6-0x2ac5 (15) +0x2ab0| 01 | . | reserved: raw bits 0x2ab6-0x2ab6.7 (0.7) +0x2ab0| 01 | . | type: "automatic" (1) 0x2ab6.7-0x2ab7 (0.1) + | | | config_name: "2.1.4" +0x2ab0| 00 00 00 00 00 00 00 00 00| .........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2ab7-0x2ac5 (14) +0x2ac0|00 00 00 00 00 |..... | + | | | [7]{}: trim_config 0x2ac5-0x2ad4 (15) +0x2ac0| 01 | . | reserved: raw bits 0x2ac5-0x2ac5.7 (0.7) +0x2ac0| 01 | . | type: "automatic" (1) 0x2ac5.7-0x2ac6 (0.1) + | | | config_name: "5.1.4" +0x2ac0| 00 00 00 00 00 00 00 00 00 00| ..........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2ac6-0x2ad4 (14) +0x2ad0|00 00 00 00 |.... | + | | | [8]{}: trim_config 0x2ad4-0x2ae3 (15) +0x2ad0| 01 | . | reserved: raw bits 0x2ad4-0x2ad4.7 (0.7) +0x2ad0| 01 | . | type: "automatic" (1) 0x2ad4.7-0x2ad5 (0.1) + | | | config_name: "7.1.4" +0x2ad0| 00 00 00 00 00 00 00 00 00 00 00| ...........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2ad5-0x2ae3 (14) +0x2ae0|00 00 00 |... | + | | | objects[0:3]: 0x2ae3-0x2ae6 (3) +0x2ae0| 00 | . | [0]: 0 object_value 0x2ae3-0x2ae4 (1) +0x2ae0| 00 | . | [1]: 0 object_value 0x2ae4-0x2ae5 (1) +0x2ae0| 00 | . | [2]: 0 object_value 0x2ae5-0x2ae6 (1) + | | | binaural_render_modes[0:3]: 0x2ae6-0x2ae9 (3) +0x2ae0| 84 | . | [0]: "not_indicated" (4) render_mode 0x2ae6-0x2ae7 (1) +0x2ae0| 84 | . | [1]: "not_indicated" (4) render_mode 0x2ae7-0x2ae8 (1) +0x2ae0| 84 | . | [2]: "not_indicated" (4) render_mode 0x2ae8-0x2ae9 (1) +0x2ae0| 3e | > | checksum: 0x3e 0x2ae9-0x2aea (1) + | | | [3]{}: metadata_segment 0x2aea-0x2aeb (1) +0x2ae0| 00 | . | id: "end" (0) 0x2aea-0x2aeb (1) +0x2ae0| 00| | .| | gap0: raw bits 0x2aeb-0x2aec (1) diff --git a/format/riff/testdata/dolby_metadata.wav b/format/riff/testdata/dolby_metadata.wav new file mode 100644 index 0000000000000000000000000000000000000000..9e489453abbf4a2e414c38c2639dea342baa9bf9 GIT binary patch literal 10988 zcmeHN&2HO95MIZCTlv&mdnAY)+5)y@DJTMgRw_$&;YLmb*>2Nb)RnlhnTVuHQnds0 z5TJg59t!j+`UFMarH4L1jybiR{Ue8>EUA*5A^X33rm}l0u3lGusdnC29x$ zML=Db8A3CDJ#Oxs^>(Mb2hNxivM1DKAdOr=)@=Ev-ixE9#HkPOnwnK;h#h4kcbIojJ!DC0DX4cA9Q7b4*Y5#>JmL#SM_WBit&h;a2(203_j4yBr0Jr+_$^ zIt#>dM5M+zjC?nl3OE(L9I+-FIG!UDUDF9*Bb|;IeF1q{%3HL@hCDw&j*&spS>cj; z9vh};)rc)oJ~pySm?Bfe^>_|VlqpJL4z)z$5#~(Ndpl$zj9VCjz3xNpqbGQ++#Yo%GMDdr-06m`|nfn$kDor>VxxR)1-d&jxBe0zZ} zFA(_7dIP^q0N>fT&$rVaGipxt_+! zRqcDMA;aHpoOClOe;__Qa2Wtqb3xRqx=e&e~kn^qJ-A8n#c?1L-9ue^zHvFX0lxfPe6H zgRUV=qvu3THsa@R`2pONPukRj-jD`Vvk}~Kk3+caH=6#i_tIeB zkDZYVml3d->=Eh6X!tICo3p?;3`3B@Wvub>z+v_fy2OV5Bx~IUfC+6=Nz8x5gML-L z Date: Tue, 29 Oct 2024 13:10:54 -0400 Subject: [PATCH 10/13] Run make doc, try to reconcile Dolby lack of spec, its code, and MediaInfo --- README.md | 4 ---- doc/formats.md | 29 +++------------------- format/riff/dolby_metadata.go | 45 +++++++++++++++++++++++++---------- format/riff/dolby_metadata.md | 1 + 4 files changed, 36 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 0ffac8b18..4277eec52 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ It was originally designed to query, inspect and debug media codecs and containe [aac_frame](doc/formats.md#aac_frame), adts, adts_frame, -[adm](doc/formats.md#adm), aiff, amf0, apev2, @@ -54,7 +53,6 @@ avc_sei, avc_sps, [avi](doc/formats.md#avi), [avro_ocf](doc/formats.md#avro_ocf), -[axml](doc/formats.md#adm), [bencode](doc/formats.md#bencode), bitcoin_blkdat, [bitcoin_block](doc/formats.md#bitcoin_block), @@ -68,9 +66,7 @@ bsd_loopback_frame, bzip2, [caff](doc/formats.md#caff), [cbor](doc/formats.md#cbor), -[chna](doc/formats.md#adm), [csv](doc/formats.md#csv), -[dolby_metadata](doc/formats.md#dolby_metadata), dns, dns_tcp, [dolby_metadata](doc/formats.md#dolby_metadata), diff --git a/doc/formats.md b/doc/formats.md index 75e173471..e0138c63a 100644 --- a/doc/formats.md +++ b/doc/formats.md @@ -7,7 +7,6 @@ |[`aac_frame`](#aac_frame) |Advanced Audio Coding frame || |`adts` |Audio Data Transport Stream |`adts_frame`| |`adts_frame` |Audio Data Transport Stream frame |`aac_frame`| -|[`adm`](#adm) |Audio Definition Model |`riff`| |`aiff` |Audio Interchange File Format || |`amf0` |Action Message Format 0 || |`apev2` |APEv2 metadata tag |`image`| @@ -26,7 +25,6 @@ |`avc_sps` |H.264/AVC Sequence Parameter Set || |[`avi`](#avi) |Audio Video Interleaved |`avc_au` `hevc_au` `mp3_frame` `flac_frame`| |[`avro_ocf`](#avro_ocf) |Avro object container file || -|[`axml`](#adm) |Audio Definition Model  Chunk |`riff`| |[`bencode`](#bencode) |BitTorrent bencoding || |`bitcoin_blkdat` |Bitcoin blk.dat |`bitcoin_block`| |[`bitcoin_block`](#bitcoin_block) |Bitcoin block |`bitcoin_transaction`| @@ -40,9 +38,7 @@ |`bzip2` |bzip2 compression |`probe`| |[`caff`](#caff) |Live2D Cubism archive |`probe`| |[`cbor`](#cbor) |Concise Binary Object Representation || -|[`chna`](#adm) |Audio Definition Model  Chunk |`riff`| |[`csv`](#csv) |Comma separated values || -|[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Digital Plus) |`riff`| |`dns` |DNS packet || |`dns_tcp` |DNS packet (TCP) || |[`dolby_metadata`](#dolby_metadata) |Dolby Metadata (Atmos, AC3, Dolby Digital) || @@ -184,27 +180,6 @@ Decode value as aac_frame ... | aac_frame({object_type:1}) ``` -## adm -[Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) including 3D Audio. - -RIFF / WAV / Broadcast Wave Format (BWF) chunks: -- `` Chunk, Track UIDs of Audio Definition Model -- `` Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements - -### Examples -Decode ADM configuration from `` and `` chunks: -``` -$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' bwf.wav -``` - -### Authors -- [@johnnymarnell](https://johnnymarnell.github.io), original author - -### References -- https://adm.ebu.io/background/what_is_the_adm.html -- https://tech.ebu.ch/publications/tech3285s7 -- https://tech.ebu.ch/publications/tech3285s5 - ## apple_bookmark Apple BookmarkData. @@ -625,7 +600,7 @@ including Dolby Atmos, AC3, Dolby Digital \[Plus\], and Dolby Audio Info (e.g. L ### Examples Decode Dolby metadata from `` chunk: ``` -$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' bwf.wav +$ fq -d wav '.chunks[] | select(.id | IN("dbmd")) | tovalue' adm-bwf.wav ``` RIFF / WAV / Broadcast Wave Format (BWF) chunks: @@ -641,6 +616,7 @@ RIFF / WAV / Broadcast Wave Format (BWF) chunks: - https://tech.ebu.ch/publications/tech3285s5 - https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf - https://github.com/DolbyLaboratories/dbmd-atmos-parser +- https://github.com/MediaArea/MediaInfoLib/tree/Source/MediaInfo/Audio/File_DolbyAudioMetadata.cpp ## fit Garmin Flexible and Interoperable Data Transfer. @@ -834,6 +810,7 @@ LevelDB Table. - Zstandard uncompression is not implemented yet. ### Authors + - [@mikez](https://github.com/mikez), original author ### References diff --git a/format/riff/dolby_metadata.go b/format/riff/dolby_metadata.go index 5d8cd4414..4679e3f06 100644 --- a/format/riff/dolby_metadata.go +++ b/format/riff/dolby_metadata.go @@ -83,6 +83,19 @@ var compressionDescMap = scalar.UintMapSymStr{ 5: "speech", } +var downmix5to2DescMap = scalar.UintMap{ + 0: {Sym: "not_indicated", Description: "Not indicated (Lo/Ro)"}, + 1: {Sym: "loro", Description: "Lo/Ro"}, + 2: {Sym: "ltrt_dpl", Description: "Lt/Rt (Dolby Pro Logic)"}, + 3: {Sym: "ltrt_dpl2", Description: "Lt/Rt (Dolby Pro Logic II)"}, + 4: {Sym: "direct_stereo_render", Description: "Direct stereo render"}, +} + +var phaseShift5to2DescMap = scalar.UintMap{ + 0: {Sym: "no_shift", Description: "Without Phase 90"}, + 1: {Sym: "shift_90", Description: "With Phase 90"}, +} + var bitstreamModeMap = scalar.UintMapDescription{ 0b000: "main audio service: complete main (CM)", 0b001: "main audio service: music and effects (ME)", @@ -105,10 +118,10 @@ var binauralRenderModeMap = scalar.UintMapSymStr{ } var warpModeMap = scalar.UintMap{ - 0: {Sym: "normal"}, - 1: {Sym: "warping"}, - 2: {Sym: "downmix_dolby_pro_logic_iix"}, - 3: {Sym: "downmix_loro"}, + 0: {Sym: "normal", Description: "possibly: Direct render"}, + 1: {Sym: "warping", Description: "possibly: Direct render with room balance"}, + 2: {Sym: "downmix_dolby_pro_logic_iix", Description: "Dolby Pro Logic IIx"}, + 3: {Sym: "downmix_loro", Description: "possibly: Standard (Lo/Ro)"}, 4: {Sym: "not_indicated", Description: "Default warping will be applied"}, } @@ -236,29 +249,35 @@ func parseAudioInfo(d *decode.D) { } func parseDolbyAtmos(d *decode.D) { - // TODO: both these are fixed size null terminated strings? d.FieldUTF8NullFixedLen("atmos_dbmd_content_creation_preamble", 32) d.FieldUTF8NullFixedLen("atmos_dbmd_content_creation_tool", 64) d.FieldStruct("version", func(d *decode.D) { d.FieldU8("major") d.FieldU8("minor") - d.FieldU8("micro") + d.FieldU8("patch") }) + // TODO: All these unknowns? (mostly from MediaInfoLib, also Dolby repo) + + d.FieldRawLen("unknown0", 21*8) + + d.FieldRawLen("unknown1", 1) + d.FieldU3("downmix_5to2", scalar.UintSym("unknown"), downmix5to2DescMap) + d.FieldRawLen("unknown2", 2) + d.FieldU2("phaseshift_90deg_5to2", scalar.UintSym("unknown"), phaseShift5to2DescMap) - // TODO: what is this? - d.FieldRawLen("unknown0", 53*8) + d.FieldRawLen("unknown3", 12*8) - d.FieldU8("warp_mode", warpModeMap) + d.FieldRawLen("bed_distribution", 2) + d.FieldRawLen("reserved0", 3) + d.FieldU3("warp_mode", warpModeMap) - // TODO: what is this? - d.FieldRawLen("unknown1", 15*8) - d.FieldRawLen("unknown2", 80*8) + d.FieldRawLen("unknown4", 15*8) + d.FieldRawLen("unknown5", 80*8) } func parseDolbyAtmosSupplemental(d *decode.D) { d.FieldU32("dasms_sync", d.UintAssert(0xf8726fbd), scalar.UintHex) - // TODO: wav.go sets LE default i think? objectCount := int64(d.FieldU16("object_count")) d.FieldU8("reserved") diff --git a/format/riff/dolby_metadata.md b/format/riff/dolby_metadata.md index f3e952e5f..19386780f 100644 --- a/format/riff/dolby_metadata.md +++ b/format/riff/dolby_metadata.md @@ -20,3 +20,4 @@ RIFF / WAV / Broadcast Wave Format (BWF) chunks: - https://tech.ebu.ch/publications/tech3285s5 - https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf - https://github.com/DolbyLaboratories/dbmd-atmos-parser +- https://github.com/MediaArea/MediaInfoLib/tree/Source/MediaInfo/Audio/File_DolbyAudioMetadata.cpp From 90adba3cbb9f1c94478679bd12d550f0823258d8 Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Thu, 31 Oct 2024 10:53:21 -0400 Subject: [PATCH 11/13] Address latest CR, temp using old code as supplemental segment broke? --- README.md | 2 +- doc/formats.md | 47 ++++++++++++++++++++- format/riff/adm.go | 30 ------------- format/riff/adm.md | 22 ---------- format/riff/aiff.go | 2 +- format/riff/avi.go | 2 +- format/riff/common.go | 10 ++++- format/riff/dolby.go | 34 +++++++-------- format/riff/dolby_metadata.go | 79 ++++++++++++++++------------------- format/riff/dolby_metadata.md | 2 +- format/riff/wav.go | 27 ++++++++++-- format/riff/wav.md | 39 +++++++++++++++++ format/riff/webp.go | 2 +- 13 files changed, 174 insertions(+), 124 deletions(-) delete mode 100644 format/riff/adm.go delete mode 100644 format/riff/adm.md create mode 100644 format/riff/wav.md diff --git a/README.md b/README.md index 4277eec52..214bf5640 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ vp9_cfm, vp9_frame, vpx_ccr, [wasm](doc/formats.md#wasm), -wav, +[wav](doc/formats.md#wav), webp, [xml](doc/formats.md#xml), yaml, diff --git a/doc/formats.md b/doc/formats.md index e0138c63a..beb3937d1 100644 --- a/doc/formats.md +++ b/doc/formats.md @@ -130,7 +130,7 @@ |`vp9_frame` |VP9 frame || |`vpx_ccr` |VPX Codec Configuration Record || |[`wasm`](#wasm) |WebAssembly Binary Format || -|`wav` |WAV file |`id3v2` `id3v1` `id3v11` `dolby_metadata`| +|[`wav`](#wav) |WAV file |`id3v2` `id3v1` `id3v11` `dolby_metadata`| |`webp` |WebP image |`exif` `vp8_frame` `icc_profile` `xml`| |[`xml`](#xml) |Extensible Markup Language || |`yaml` |YAML Ain't Markup Language || @@ -616,7 +616,7 @@ RIFF / WAV / Broadcast Wave Format (BWF) chunks: - https://tech.ebu.ch/publications/tech3285s5 - https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf - https://github.com/DolbyLaboratories/dbmd-atmos-parser -- https://github.com/MediaArea/MediaInfoLib/tree/Source/MediaInfo/Audio/File_DolbyAudioMetadata.cpp +- https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/Audio/File_DolbyAudioMetadata.cpp ## fit Garmin Flexible and Interoperable Data Transfer. @@ -1470,6 +1470,49 @@ $ fq '.sections | {import: map(select(.id == "import_section").content.im.x[].nm ### References - https://webassembly.github.io/spec/core/ +## wav +WAV file. + +WAVE audio file format. + +Also includes support for [Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) and 3D Audio. + +RIFF / WAV / Broadcast Wave Format (BWF) chunks: + +- `RIFF`: primary container chunk specifying the file type and containing sub-chunks (e.g., fmt, data) +- `fmt`: describes format / stream encoding in data chunk +- `data`: indicates size and contains encoded raw sound data +- `bext`: broadcast extension chunk, containing broadcast-specific metadata such as description, originator, creation date, time reference, and more +- `LIST`: organizes additional metadata in sub-chunks, often used to include information like artist, genre, or title in INFO or other standardized formats +- `smpl`: sample metadata chunk, containing looping and sampling information, such as start and end points for loops, sample rate, and MIDI pitch +- `fact`: contains metadata on the original uncompressed data, such as the number of samples, typically used in non-PCM (compressed) formats to aid in playback and synchronization +- `chna`: track UIDs of Audio Definition Model +- `axml`: XML metadata, e.g. for Audio Definition Model ambisonics and elements as in [EBUCore spec](https://tech.ebu.ch/docs/tech/tech3293.pdf) +- `dbmd`: Dolby specific metadata like loudness and binaural settings, see also [`dolby_metadata` format](#dolby_metadata) + + +### Examples +Decode ADM configuration from `` and `` chunks: +```bash +$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' amd-bwf.wav + +# Extract ADM chunk objects definitions xml content +$ fq -r -d wav '.chunks[] | select(.id | IN("axml")) | .xml | tovalue' amd-bwf.wav | tee axml-content.xml +``` + +### Authors +- [@wader](https://github.com/wader), original author +- [@johnnymarnell](https://johnnymarnell.github.io), ADM support + +### References +- http://soundfile.sapp.org/doc/WaveFormat/ +- https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/wavdec.c +- https://tech.ebu.ch/docs/tech/tech3285.pdf +- http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html +- https://adm.ebu.io/background/what_is_the_adm.html +- https://tech.ebu.ch/docs/tech/tech3285s7.pdf +- https://tech.ebu.ch/docs/tech/tech3285s5.pdf + ## xml Extensible Markup Language. diff --git a/format/riff/adm.go b/format/riff/adm.go deleted file mode 100644 index b8955263c..000000000 --- a/format/riff/adm.go +++ /dev/null @@ -1,30 +0,0 @@ -package riff - -// Audio Definition Model -// https://adm.ebu.io/background/what_is_the_adm.html -// https://tech.ebu.ch/publications/tech3285s7 -// https://tech.ebu.ch/publications/tech3285s5 - -import ( - "github.com/wader/fq/pkg/decode" -) - -func chnaDecode(d *decode.D) { - d.FieldU16("num_tracks") - d.FieldU16("num_uids") - d.FieldArray("audio_ids", func(d *decode.D) { - for !d.End() { - d.FieldStruct("audio_id", func(d *decode.D) { - d.FieldU16("track_index") - d.FieldUTF8("uid", 12) - d.FieldUTF8("track_format_id_reference", 14) - d.FieldUTF8("pack_format_id_reference", 11) - d.FieldRawLen("padding", 8) - }) - } - }) -} - -func axmlDecode(d *decode.D) { - d.FieldUTF8("xml", int(d.BitsLeft())/8) -} diff --git a/format/riff/adm.md b/format/riff/adm.md deleted file mode 100644 index 1ae565e20..000000000 --- a/format/riff/adm.md +++ /dev/null @@ -1,22 +0,0 @@ -[Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) including 3D Audio. - -RIFF / WAV / Broadcast Wave Format (BWF) chunks: -- `` Chunk, Track UIDs of Audio Definition Model -- `` Chunk, BWF XML Metadata, e.g. for Audio Definition Model ambisonics and elements - -### Examples -Decode ADM configuration from `` and `` chunks: -```bash -$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' amd-bwf.wav - -# Extract ADM chunk objects definitions xml content -$ fq -r -d wav '.chunks[] | select(.id | IN("axml")) | .xml | tovalue' amd-bwf.wav | tee axml-content.xml -``` - -### Authors -- [@johnnymarnell](https://johnnymarnell.github.io), original author - -### References -- https://adm.ebu.io/background/what_is_the_adm.html -- https://tech.ebu.ch/publications/tech3285s7 -- https://tech.ebu.ch/publications/tech3285s5 \ No newline at end of file diff --git a/format/riff/aiff.go b/format/riff/aiff.go index d02346176..6efcd68c7 100644 --- a/format/riff/aiff.go +++ b/format/riff/aiff.go @@ -54,7 +54,7 @@ func aiffDecode(d *decode.D) any { } return id, size }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "FORM": riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType)) diff --git a/format/riff/avi.go b/format/riff/avi.go index f2c57406b..d35ae3a85 100644 --- a/format/riff/avi.go +++ b/format/riff/avi.go @@ -238,7 +238,7 @@ func aviDecodeEx(d *decode.D, ai format.AVI_In, extendedChunk bool) { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "RIFF": foundRiffType = d.FieldUTF8("type", 4, d.StrAssert(requiredRiffType)) diff --git a/format/riff/common.go b/format/riff/common.go index bfea3a850..05b66ae3e 100644 --- a/format/riff/common.go +++ b/format/riff/common.go @@ -19,11 +19,11 @@ func (p path) topData() any { return p[len(p)-1].data } -func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path, size int64) (bool, any)) { +func riffDecode(d *decode.D, path path, headFn func(d *decode.D, path path) (string, int64), chunkFn func(d *decode.D, id string, path path) (bool, any)) { id, size := headFn(d, path) d.FramedFn(size*8, func(d *decode.D) { - hasChildren, data := chunkFn(d, id, path, size) + hasChildren, data := chunkFn(d, id, path) if hasChildren { np := append(path, pathEntry{id: id, data: data}) d.FieldArray("chunks", func(d *decode.D) { @@ -58,6 +58,12 @@ var chunkIDDescriptions = scalar.StrMapDescription{ "dmlh": "Extended AVI header", + "data": "Raw sound encoded data", + "bext": "Broadcast extension, e.g. creator, date, etc.", + "smpl": "Sample metadata, e.g. loop points", + "fact": "Original info used for compression, e.g. sample length", + + // BWF ADM master and Dolby Metadata "chna": "Track UIDs of Audio Definition Model", "axml": "Audio Definition Model ambisonics and elements", "dbmd": "Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus]", diff --git a/format/riff/dolby.go b/format/riff/dolby.go index ce4d90cb7..32f5a30da 100644 --- a/format/riff/dolby.go +++ b/format/riff/dolby.go @@ -12,7 +12,7 @@ import ( "github.com/wader/fq/pkg/scalar" ) -func tmp_dbmdDecode(d *decode.D, size int64) any { +func old_dbmdDecode(d *decode.D) any { version := d.U32() major := (version >> 24) & 0xFF minor := (version >> 16) & 0xFF @@ -35,32 +35,32 @@ func tmp_dbmdDecode(d *decode.D, size int64) any { } segmentSize := d.FieldU16("metadata_segment_size") - bitsLeft := d.BitsLeft() + // bitsLeft := d.BitsLeft() switch segmentID { case 1: - parseDolbyE(d) + tmp_parseDolbyE(d) case 3: - parseDolbyDigital(d) + tmp_parseDolbyDigital(d) case 7: - parseDolbyDigitalPlus(d) + tmp_parseDolbyDigitalPlus(d) case 8: - parseAudioInfo(d) + tmp_parseAudioInfo(d) case 9: - parseDolbyAtmos(d) + tmp_parseDolbyAtmos(d) case 10: - parseDolbyAtmosSupplemental(d) + tmp_parseDolbyAtmosSupplemental(d) default: d.FieldRawLen("unknown_segment_raw", int64(segmentSize*8)) } - bytesRemaining := (bitsLeft-d.BitsLeft())/8 - int64(segmentSize) - if bytesRemaining < 0 { - d.Fatalf("Read too many bytes for segment %d, read %d over, expected %d", segmentID, -bytesRemaining, segmentSize) - } else if bytesRemaining > 0 { - d.FieldValueUint("SKIPPED_BYTES", uint64(bytesRemaining)) - d.SeekRel((int64(segmentSize) - bytesRemaining) * 8) - } + // bytesRemaining := (bitsLeft-d.BitsLeft())/8 - int64(segmentSize) + // if bytesRemaining < 0 { + // d.Fatalf("Read too many bytes for segment %d, read %d over, expected %d", segmentID, -bytesRemaining, segmentSize) + // } else if bytesRemaining > 0 { + // d.FieldValueUint("SKIPPED_BYTES", uint64(bytesRemaining)) + // d.SeekRel((int64(segmentSize) - bytesRemaining) * 8) + // } d.FieldU8("metadata_segment_checksum") }) @@ -224,7 +224,7 @@ func tmp_parseAudioInfo(d *decode.D) { d.FieldUTF8("segment_modified_date", 32) } -func tmp_parseDolbyAtmos(d *decode.D, size uint64) { +func tmp_parseDolbyAtmos(d *decode.D) { d.FieldValueStr("metadata_segment_type", "dolby_atmos") // d.SeekRel(32 * 8) @@ -248,7 +248,7 @@ func tmp_parseDolbyAtmos(d *decode.D, size uint64) { d.SeekRel(80 * 8) } -func tmp_parseDolbyAtmosSupplemental(d *decode.D, size uint64) { +func tmp_parseDolbyAtmosSupplemental(d *decode.D) { d.FieldValueStr("metadata_segment_type", "dolby_atmos_supplemental") sync := d.FieldU32LE("dasms_sync") diff --git a/format/riff/dolby_metadata.go b/format/riff/dolby_metadata.go index 4679e3f06..56a521139 100644 --- a/format/riff/dolby_metadata.go +++ b/format/riff/dolby_metadata.go @@ -42,7 +42,9 @@ func dbmdDecode(d *decode.D) any { for !seenEnd { d.FieldStruct("metadata_segment", func(d *decode.D) { segmentID := d.FieldU8("id", metadataSegmentTypeMap) - if segmentID == 0 { + + // TODO(jmarnell): This will always make an empty end segment, I think it would be better to omit it + if segmentID == metadataSegmentTypeEnd { seenEnd = true return } @@ -50,11 +52,11 @@ func dbmdDecode(d *decode.D) any { segmentSize := d.FieldU16("size") switch segmentID { - case metadataSegmentTypeDolbyEMetadata: + case metadataSegmentTypeDolbyE: parseDolbyE(d) - case metadataSegmentTypeDolbyEDigitaletadata: + case metadataSegmentTypeDolbyDigital: parseDolbyDigital(d) - case metadataSegmentTypeDolbyDigitalPlusMetadata: + case metadataSegmentTypeDolbyDigitalPlus: parseDolbyDigitalPlus(d) case metadataSegmentTypeAudioInfo: parseAudioInfo(d) @@ -66,6 +68,7 @@ func dbmdDecode(d *decode.D) any { d.FieldRawLen("unknown", int64(segmentSize*8)) } + // TODO: use this to validate parsing d.FieldU8("checksum", scalar.UintHex) }) } @@ -138,31 +141,31 @@ var trimConfigName = scalar.UintMapDescription{ } const ( - metadataSegmentTypeEnd = 0 - metadataSegmentTypeDolbyEMetadata = 1 - metadataSegmentTypeDolbyReserved2 = 2 - metadataSegmentTypeDolbyEDigitaletadata = 3 - metadataSegmentTypeDolbyReserved4 = 4 - metadataSegmentTypeDolbyReserved5 = 5 - metadataSegmentTypeDolbyReserved6 = 6 - metadataSegmentTypeDolbyDigitalPlusMetadata = 7 - metadataSegmentTypeAudioInfo = 8 - metadataSegmentTypeDolbyAtmos = 9 - metadataSegmentTypeDolbyAtmosSupplemental = 10 + metadataSegmentTypeEnd = 0 + metadataSegmentTypeDolbyE = 1 + metadataSegmentTypeDolbyReserved2 = 2 + metadataSegmentTypeDolbyDigital = 3 + metadataSegmentTypeDolbyReserved4 = 4 + metadataSegmentTypeDolbyReserved5 = 5 + metadataSegmentTypeDolbyReserved6 = 6 + metadataSegmentTypeDolbyDigitalPlus = 7 + metadataSegmentTypeAudioInfo = 8 + metadataSegmentTypeDolbyAtmos = 9 + metadataSegmentTypeDolbyAtmosSupplemental = 10 ) var metadataSegmentTypeMap = scalar.UintMapSymStr{ - metadataSegmentTypeEnd: "end", - metadataSegmentTypeDolbyEMetadata: "dolby_e_metadata", - metadataSegmentTypeDolbyReserved2: "reserved2", - metadataSegmentTypeDolbyEDigitaletadata: "dolby_e_digitale_tadata", - metadataSegmentTypeDolbyReserved4: "reserved4", - metadataSegmentTypeDolbyReserved5: "reserved5", - metadataSegmentTypeDolbyReserved6: "reserved6", - metadataSegmentTypeDolbyDigitalPlusMetadata: "dolby_digital_plus_metadata", - metadataSegmentTypeAudioInfo: "audio_info", - metadataSegmentTypeDolbyAtmos: "dolby_atmos", - metadataSegmentTypeDolbyAtmosSupplemental: "dolby_atmos_supplemental", + metadataSegmentTypeEnd: "end", + metadataSegmentTypeDolbyE: "dolby_e_metadata", + metadataSegmentTypeDolbyReserved2: "reserved2", + metadataSegmentTypeDolbyDigital: "dolby_digital_metadata", + metadataSegmentTypeDolbyReserved4: "reserved4", + metadataSegmentTypeDolbyReserved5: "reserved5", + metadataSegmentTypeDolbyReserved6: "reserved6", + metadataSegmentTypeDolbyDigitalPlus: "dolby_digital_plus_metadata", + metadataSegmentTypeAudioInfo: "audio_info", + metadataSegmentTypeDolbyAtmos: "dolby_atmos", + metadataSegmentTypeDolbyAtmosSupplemental: "dolby_atmos_supplemental", } func parseDolbyE(d *decode.D) { @@ -283,27 +286,19 @@ func parseDolbyAtmosSupplemental(d *decode.D) { i := 0 d.FieldStructNArray("trim_configs", "trim_config", 9, func(d *decode.D) { - d.FieldRawLen("reserved", 7) - d.FieldU1("type", scalar.UintMapSymStr{ + d.FieldRawLen("reserved0", 7) + trimType := d.FieldU1("type", scalar.UintMapSymStr{ 0: "manual", 1: "automatic", }) d.FieldValueStr("config_name", trimConfigName[uint64(i)]) - // TODO: this is null separted list of def strings? - d.FieldUTF8("raw", 14) - // str := d.UTF8(14) - // bytes := []byte(str) - // var nonZeroBytes []string - // for _, b := range bytes { - // if b != 0 { - // nonZeroBytes = append(nonZeroBytes, fmt.Sprintf("%d", b)) - // } - // } - // TODO(jmarnell): I think the +3dB trim settings are here. - // Would like this at least as an array of numbers, instead of this CSV string - // d.FieldValueStr("trim_defs", strings.Join(nonZeroBytes, ", ")) - + if trimType == 1 { + d.FieldUTF8("reserved1", 14) + } else { + // TODO: Reference MediaInfo's logic and Dolby pdf's + d.FieldUTF8("manual_trim_raw_config", 14) + } i++ }) diff --git a/format/riff/dolby_metadata.md b/format/riff/dolby_metadata.md index 19386780f..dd74b09ca 100644 --- a/format/riff/dolby_metadata.md +++ b/format/riff/dolby_metadata.md @@ -20,4 +20,4 @@ RIFF / WAV / Broadcast Wave Format (BWF) chunks: - https://tech.ebu.ch/publications/tech3285s5 - https://tech.ebu.ch/files/live/sites/tech/files/shared/tech/tech3285s6.pdf - https://github.com/DolbyLaboratories/dbmd-atmos-parser -- https://github.com/MediaArea/MediaInfoLib/tree/Source/MediaInfo/Audio/File_DolbyAudioMetadata.cpp +- https://github.com/MediaArea/MediaInfoLib/blob/master/Source/MediaInfo/Audio/File_DolbyAudioMetadata.cpp diff --git a/format/riff/wav.go b/format/riff/wav.go index aacc11833..5fb9dbf1a 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -8,12 +8,17 @@ package riff // TODO: default little endian import ( + "embed" + "github.com/wader/fq/format" "github.com/wader/fq/pkg/decode" "github.com/wader/fq/pkg/interp" "github.com/wader/fq/pkg/scalar" ) +//go:embed wav.md +var wavFS embed.FS + var wavHeaderGroup decode.Group var wavFooterGroup decode.Group var wavDolbyMetadataGroup decode.Group @@ -32,6 +37,7 @@ func init() { {Groups: []*decode.Group{format.Dolby_Metadata}, Out: &wavDolbyMetadataGroup}, }, }) + interp.RegisterFS(wavFS) } const ( @@ -78,7 +84,7 @@ func wavDecode(d *decode.D) any { return id, size }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(wavRiffType)) @@ -161,13 +167,26 @@ func wavDecode(d *decode.D) any { return false, nil case "chna": - chnaDecode(d) + d.FieldU16("num_tracks") + d.FieldU16("num_uids") + d.FieldArray("audio_ids", func(d *decode.D) { + for !d.End() { + d.FieldStruct("audio_id", func(d *decode.D) { + d.FieldU16("track_index") + d.FieldUTF8("uid", 12) + d.FieldUTF8("track_format_id_reference", 14) + d.FieldUTF8("pack_format_id_reference", 11) + d.FieldRawLen("padding", 8) + }) + } + }) return false, nil case "axml": - axmlDecode(d) + d.FieldUTF8("xml", int(d.BitsLeft())/8) return false, nil case "dbmd": - d.Format(&wavDolbyMetadataGroup, nil) + // TEMP TEMP TEMP: delete old dolby.go and bring uncomment + old_dbmdDecode(d) // d.Format(&wavDolbyMetadataGroup, nil) return false, nil default: diff --git a/format/riff/wav.md b/format/riff/wav.md new file mode 100644 index 000000000..a9cb2658a --- /dev/null +++ b/format/riff/wav.md @@ -0,0 +1,39 @@ +WAVE audio file format. + +Also includes support for [Audio Definition Model](https://adm.ebu.io/background/what_is_the_adm.html) and 3D Audio. + +RIFF / WAV / Broadcast Wave Format (BWF) chunks: + +- `RIFF`: primary container chunk specifying the file type and containing sub-chunks (e.g., fmt, data) +- `fmt`: describes format / stream encoding in data chunk +- `data`: indicates size and contains encoded raw sound data +- `bext`: broadcast extension chunk, containing broadcast-specific metadata such as description, originator, creation date, time reference, and more +- `LIST`: organizes additional metadata in sub-chunks, often used to include information like artist, genre, or title in INFO or other standardized formats +- `smpl`: sample metadata chunk, containing looping and sampling information, such as start and end points for loops, sample rate, and MIDI pitch +- `fact`: contains metadata on the original uncompressed data, such as the number of samples, typically used in non-PCM (compressed) formats to aid in playback and synchronization +- `chna`: track UIDs of Audio Definition Model +- `axml`: XML metadata, e.g. for Audio Definition Model ambisonics and elements as in [EBUCore spec](https://tech.ebu.ch/docs/tech/tech3293.pdf) +- `dbmd`: Dolby specific metadata like loudness and binaural settings, see also [`dolby_metadata` format](#dolby_metadata) + + +### Examples +Decode ADM configuration from `` and `` chunks: +```bash +$ fq -d wav '.chunks[] | select(.id | IN("chna", "axml")) | tovalue' amd-bwf.wav + +# Extract ADM chunk objects definitions xml content +$ fq -r -d wav '.chunks[] | select(.id | IN("axml")) | .xml | tovalue' amd-bwf.wav | tee axml-content.xml +``` + +### Authors +- [@wader](https://github.com/wader), original author +- [@johnnymarnell](https://johnnymarnell.github.io), ADM support + +### References +- http://soundfile.sapp.org/doc/WaveFormat/ +- https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/wavdec.c +- https://tech.ebu.ch/docs/tech/tech3285.pdf +- http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html +- https://adm.ebu.io/background/what_is_the_adm.html +- https://tech.ebu.ch/docs/tech/tech3285s7.pdf +- https://tech.ebu.ch/docs/tech/tech3285s5.pdf diff --git a/format/riff/webp.go b/format/riff/webp.go index de69d6238..ebc0bbc59 100644 --- a/format/riff/webp.go +++ b/format/riff/webp.go @@ -44,7 +44,7 @@ func webpDecode(d *decode.D) any { size := d.FieldU32("size") return id, int64(size) }, - func(d *decode.D, id string, path path, size int64) (bool, any) { + func(d *decode.D, id string, path path) (bool, any) { switch id { case "RIFF": riffType = d.FieldUTF8("format", 4, d.StrAssert(webpRiffType)) From 202b723e1eec4efa64868d2f1cadbfa869ab717a Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Thu, 31 Oct 2024 11:04:11 -0400 Subject: [PATCH 12/13] Updating existing tests, rm my failing --- format/riff/testdata/bext.wav.fqtest | 2 +- format/riff/testdata/dolby_metadata.fqtest | 192 --------------------- format/riff/testdata/end-of-file.fqtest | 2 +- format/riff/testdata/stereo.fqtest | 2 +- format/riff/wav.go | 3 +- format/riff/wav.md | 2 +- 6 files changed, 6 insertions(+), 197 deletions(-) delete mode 100644 format/riff/testdata/dolby_metadata.fqtest diff --git a/format/riff/testdata/bext.wav.fqtest b/format/riff/testdata/bext.wav.fqtest index 874ffab58..ef32db4a2 100644 --- a/format/riff/testdata/bext.wav.fqtest +++ b/format/riff/testdata/bext.wav.fqtest @@ -5,7 +5,7 @@ $ fq dv bext.wav 0x000| 57 41 56 45 | WAVE | format: "WAVE" (valid) 0x8-0xc (4) | | | chunks[0:1]: 0xc-0x26e (610) | | | [0]{}: chunk 0xc-0x26e (610) -0x000| 62 65 78 74| bext| id: "bext" 0xc-0x10 (4) +0x000| 62 65 78 74| bext| id: "bext" (Broadcast extension, e.g. creator, date, etc.) 0xc-0x10 (4) 0x010|5a 02 00 00 |Z... | size: 602 0x10-0x14 (4) 0x010| 00 00 00 00 00 00 00 00 00 00 00 00| ............| description: "" 0x14-0x114 (256) 0x020|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| diff --git a/format/riff/testdata/dolby_metadata.fqtest b/format/riff/testdata/dolby_metadata.fqtest deleted file mode 100644 index cc9bc6436..000000000 --- a/format/riff/testdata/dolby_metadata.fqtest +++ /dev/null @@ -1,192 +0,0 @@ - |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: ./format/riff/testdata/dolby_metadata.wav (wav) 0x0-0x2aec (10988) -0x0000|52 49 46 46 |RIFF | id: "RIFF" 0x0-0x4 (4) -0x0000| e4 2a 00 00 | .*.. | size: 10980 0x4-0x8 (4) -0x0000| 57 41 56 45 | WAVE | format: "WAVE" (valid) 0x8-0xc (4) - | | | chunks[0:6]: 0xc-0x2aeb (10975) - | | | [0]{}: chunk 0xc-0x54 (72) -0x0000| 4a 55 4e 4b| JUNK| id: "JUNK" (Alignment) 0xc-0x10 (4) -0x0010|40 00 00 00 |@... | size: 64 0x10-0x14 (4) -0x0010| 00 00 00 00 00 00 00 00 00 00 00 00| ............| data: raw bits 0x14-0x54 (64) -0x0020|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| -* |until 0x53.7 (64) | | - | | | [1]{}: chunk 0x54-0x6c (24) -0x0050| 66 6d 74 20 | fmt | id: "fmt" 0x54-0x58 (4) -0x0050| 10 00 00 00 | .... | size: 16 0x58-0x5c (4) -0x0050| 01 00 | .. | audio_format: "pcm_s16le" (1) 0x5c-0x5e (2) -0x0050| 03 00| ..| num_channels: 3 0x5e-0x60 (2) -0x0060|80 bb 00 00 |.... | sample_rate: 48000 0x60-0x64 (4) -0x0060| 80 97 06 00 | .... | byte_rate: 432000 0x64-0x68 (4) -0x0060| 09 00 | .. | block_align: 9 0x68-0x6a (2) -0x0060| 18 00 | .. | bits_per_sample: 24 0x6a-0x6c (2) - | | | [2]{}: chunk 0x6c-0x1154 (4328) -0x0060| 64 61 74 61| data| id: "data" 0x6c-0x70 (4) -0x0070|e0 10 00 00 |.... | size: 4320 0x70-0x74 (4) -0x0070| 00 00 00 00 00 00 00 00 00 00 00 00| ............| samples: raw bits 0x74-0x1154 (4320) -0x0080|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| -* |until 0x1153.7 (4320) | | - | | | [3]{}: chunk 0x1154-0x2862 (5902) -0x1150| 61 78 6d 6c | axml | id: "axml" (Audio Definition Model ambisonics and elements) 0x1154-0x1158 (4) -0x1150| 06 17 00 00 | .... | size: 5894 0x1158-0x115c (4) -0x1150| 3c 3f 78 6d| \n\n\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tACO_1001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAO_1001\n\t\t\t\t\tAO_100b\n\t\t\t\t\t2\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tATU_00000001\n\t\t\t\t\tATU_00000002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAP_00031001\n\t\t\t\t\tATU_00000003\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011001\n\t\t\t\t\tAC_00011002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00031001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tRC_L\n\t\t\t\t\t\t1\n\t\t\t\t\t\t-1.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tRC_R\n\t\t\t\t\t\t1\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t1\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t1\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011001\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tAT_00011001_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011002\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tAT_00011002_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00031001\n\t\t\t\t\tAP_00031001\n\t\t\t\t\tAT_00031001_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00011002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00031001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00011001_01\n\t\t\t\t\tAP_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00011002_01\n\t\t\t\t\tAP_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00031001_01\n\t\t\t\t\tAP_00031001\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\n" 0x115c-0x2862 (5894) -0x1160|6c 20 76 65 72 73 69 6f 6e 3d 22 31 2e 30 22 20|l version="1.0" | -* |until 0x2861.7 (5894) | | - | | | [4]{}: chunk 0x2862-0x28e6 (132) -0x2860| 63 68 6e 61 | chna | id: "chna" (Track UIDs of Audio Definition Model) 0x2862-0x2866 (4) -0x2860| 7c 00 00 00 | |... | size: 124 0x2866-0x286a (4) -0x2860| 03 00 | .. | num_tracks: 3 0x286a-0x286c (2) -0x2860| 03 00 | .. | num_uids: 3 0x286c-0x286e (2) - | | | audio_ids[0:3]: 0x286e-0x28e6 (120) - | | | [0]{}: audio_id 0x286e-0x2896 (40) -0x2860| 01 00| ..| track_index: 1 0x286e-0x2870 (2) -0x2870|41 54 55 5f 30 30 30 30 30 30 30 31 |ATU_00000001 | uid: "ATU_00000001" 0x2870-0x287c (12) -0x2870| 41 54 5f 30| AT_0| track_format_id_reference: "AT_00011001_01" 0x287c-0x288a (14) -0x2880|30 30 31 31 30 30 31 5f 30 31 |0011001_01 | -0x2880| 41 50 5f 30 30 30| AP_000| pack_format_id_reference: "AP_00011001" 0x288a-0x2895 (11) -0x2890|31 31 30 30 31 |11001 | -0x2890| 00 | . | padding: raw bits 0x2895-0x2896 (1) - | | | [1]{}: audio_id 0x2896-0x28be (40) -0x2890| 02 00 | .. | track_index: 2 0x2896-0x2898 (2) -0x2890| 41 54 55 5f 30 30 30 30| ATU_0000| uid: "ATU_00000002" 0x2898-0x28a4 (12) -0x28a0|30 30 30 32 |0002 | -0x28a0| 41 54 5f 30 30 30 31 31 30 30 32 5f| AT_00011002_| track_format_id_reference: "AT_00011002_01" 0x28a4-0x28b2 (14) -0x28b0|30 31 |01 | -0x28b0| 41 50 5f 30 30 30 31 31 30 30 31 | AP_00011001 | pack_format_id_reference: "AP_00011001" 0x28b2-0x28bd (11) -0x28b0| 00 | . | padding: raw bits 0x28bd-0x28be (1) - | | | [2]{}: audio_id 0x28be-0x28e6 (40) -0x28b0| 03 00| ..| track_index: 3 0x28be-0x28c0 (2) -0x28c0|41 54 55 5f 30 30 30 30 30 30 30 33 |ATU_00000003 | uid: "ATU_00000003" 0x28c0-0x28cc (12) -0x28c0| 41 54 5f 30| AT_0| track_format_id_reference: "AT_00031001_01" 0x28cc-0x28da (14) -0x28d0|30 30 33 31 30 30 31 5f 30 31 |0031001_01 | -0x28d0| 41 50 5f 30 30 30| AP_000| pack_format_id_reference: "AP_00031001" 0x28da-0x28e5 (11) -0x28e0|33 31 30 30 31 |31001 | -0x28e0| 00 | . | padding: raw bits 0x28e5-0x28e6 (1) - | | | [5]{}: chunk 0x28e6-0x2aeb (517) -0x28e0| 64 62 6d 64 | dbmd | id: "dbmd" (Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus]) 0x28e6-0x28ea (4) -0x28e0| fe 01 00 00 | .... | size: 510 0x28ea-0x28ee (4) - | | | version{}: 0x28ee-0x28f2 (4) -0x28e0| 06 | . | major: 6 0x28ee-0x28ef (1) -0x28e0| 00| .| minor: 0 0x28ef-0x28f0 (1) -0x28f0|00 |. | patch: 0 0x28f0-0x28f1 (1) -0x28f0| 01 | . | build: 1 0x28f1-0x28f2 (1) - | | | metadata_segments[0:4]: 0x28f2-0x2aeb (505) - | | | [0]{}: metadata_segment 0x28f2-0x2956 (100) -0x28f0| 07 | . | id: "dolby_digital_plus_metadata" (7) 0x28f2-0x28f3 (1) -0x28f0| 60 00 | `. | size: 96 0x28f3-0x28f5 (2) -0x28f0| 00 | . | program_id: 0 0x28f5-0x28f6 (1) -0x28f0| 47 | G | program_info: 71 0x28f6-0x28f7 (1) - | | | lfe_on: true - | | | bitstream_mode: "main audio service: complete main (CM)" -0x28f0| 00 00 | .. | ddplus_reserved_a: 0 0x28f7-0x28f9 (2) -0x28f0| 00 | . | surround_config: 0 0x28f9-0x28fa (1) -0x28f0| 60 | ` | dialnorm_info: 96 0x28fa-0x28fb (1) -0x28f0| 00 | . | langcod: 0 0x28fb-0x28fc (1) -0x28f0| 00 | . | audio_prod_info: 0 0x28fc-0x28fd (1) -0x28f0| 24 | $ | ext_bsi1_word1: 36 0x28fd-0x28fe (1) -0x28f0| 24 | $ | ext_bsi1_word2: 36 0x28fe-0x28ff (1) -0x28f0| 00| .| ext_bsi2_word1: 0 0x28ff-0x2900 (1) -0x2900|00 00 00 |... | ddplus_reserved_b: 0 0x2900-0x2903 (3) -0x2900| 02 | . | compr1: "film_light" (2) 0x2903-0x2904 (1) -0x2900| 02 | . | dynrng1: "film_light" (2) 0x2904-0x2905 (1) -0x2900| 00 00 00 | ... | ddplus_reserved_c: 0 0x2905-0x2908 (3) -0x2900| 00 | . | ddplus_info1: 0 0x2908-0x2909 (1) -0x2900| 00 00 00 00 00 | ..... | ddplus_reserved_d: 0 0x2909-0x290e (5) -0x2900| 00 00| ..| datarate: 0 0x290e-0x2910 (2) -0x2910|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| reserved_for_future_use: raw bits 0x2910-0x2955 (69) -* |until 0x2954.7 (69) | | -0x2950| ad | . | checksum: 0xad 0x2955-0x2956 (1) - | | | [1]{}: metadata_segment 0x2956-0x2a52 (252) -0x2950| 09 | . | id: "dolby_atmos" (9) 0x2956-0x2957 (1) -0x2950| f8 00 | .. | size: 248 0x2957-0x2959 (2) -0x2950| 43 72 65 61 74 65 64| Created| atmos_dbmd_content_creation_preamble: "Created using Dolby equipment" 0x2959-0x2979 (32) -0x2960|20 75 73 69 6e 67 20 44 6f 6c 62 79 20 65 71 75| using Dolby equ| -0x2970|69 70 6d 65 6e 74 00 00 00 |ipment... | -0x2970| 44 6f 6c 62 79 20 41| Dolby A| atmos_dbmd_content_creation_tool: "Dolby Atmos Composer Essential (fiedler audio)" 0x2979-0x29b9 (64) -0x2980|74 6d 6f 73 20 43 6f 6d 70 6f 73 65 72 20 45 73|tmos Composer Es| -* |until 0x29b8.7 (64) | | - | | | version{}: 0x29b9-0x29bc (3) -0x29b0| 01 | . | major: 1 0x29b9-0x29ba (1) -0x29b0| 00 | . | minor: 0 0x29ba-0x29bb (1) -0x29b0| 01 | . | micro: 1 0x29bb-0x29bc (1) -0x29b0| 00 00 00 00| ....| unknown0: raw bits 0x29bc-0x29f1 (53) -0x29c0|03 00 00 00 00 00 00 00 22 ff 00 00 00 00 00 03|........".......| -* |until 0x29f0.7 (53) | | -0x29f0| 83 | . | warp_mode: 131 0x29f1-0x29f2 (1) -0x29f0| 00 00 00 00 00 00 00 00 00 00 00 00 00 00| ..............| unknown1: raw bits 0x29f2-0x2a01 (15) -0x2a00|00 |. | -0x2a00| f0 00 08 00 00 00 00 00 00 00 00 00 00 00 00| ...............| unknown2: raw bits 0x2a01-0x2a51 (80) -0x2a10|00 f0 00 08 00 00 00 00 00 00 00 00 00 00 00 00|................| -* |until 0x2a50.7 (80) | | -0x2a50| 90 | . | checksum: 0x90 0x2a51-0x2a52 (1) - | | | [2]{}: metadata_segment 0x2a52-0x2aea (152) -0x2a50| 0a | . | id: "dolby_atmos_supplemental" (10) 0x2a52-0x2a53 (1) -0x2a50| 94 00 | .. | size: 148 0x2a53-0x2a55 (2) -0x2a50| bd 6f 72 f8 | .or. | dasms_sync: 0xf8726fbd (valid) 0x2a55-0x2a59 (4) -0x2a50| 03 00 | .. | object_count: 3 0x2a59-0x2a5b (2) -0x2a50| 00 | . | reserved: 0 0x2a5b-0x2a5c (1) - | | | trim_configs[0:9]: 0x2a5c-0x2ae3 (135) - | | | [0]{}: trim_config 0x2a5c-0x2a6b (15) -0x2a50| 01 | . | reserved: raw bits 0x2a5c-0x2a5c.7 (0.7) -0x2a50| 01 | . | type: "automatic" (1) 0x2a5c.7-0x2a5d (0.1) - | | | config_name: "2.0" -0x2a50| 00 00 00| ...| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a5d-0x2a6b (14) -0x2a60|00 00 00 00 00 00 00 00 00 00 00 |........... | - | | | [1]{}: trim_config 0x2a6b-0x2a7a (15) -0x2a60| 01 | . | reserved: raw bits 0x2a6b-0x2a6b.7 (0.7) -0x2a60| 01 | . | type: "automatic" (1) 0x2a6b.7-0x2a6c (0.1) - | | | config_name: "5.1" -0x2a60| 00 00 00 00| ....| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a6c-0x2a7a (14) -0x2a70|00 00 00 00 00 00 00 00 00 00 |.......... | - | | | [2]{}: trim_config 0x2a7a-0x2a89 (15) -0x2a70| 01 | . | reserved: raw bits 0x2a7a-0x2a7a.7 (0.7) -0x2a70| 01 | . | type: "automatic" (1) 0x2a7a.7-0x2a7b (0.1) - | | | config_name: "7.1" -0x2a70| 00 00 00 00 00| .....| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a7b-0x2a89 (14) -0x2a80|00 00 00 00 00 00 00 00 00 |......... | - | | | [3]{}: trim_config 0x2a89-0x2a98 (15) -0x2a80| 01 | . | reserved: raw bits 0x2a89-0x2a89.7 (0.7) -0x2a80| 01 | . | type: "automatic" (1) 0x2a89.7-0x2a8a (0.1) - | | | config_name: "2.1.2" -0x2a80| 00 00 00 00 00 00| ......| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a8a-0x2a98 (14) -0x2a90|00 00 00 00 00 00 00 00 |........ | - | | | [4]{}: trim_config 0x2a98-0x2aa7 (15) -0x2a90| 01 | . | reserved: raw bits 0x2a98-0x2a98.7 (0.7) -0x2a90| 01 | . | type: "automatic" (1) 0x2a98.7-0x2a99 (0.1) - | | | config_name: "5.1.2" -0x2a90| 00 00 00 00 00 00 00| .......| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2a99-0x2aa7 (14) -0x2aa0|00 00 00 00 00 00 00 |....... | - | | | [5]{}: trim_config 0x2aa7-0x2ab6 (15) -0x2aa0| 01 | . | reserved: raw bits 0x2aa7-0x2aa7.7 (0.7) -0x2aa0| 01 | . | type: "automatic" (1) 0x2aa7.7-0x2aa8 (0.1) - | | | config_name: "7.1.2" -0x2aa0| 00 00 00 00 00 00 00 00| ........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2aa8-0x2ab6 (14) -0x2ab0|00 00 00 00 00 00 |...... | - | | | [6]{}: trim_config 0x2ab6-0x2ac5 (15) -0x2ab0| 01 | . | reserved: raw bits 0x2ab6-0x2ab6.7 (0.7) -0x2ab0| 01 | . | type: "automatic" (1) 0x2ab6.7-0x2ab7 (0.1) - | | | config_name: "2.1.4" -0x2ab0| 00 00 00 00 00 00 00 00 00| .........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2ab7-0x2ac5 (14) -0x2ac0|00 00 00 00 00 |..... | - | | | [7]{}: trim_config 0x2ac5-0x2ad4 (15) -0x2ac0| 01 | . | reserved: raw bits 0x2ac5-0x2ac5.7 (0.7) -0x2ac0| 01 | . | type: "automatic" (1) 0x2ac5.7-0x2ac6 (0.1) - | | | config_name: "5.1.4" -0x2ac0| 00 00 00 00 00 00 00 00 00 00| ..........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2ac6-0x2ad4 (14) -0x2ad0|00 00 00 00 |.... | - | | | [8]{}: trim_config 0x2ad4-0x2ae3 (15) -0x2ad0| 01 | . | reserved: raw bits 0x2ad4-0x2ad4.7 (0.7) -0x2ad0| 01 | . | type: "automatic" (1) 0x2ad4.7-0x2ad5 (0.1) - | | | config_name: "7.1.4" -0x2ad0| 00 00 00 00 00 00 00 00 00 00 00| ...........| raw: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 0x2ad5-0x2ae3 (14) -0x2ae0|00 00 00 |... | - | | | objects[0:3]: 0x2ae3-0x2ae6 (3) -0x2ae0| 00 | . | [0]: 0 object_value 0x2ae3-0x2ae4 (1) -0x2ae0| 00 | . | [1]: 0 object_value 0x2ae4-0x2ae5 (1) -0x2ae0| 00 | . | [2]: 0 object_value 0x2ae5-0x2ae6 (1) - | | | binaural_render_modes[0:3]: 0x2ae6-0x2ae9 (3) -0x2ae0| 84 | . | [0]: "not_indicated" (4) render_mode 0x2ae6-0x2ae7 (1) -0x2ae0| 84 | . | [1]: "not_indicated" (4) render_mode 0x2ae7-0x2ae8 (1) -0x2ae0| 84 | . | [2]: "not_indicated" (4) render_mode 0x2ae8-0x2ae9 (1) -0x2ae0| 3e | > | checksum: 0x3e 0x2ae9-0x2aea (1) - | | | [3]{}: metadata_segment 0x2aea-0x2aeb (1) -0x2ae0| 00 | . | id: "end" (0) 0x2aea-0x2aeb (1) -0x2ae0| 00| | .| | gap0: raw bits 0x2aeb-0x2aec (1) diff --git a/format/riff/testdata/end-of-file.fqtest b/format/riff/testdata/end-of-file.fqtest index 434629fea..3e348e95a 100644 --- a/format/riff/testdata/end-of-file.fqtest +++ b/format/riff/testdata/end-of-file.fqtest @@ -25,7 +25,7 @@ $ fq -d wav dv end-of-file.wav 0x030| 4c 61 76 66 35 38 2e 34| Lavf58.4| value: "Lavf58.45.100" 0x38-0x46 (14) 0x040|35 2e 31 30 30 00 |5.100. | | | | [2]{}: chunk 0x46-0x732 (1772) -0x040| 64 61 74 61 | data | id: "data" 0x46-0x4a (4) +0x040| 64 61 74 61 | data | id: "data" (Raw sound encoded data) 0x46-0x4a (4) 0x040| ff ff ff ff | .... | size: 0xffffffff (Rest of file) 0x4a-0x4e (4) 0x040| 00 00| ..| samples: raw bits 0x4e-0x732 (1764) 0x050|00 00 b5 00 b5 00 69 01 69 01 1d 02 1d 02 ce 02|......i.i.......| diff --git a/format/riff/testdata/stereo.fqtest b/format/riff/testdata/stereo.fqtest index 2da687796..c29818786 100644 --- a/format/riff/testdata/stereo.fqtest +++ b/format/riff/testdata/stereo.fqtest @@ -25,7 +25,7 @@ $ fq -d wav dv stereo.wav 0x030| 4c 61 76 66 35 38 2e 32| Lavf58.2| value: "Lavf58.29.100" 0x38-0x46 (14) 0x040|39 2e 31 30 30 00 |9.100. | | | | [2]{}: chunk 0x46-0x732 (1772) -0x040| 64 61 74 61 | data | id: "data" 0x46-0x4a (4) +0x040| 64 61 74 61 | data | id: "data" (Raw sound encoded data) 0x46-0x4a (4) 0x040| e4 06 00 00 | .... | size: 1764 0x4a-0x4e (4) 0x040| 00 00| ..| samples: raw bits 0x4e-0x732 (1764) 0x050|00 00 b5 00 b5 00 69 01 69 01 1d 02 1d 02 ce 02|......i.i.......| diff --git a/format/riff/wav.go b/format/riff/wav.go index 5fb9dbf1a..32a4c52d3 100644 --- a/format/riff/wav.go +++ b/format/riff/wav.go @@ -186,7 +186,8 @@ func wavDecode(d *decode.D) any { return false, nil case "dbmd": // TEMP TEMP TEMP: delete old dolby.go and bring uncomment - old_dbmdDecode(d) // d.Format(&wavDolbyMetadataGroup, nil) + // old_dbmdDecode(d) // + d.Format(&wavDolbyMetadataGroup, nil) return false, nil default: diff --git a/format/riff/wav.md b/format/riff/wav.md index a9cb2658a..a448e3ea0 100644 --- a/format/riff/wav.md +++ b/format/riff/wav.md @@ -27,7 +27,7 @@ $ fq -r -d wav '.chunks[] | select(.id | IN("axml")) | .xml | tovalue' amd-bwf.w ### Authors - [@wader](https://github.com/wader), original author -- [@johnnymarnell](https://johnnymarnell.github.io), ADM support +- [@johnnymarnell](https://johnnymarnell.github.io), ADM and Dolby support ### References - http://soundfile.sapp.org/doc/WaveFormat/ From 351920d92e95385b12c382e5dde36428e149f6b6 Mon Sep 17 00:00:00 2001 From: Johnny Marnell Date: Thu, 31 Oct 2024 11:50:33 -0400 Subject: [PATCH 13/13] =?UTF-8?q?Ok!=20I=20think=20I=20figured=20out=20tes?= =?UTF-8?q?ting,=20added=20docs=20=F0=9F=A4=9E=F0=9F=8F=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/dev.md | 20 +++- format/all/all.fqtest | 1 + format/riff/testdata/dolby_metadata.fqtest | 131 +++++++++++++++++++++ 3 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 format/riff/testdata/dolby_metadata.fqtest diff --git a/doc/dev.md b/doc/dev.md index 508583c1d..44a8dbade 100644 --- a/doc/dev.md +++ b/doc/dev.md @@ -38,13 +38,21 @@ Flags can be struct with bit-fields. - Use commit messages with a context prefix to make it easier to find and understand, ex:
`mp3: Validate sync correctly` - Tests: - - If possible, add one or more pairs of example input file and expected CLI output, with naming like: - - `./format//testdata/.`, e.g. [`./format/mp4/testdata/aac.mp4`](../format/mp4/testdata/aac.mp4) - - and `./format//testdata/.fqtest`, e.g. [`./format/mp4/testdata/aac.fqtest`](../format/mp4/testdata/aac.fqtest) - - The latter contents should be `$ go run . dv ` or `$ go run . 'dv,torepr' ` if there is `torepr` support. + - If possible, add one or more pairs of example input binary file plus an `.fqtest` file with one or more expected CLI commands and their outputs, with naming like: + - `./format//testdata/.`, e.g. [`./format/mp4/testdata/aac.mp4`](../format/mp4/testdata/aac.mp4) + - and `./format//testdata/.fqtest`, e.g. [`./format/mp4/testdata/aac.fqtest`](../format/mp4/testdata/aac.fqtest) + - The `*.fqtest` files use lines prefixed with `$` of commands to run / test (usually `fq` command style of course), and their expected output below them, e.g. in simple [`./pkg/interp/testdata/version.fqtest`](../pkg/interp/testdata/version.fqtest): + ``` + $ fq -v + testversion (testos testarch) + ``` + - A basic test of a (potentially new) format can be specified by creating a single line `./format//testdata/.fqtest` with `$ fq dv .` or `$ fq 'dv,torepr' .` if there is `torepr` support. (Note: for the test file, do not use full path like `./format//testdata/.`) + - You can of course look at your code's current output for a test like this by locally running the same command using `go` and with full path, e.g. `go run . dv ./format//testdata/.` - If `dv` produces a lof of output maybe use `dv({array_truncate: 50})` etc - - Run `go test ./format -run TestFormats/` to test expected output. - - Run `go test ./format -run TestFormats/ -update` to update current output as expected output. + - Run `go test ./format -run TestFormats/` to test expected outputs for all tests under format ``. + - Run `go test ./format -run TestFormats/ -update` to automatically update current (or create, on initial addition of new test files and lines) expected output with the currently locally executing content. + - If you've added a new format, you'll probably also see that the generic `all` test fails, update that as well with: `go test ./format -run TestFormats/all -update` + - Double check that your git diff looks sensible for new or updated `*.fqtest` files (e.g. avoiding checking in test failure output like `exitcode: 2 error: wrong_path.mp4: no such file or directory`) - If you have format specific documentation: - Put it in `format/*/.md` and use `//go:embed .md`/`interp.RegisterFS(..)` to embed/register it. - Use simple markdown, just sections (depth starts at 3, `### Section`), paragraphs, lists and links. diff --git a/format/all/all.fqtest b/format/all/all.fqtest index 7dea22d8b..393e32610 100644 --- a/format/all/all.fqtest +++ b/format/all/all.fqtest @@ -85,6 +85,7 @@ cbor Concise Binary Object Representation csv Comma separated values dns DNS packet dns_tcp DNS packet (TCP) +dolby_metadata Dolby Metadata (Atmos, AC3, Dolby Digital) elf Executable and Linkable Format ether8023_frame Ethernet 802.3 frame exif Exchangeable Image File Format diff --git a/format/riff/testdata/dolby_metadata.fqtest b/format/riff/testdata/dolby_metadata.fqtest new file mode 100644 index 000000000..e3874965a --- /dev/null +++ b/format/riff/testdata/dolby_metadata.fqtest @@ -0,0 +1,131 @@ +$ fq dv foobar.wav + |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: dolby_metadata.wav (wav) 0x0-0x2aec (10988) +0x0000|52 49 46 46 |RIFF | id: "RIFF" 0x0-0x4 (4) +0x0000| e4 2a 00 00 | .*.. | size: 10980 0x4-0x8 (4) +0x0000| 57 41 56 45 | WAVE | format: "WAVE" (valid) 0x8-0xc (4) + | | | chunks[0:6]: 0xc-0x2a40 (10804) + | | | [0]{}: chunk 0xc-0x54 (72) +0x0000| 4a 55 4e 4b| JUNK| id: "JUNK" (Alignment) 0xc-0x10 (4) +0x0010|40 00 00 00 |@... | size: 64 0x10-0x14 (4) +0x0010| 00 00 00 00 00 00 00 00 00 00 00 00| ............| data: raw bits 0x14-0x54 (64) +0x0020|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| +* |until 0x53.7 (64) | | + | | | [1]{}: chunk 0x54-0x6c (24) +0x0050| 66 6d 74 20 | fmt | id: "fmt" 0x54-0x58 (4) +0x0050| 10 00 00 00 | .... | size: 16 0x58-0x5c (4) +0x0050| 01 00 | .. | audio_format: "pcm_s16le" (1) 0x5c-0x5e (2) +0x0050| 03 00| ..| num_channels: 3 0x5e-0x60 (2) +0x0060|80 bb 00 00 |.... | sample_rate: 48000 0x60-0x64 (4) +0x0060| 80 97 06 00 | .... | byte_rate: 432000 0x64-0x68 (4) +0x0060| 09 00 | .. | block_align: 9 0x68-0x6a (2) +0x0060| 18 00 | .. | bits_per_sample: 24 0x6a-0x6c (2) + | | | [2]{}: chunk 0x6c-0x1154 (4328) +0x0060| 64 61 74 61| data| id: "data" (Raw sound encoded data) 0x6c-0x70 (4) +0x0070|e0 10 00 00 |.... | size: 4320 0x70-0x74 (4) +0x0070| 00 00 00 00 00 00 00 00 00 00 00 00| ............| samples: raw bits 0x74-0x1154 (4320) +0x0080|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| +* |until 0x1153.7 (4320) | | + | | | [3]{}: chunk 0x1154-0x2862 (5902) +0x1150| 61 78 6d 6c | axml | id: "axml" (Audio Definition Model ambisonics and elements) 0x1154-0x1158 (4) +0x1150| 06 17 00 00 | .... | size: 5894 0x1158-0x115c (4) +0x1150| 3c 3f 78 6d| \n\n\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tACO_1001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAO_1001\n\t\t\t\t\tAO_100b\n\t\t\t\t\t2\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tATU_00000001\n\t\t\t\t\tATU_00000002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAP_00031001\n\t\t\t\t\tATU_00000003\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011001\n\t\t\t\t\tAC_00011002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00031001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tRC_L\n\t\t\t\t\t\t1\n\t\t\t\t\t\t-1.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tRC_R\n\t\t\t\t\t\t1\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t1\n\t\t\t\t\t\t0.0000000000\n\t\t\t\t\t\t1.0000000000\n\t\t\t\t\t\t1\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011001\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tAT_00011001_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00011002\n\t\t\t\t\tAP_00011001\n\t\t\t\t\tAT_00011002_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAC_00031001\n\t\t\t\t\tAP_00031001\n\t\t\t\t\tAT_00031001_01\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00011002\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAS_00031001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00011001_01\n\t\t\t\t\tAP_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00011002_01\n\t\t\t\t\tAP_00011001\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAT_00031001_01\n\t\t\t\t\tAP_00031001\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\n" 0x115c-0x2862 (5894) +0x1160|6c 20 76 65 72 73 69 6f 6e 3d 22 31 2e 30 22 20|l version="1.0" | +* |until 0x2861.7 (5894) | | + | | | [4]{}: chunk 0x2862-0x28e6 (132) +0x2860| 63 68 6e 61 | chna | id: "chna" (Track UIDs of Audio Definition Model) 0x2862-0x2866 (4) +0x2860| 7c 00 00 00 | |... | size: 124 0x2866-0x286a (4) +0x2860| 03 00 | .. | num_tracks: 3 0x286a-0x286c (2) +0x2860| 03 00 | .. | num_uids: 3 0x286c-0x286e (2) + | | | audio_ids[0:3]: 0x286e-0x28e6 (120) + | | | [0]{}: audio_id 0x286e-0x2896 (40) +0x2860| 01 00| ..| track_index: 1 0x286e-0x2870 (2) +0x2870|41 54 55 5f 30 30 30 30 30 30 30 31 |ATU_00000001 | uid: "ATU_00000001" 0x2870-0x287c (12) +0x2870| 41 54 5f 30| AT_0| track_format_id_reference: "AT_00011001_01" 0x287c-0x288a (14) +0x2880|30 30 31 31 30 30 31 5f 30 31 |0011001_01 | +0x2880| 41 50 5f 30 30 30| AP_000| pack_format_id_reference: "AP_00011001" 0x288a-0x2895 (11) +0x2890|31 31 30 30 31 |11001 | +0x2890| 00 | . | padding: raw bits 0x2895-0x2896 (1) + | | | [1]{}: audio_id 0x2896-0x28be (40) +0x2890| 02 00 | .. | track_index: 2 0x2896-0x2898 (2) +0x2890| 41 54 55 5f 30 30 30 30| ATU_0000| uid: "ATU_00000002" 0x2898-0x28a4 (12) +0x28a0|30 30 30 32 |0002 | +0x28a0| 41 54 5f 30 30 30 31 31 30 30 32 5f| AT_00011002_| track_format_id_reference: "AT_00011002_01" 0x28a4-0x28b2 (14) +0x28b0|30 31 |01 | +0x28b0| 41 50 5f 30 30 30 31 31 30 30 31 | AP_00011001 | pack_format_id_reference: "AP_00011001" 0x28b2-0x28bd (11) +0x28b0| 00 | . | padding: raw bits 0x28bd-0x28be (1) + | | | [2]{}: audio_id 0x28be-0x28e6 (40) +0x28b0| 03 00| ..| track_index: 3 0x28be-0x28c0 (2) +0x28c0|41 54 55 5f 30 30 30 30 30 30 30 33 |ATU_00000003 | uid: "ATU_00000003" 0x28c0-0x28cc (12) +0x28c0| 41 54 5f 30| AT_0| track_format_id_reference: "AT_00031001_01" 0x28cc-0x28da (14) +0x28d0|30 30 33 31 30 30 31 5f 30 31 |0031001_01 | +0x28d0| 41 50 5f 30 30 30| AP_000| pack_format_id_reference: "AP_00031001" 0x28da-0x28e5 (11) +0x28e0|33 31 30 30 31 |31001 | +0x28e0| 00 | . | padding: raw bits 0x28e5-0x28e6 (1) + | | | [5]{}: chunk 0x28e6-0x2a40 (346) +0x28e0| 64 62 6d 64 | dbmd | id: "dbmd" (Dolby Metadata, e.g. Atmos, AC3, Dolby Digital [Plus]) 0x28e6-0x28ea (4) +0x28e0| fe 01 00 00 | .... | size: 510 0x28ea-0x28ee (4) + | | | version{}: 0x28ee-0x28f2 (4) +0x28e0| 06 | . | major: 6 0x28ee-0x28ef (1) +0x28e0| 00| .| minor: 0 0x28ef-0x28f0 (1) +0x28f0|00 |. | patch: 0 0x28f0-0x28f1 (1) +0x28f0| 01 | . | build: 1 0x28f1-0x28f2 (1) + | | | metadata_segments[0:3]: 0x28f2-0x2a40 (334) + | | | [0]{}: metadata_segment 0x28f2-0x2956 (100) +0x28f0| 07 | . | id: "dolby_digital_plus_metadata" (7) 0x28f2-0x28f3 (1) +0x28f0| 60 00 | `. | size: 96 0x28f3-0x28f5 (2) +0x28f0| 00 | . | program_id: 0 0x28f5-0x28f6 (1) +0x28f0| 47 | G | program_info: 71 0x28f6-0x28f7 (1) + | | | lfe_on: true + | | | bitstream_mode: "main audio service: complete main (CM)" +0x28f0| 00 00 | .. | ddplus_reserved_a: 0 0x28f7-0x28f9 (2) +0x28f0| 00 | . | surround_config: 0 0x28f9-0x28fa (1) +0x28f0| 60 | ` | dialnorm_info: 96 0x28fa-0x28fb (1) +0x28f0| 00 | . | langcod: 0 0x28fb-0x28fc (1) +0x28f0| 00 | . | audio_prod_info: 0 0x28fc-0x28fd (1) +0x28f0| 24 | $ | ext_bsi1_word1: 36 0x28fd-0x28fe (1) +0x28f0| 24 | $ | ext_bsi1_word2: 36 0x28fe-0x28ff (1) +0x28f0| 00| .| ext_bsi2_word1: 0 0x28ff-0x2900 (1) +0x2900|00 00 00 |... | ddplus_reserved_b: 0 0x2900-0x2903 (3) +0x2900| 02 | . | compr1: "film_light" (2) 0x2903-0x2904 (1) +0x2900| 02 | . | dynrng1: "film_light" (2) 0x2904-0x2905 (1) +0x2900| 00 00 00 | ... | ddplus_reserved_c: 0 0x2905-0x2908 (3) +0x2900| 00 | . | ddplus_info1: 0 0x2908-0x2909 (1) +0x2900| 00 00 00 00 00 | ..... | ddplus_reserved_d: 0 0x2909-0x290e (5) +0x2900| 00 00| ..| datarate: 0 0x290e-0x2910 (2) +0x2910|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| reserved_for_future_use: raw bits 0x2910-0x2955 (69) +* |until 0x2954.7 (69) | | +0x2950| ad | . | checksum: 0xad 0x2955-0x2956 (1) + | | | [1]{}: metadata_segment 0x2956-0x2a3f (233) +0x2950| 09 | . | id: "dolby_atmos" (9) 0x2956-0x2957 (1) +0x2950| f8 00 | .. | size: 248 0x2957-0x2959 (2) +0x2950| 43 72 65 61 74 65 64| Created| atmos_dbmd_content_creation_preamble: "Created using Dolby equipment" 0x2959-0x2979 (32) +0x2960|20 75 73 69 6e 67 20 44 6f 6c 62 79 20 65 71 75| using Dolby equ| +0x2970|69 70 6d 65 6e 74 00 00 00 |ipment... | +0x2970| 44 6f 6c 62 79 20 41| Dolby A| atmos_dbmd_content_creation_tool: "Dolby Atmos Composer Essential (fiedler audio)" 0x2979-0x29b9 (64) +0x2980|74 6d 6f 73 20 43 6f 6d 70 6f 73 65 72 20 45 73|tmos Composer Es| +* |until 0x29b8.7 (64) | | + | | | version{}: 0x29b9-0x29bc (3) +0x29b0| 01 | . | major: 1 0x29b9-0x29ba (1) +0x29b0| 00 | . | minor: 0 0x29ba-0x29bb (1) +0x29b0| 01 | . | patch: 1 0x29bb-0x29bc (1) +0x29b0| 00 00 00 00| ....| unknown0: raw bits 0x29bc-0x29d1 (21) +0x29c0|03 00 00 00 00 00 00 00 22 ff 00 00 00 00 00 03|........".......| +0x29d0|00 |. | +0x29d0| 00 | . | unknown1: raw bits 0x29d1-0x29d1.1 (0.1) +0x29d0| 00 | . | downmix_5to2: "not_indicated" (0) (Not indicated (Lo/Ro)) 0x29d1.1-0x29d1.4 (0.3) +0x29d0| 00 | . | unknown2: raw bits 0x29d1.4-0x29d1.6 (0.2) +0x29d0| 00 | . | phaseshift_90deg_5to2: "no_shift" (0) (Without Phase 90) 0x29d1.6-0x29d2 (0.2) +0x29d0| 00 00 00 00 00 00 00 00 00 00 00 00 | ............ | unknown3: raw bits 0x29d2-0x29de (12) +0x29d0| 00 | . | bed_distribution: raw bits 0x29de-0x29de.2 (0.2) +0x29d0| 00 | . | reserved0: raw bits 0x29de.2-0x29de.5 (0.3) +0x29d0| 00 | . | warp_mode: "normal" (0) (possibly: Direct render) 0x29de.5-0x29df (0.3) +0x29d0| f0| .| unknown4: raw bits 0x29df-0x29ee (15) +0x29e0|08 00 00 00 10 00 00 00 00 00 00 00 00 00 |.............. | +0x29e0| 00 00| ..| unknown5: raw bits 0x29ee-0x2a3e (80) +0x29f0|00 83 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| +* |until 0x2a3d.7 (80) | | +0x2a30| 00 | . | checksum: 0x0 0x2a3e-0x2a3f (1) + | | | [2]{}: metadata_segment 0x2a3f-0x2a40 (1) +0x2a30| 00| .| id: "end" (0) 0x2a3f-0x2a40 (1) +0x2a40|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................| gap0: raw bits 0x2a40-0x2aec (172) +* |until 0x2aeb.7 (end) (172) | |