From 04539fe3dc7a1e737649c5cade39fe53be123563 Mon Sep 17 00:00:00 2001 From: Mattias Wadman Date: Wed, 21 Aug 2024 09:27:10 +0200 Subject: [PATCH] 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))