1
1
mirror of https://github.com/wader/fq.git synced 2024-12-22 12:51:38 +03:00

aiff: Add basic decoder

This commit is contained in:
Mattias Wadman 2023-03-09 14:02:59 +01:00
parent 73ab18848c
commit dc4a82eeed
9 changed files with 819 additions and 435 deletions

View File

@ -37,6 +37,7 @@ In summary it aims to be jq, hexdump, dd and gdb for files combined into one.
[aac_frame](doc/formats.md#aac_frame),
adts,
adts_frame,
aiff,
amf0,
apev2,
[apple_bookmark](doc/formats.md#apple_bookmark),

View File

@ -7,6 +7,7 @@
|[`aac_frame`](#aac_frame) |Advanced&nbsp;Audio&nbsp;Coding&nbsp;frame |<sub></sub>|
|`adts` |Audio&nbsp;Data&nbsp;Transport&nbsp;Stream |<sub>`adts_frame`</sub>|
|`adts_frame` |Audio&nbsp;Data&nbsp;Transport&nbsp;Stream&nbsp;frame |<sub>`aac_frame`</sub>|
|`aiff` |Audio&nbsp;Interchange&nbsp;File&nbsp;Format |<sub></sub>|
|`amf0` |Action&nbsp;Message&nbsp;Format&nbsp;0 |<sub></sub>|
|`apev2` |APEv2&nbsp;metadata&nbsp;tag |<sub>`image`</sub>|
|[`apple_bookmark`](#apple_bookmark) |Apple&nbsp;BookmarkData |<sub></sub>|
@ -123,7 +124,7 @@
|`ip_packet` |Group |<sub>`icmp` `icmpv6` `tcp_segment` `udp_datagram`</sub>|
|`link_frame` |Group |<sub>`bsd_loopback_frame` `ether8023_frame` `ipv4_packet` `ipv6_packet` `sll2_packet` `sll_packet`</sub>|
|`mp3_frame_tags` |Group |<sub>`mp3_frame_vbri` `mp3_frame_xing`</sub>|
|`probe` |Group |<sub>`adts` `apple_bookmark` `ar` `avi` `avro_ocf` `bitcoin_blkdat` `bplist` `bzip2` `elf` `flac` `gif` `gzip` `jpeg` `json` `jsonl` `macho` `macho_fat` `matroska` `mp3` `mp4` `mpeg_ts` `ogg` `pcap` `pcapng` `png` `tar` `tiff` `toml` `tzif` `wasm` `wav` `webp` `xml` `yaml` `zip`</sub>|
|`probe` |Group |<sub>`adts` `aiff` `apple_bookmark` `ar` `avi` `avro_ocf` `bitcoin_blkdat` `bplist` `bzip2` `elf` `flac` `gif` `gzip` `jpeg` `json` `jsonl` `macho` `macho_fat` `matroska` `mp3` `mp4` `mpeg_ts` `ogg` `pcap` `pcapng` `png` `tar` `tiff` `toml` `tzif` `wasm` `wav` `webp` `xml` `yaml` `zip`</sub>|
|`tcp_stream` |Group |<sub>`dns_tcp` `rtmp` `tls`</sub>|
|`udp_payload` |Group |<sub>`dns`</sub>|

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -27,6 +27,7 @@ $ fq -n _registry.groups.probe
"wasm",
"webp",
"zip",
"aiff",
"mp3",
"mpeg_ts",
"wav",
@ -40,6 +41,7 @@ $ fq --help formats
aac_frame Advanced Audio Coding frame
adts Audio Data Transport Stream
adts_frame Audio Data Transport Stream frame
aiff Audio Interchange File Format
amf0 Action Message Format 0
apev2 APEv2 metadata tag
apple_bookmark Apple BookmarkData

View File

@ -29,6 +29,7 @@ const (
AAC_FRAME = "aac_frame"
ADTS = "adts"
ADTS_FRAME = "adts_frame"
AIFF = "aiff"
AMF0 = "amf0"
APEV2 = "apev2"
APPLE_BOOKMARK = "apple_bookmark"

117
format/riff/aiff.go Normal file
View File

@ -0,0 +1,117 @@
package riff
// http://midi.teragonaudio.com/tech/aiff.htm
import (
"github.com/wader/fq/format"
"github.com/wader/fq/internal/mathex"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
func init() {
interp.RegisterFormat(decode.Format{
Name: format.AIFF,
ProbeOrder: format.ProbeOrderBinFuzzy,
Description: "Audio Interchange File Format",
Groups: []string{format.PROBE},
DecodeFn: aiffDecode,
})
}
const aiffRiffType = "AIFF"
// pstring:
// > Pascal-style string, a one-byte count followed by that many text bytes. The total number of bytes in this data type should be even.
// > A pad byte can be added to the end of the text to accomplish this. This pad byte is not reflected in the count.
func aiffPString(d *decode.D) string {
l := d.U8()
pad := (l + 1) % 2
s := d.UTF8(int(l + pad))
return s[0 : l+1-pad]
}
func aiffDecode(d *decode.D) any {
var riffType string
riffDecode(
d,
nil,
func(d *decode.D, path path) (string, int64) {
id := d.FieldUTF8("id", 4, chunkIDDescriptions)
const restOfFileLen = 0xffffffff
size := int64(d.FieldScalarUintFn("size", func(d *decode.D) scalar.Uint {
l := d.U32()
if l == restOfFileLen {
return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberHex, Description: "Rest of file"}
}
return scalar.Uint{Actual: l, DisplayFormat: scalar.NumberDecimal}
}).Actual)
if size == restOfFileLen {
size = d.BitsLeft() / 8
}
return id, size
},
func(d *decode.D, id string, path path) (bool, any) {
switch id {
case "FORM":
riffType = d.FieldUTF8("format", 4, d.StrAssert(aiffRiffType))
return true, nil
case "COMT":
numComments := d.FieldU16("num_comments")
d.FieldArray("comments", func(d *decode.D) {
for i := 0; i < int(numComments); i++ {
d.FieldStruct("comment", func(d *decode.D) {
d.FieldU32("timestamp")
d.FieldU16("marker_id")
count := d.FieldU16("count")
pad := count % 2
d.FieldUTF8("text", int(count))
if pad != 0 {
d.FieldRawLen("pad", int64(pad)*8)
}
})
}
})
return false, nil
case "COMM":
d.FieldU16("num_channels")
d.FieldU32("num_sample_frames")
d.FieldU16("sample_size")
// TODO: support big float?
d.FieldFltFn("sample_rate", func(d *decode.D) float64 {
return mathex.NewFloat80FromBytes(d.BytesLen(10)).Float64()
})
return false, nil
case "SSND":
d.FieldU32("offset")
d.FieldU32("block_size")
d.FieldRawLen("data", d.BitsLeft())
return false, nil
case "MARK":
numMarkers := d.FieldU16("num_markers")
d.FieldArray("markers", func(d *decode.D) {
for i := 0; i < int(numMarkers); i++ {
d.FieldStruct("marker", func(d *decode.D) {
d.FieldU16("id")
d.FieldU32("position")
d.FieldStrFn("name", aiffPString)
})
}
})
return false, nil
default:
d.FieldRawLen("data", d.BitsLeft())
return false, nil
}
},
)
if riffType != aiffRiffType {
d.Errorf("wrong or no AIFF riff type found (%s)", riffType)
}
return nil
}

BIN
format/riff/testdata/sox.aiff vendored Normal file

Binary file not shown.

35
format/riff/testdata/sox.aiff.fqtest vendored Normal file
View File

@ -0,0 +1,35 @@
# ffmpeg -f lavfi -i sine -t 100ms -f wav sox.wav; sox sox.wav six.aiff
$ fq dv sox.aiff
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: sox.aiff (aiff) 0x0-0x22cb.7 (8908)
0x0000|46 4f 52 4d |FORM | id: "FORM" 0x0-0x3.7 (4)
0x0000| 00 00 22 c4 | ..". | size: 8900 0x4-0x7.7 (4)
0x0000| 41 49 46 46 | AIFF | format: "AIFF" (valid) 0x8-0xb.7 (4)
| | | chunks[0:3]: 0xc-0x22cb.7 (8896)
| | | [0]{}: chunk 0xc-0x2d.7 (34)
0x0000| 43 4f 4d 54| COMT| id: "COMT" 0xc-0xf.7 (4)
0x0010|00 00 00 1a |.... | size: 26 0x10-0x13.7 (4)
0x0010| 00 01 | .. | num_comments: 1 0x14-0x15.7 (2)
| | | comments[0:1]: 0x16-0x2d.7 (24)
| | | [0]{}: comment 0x16-0x2d.7 (24)
0x0010| e0 2f 99 4b | ./.K | timestamp: 3761215819 0x16-0x19.7 (4)
0x0010| 00 00 | .. | marker_id: 0 0x1a-0x1b.7 (2)
0x0010| 00 10 | .. | count: 16 0x1c-0x1d.7 (2)
0x0010| 50 72| Pr| text: "Processed by SoX" 0x1e-0x2d.7 (16)
0x0020|6f 63 65 73 73 65 64 20 62 79 20 53 6f 58 |ocessed by SoX |
| | | [1]{}: chunk 0x2e-0x47.7 (26)
0x0020| 43 4f| CO| id: "COMM" 0x2e-0x31.7 (4)
0x0030|4d 4d |MM |
0x0030| 00 00 00 12 | .... | size: 18 0x32-0x35.7 (4)
0x0030| 00 01 | .. | num_channels: 1 0x36-0x37.7 (2)
0x0030| 00 00 11 3a | ...: | num_sample_frames: 4410 0x38-0x3b.7 (4)
0x0030| 00 10 | .. | sample_size: 16 0x3c-0x3d.7 (2)
0x0030| 40 0e| @.| sample_rate: 44100 0x3e-0x47.7 (10)
0x0040|ac 44 00 00 00 00 00 00 |.D...... |
| | | [2]{}: chunk 0x48-0x22cb.7 (8836)
0x0040| 53 53 4e 44 | SSND | id: "SSND" 0x48-0x4b.7 (4)
0x0040| 00 00 22 7c| .."|| size: 8828 0x4c-0x4f.7 (4)
0x0050|00 00 00 00 |.... | offset: 0 0x50-0x53.7 (4)
0x0050| 00 00 00 00 | .... | block_size: 0 0x54-0x57.7 (4)
0x0050| 00 00 01 00 01 ff 02 fd| ........| data: raw bits 0x58-0x22cb.7 (8820)
0x0060|03 f8 04 ee 05 e0 06 cc 07 b0 08 8d 09 62 0a 2d|.............b.-|
* |until 0x22cb.7 (end) (8820) | |

215
internal/mathex/float80.go Normal file
View File

@ -0,0 +1,215 @@
// Float80 type from https://github.com/mewspring/mewmew-l
// modified as bit to read bytes instead of hex string
//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org>
package mathex
import (
"fmt"
"log"
"math"
"math/big"
)
// Float80 represents an 80-bit IEEE 754 extended precision floating-point
// value, in x86 extended precision format.
//
// References:
//
// https://en.wikipedia.org/wiki/Extended_precision#x86_extended_precision_format
type Float80 struct {
// Sign and exponent.
//
// 1 bit: sign
// 15 bits: exponent
se uint16
// Integer part and fraction.
//
// 1 bit: integer part
// 63 bits: fraction
m uint64
}
// Bits returns the IEEE 754 binary representation of f, with the sign and
// exponent in se and the mantissa in m.
func (f Float80) Bits() (se uint16, m uint64) {
return f.se, f.m
}
// Bytes returns the x86 extended precision binary representation of f as a byte
// slice.
func (f Float80) Bytes() []byte {
return []byte(f.String())
}
// String returns the IEEE 754 binary representation of f as a string,
// containing 10 bytes in hexadecimal format.
func (f Float80) String() string {
return fmt.Sprintf("%04X%016X", f.se, f.m)
}
// Float64 returns the float64 representation of f.
func (f Float80) Float64() float64 {
se := uint64(f.se)
m := f.m
// 1 bit: sign
sign := se >> 15
// 15 bits: exponent
exp := se & 0x7FFF
// Adjust for exponent bias.
//
// === [ binary64 ] =========================================================
//
// Exponent bias 1023.
//
// +===========================+=======================+
// | Exponent (in binary) | Notes |
// +===========================+=======================+
// | 00000000000 | zero/subnormal number |
// +---------------------------+-----------------------+
// | 00000000001 - 11111111110 | normalized value |
// +---------------------------+-----------------------+
// | 11111111111 | infinity/NaN |
// +---------------------------+-----------------------+
//
// References:
// https://en.wikipedia.org/wiki/Double-precision_floating-point_format#Exponent_encoding
exp64 := int64(exp) - 16383 + 1023
switch {
case exp == 0:
// exponent is all zeroes.
exp64 = 0
case exp == 0x7FFF:
// exponent is all ones.
exp64 = 0x7FF
default:
}
// 63 bits: fraction
frac := m & 0x7FFFFFFFFFFFFFFF
// Sign, exponent and fraction of binary64.
//
// 1 bit: sign
// 11 bits: exponent
// 52 bits: fraction
//
// References:
// https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64
bits := sign<<63 | uint64(exp64)<<52 | frac>>11
return math.Float64frombits(bits)
}
// BigFloat returns the *big.Float representation of f.
func (f Float80) BigFloat() *big.Float {
x := &big.Float{}
sign := (f.se & 0x8000) != 0
e := f.se & 0x7FFF
s := fmt.Sprintf("0x.%Xp%d", f.m, e-16383+1)
if sign {
s = "-" + s
}
x.SetPrec(52)
_, _, err := x.Parse(s, 0)
if err != nil {
log.Printf("big.Float.Parse: error %v", err)
}
return x
}
// NewFloat80FromFloat64 returns the nearest 80-bit floating-point value for x.
func NewFloat80FromFloat64(x float64) Float80 {
// Sign, exponent and fraction of binary64.
//
// 1 bit: sign
// 11 bits: exponent
// 52 bits: fraction
bits := math.Float64bits(x)
// 1 bit: sign
sign := uint16(bits >> 63)
// 11 bits: exponent
exp := bits >> 52 & 0x7FF
// 52 bits: fraction
frac := bits & 0xFFFFFFFFFFFFF
if exp == 0 && frac == 0 {
// zero value.
return Float80{}
}
// Sign, exponent and fraction of binary80.
//
// 1 bit: sign
// 15 bits: exponent
// 1 bit: integer part
// 63 bits: fraction
// 15 bits: exponent.
//
// Exponent bias 1023 (binary64)
// Exponent bias 16383 (binary80)
exp80 := int64(exp) - 1023 + 16383
// 63 bits: fraction.
//
frac80 := frac << 11
switch {
case exp == 0:
exp80 = 0
case exp == 0x7FF:
exp80 = 0x7FFF
}
se := sign<<15 | uint16(exp80)
// Integer part set to specify normalized value.
m := 0x8000000000000000 | frac80
return NewFloat80FromBits(se, m)
}
// NewFloat80FromBytes returns a new 80-bit floating-point value based on b,
func NewFloat80FromBytes(b []byte) Float80 {
var f Float80
if len(b) != 10 {
panic(fmt.Errorf("invalid length of float80 representation, expected 10, got %d", len(b)))
}
f.se = uint16(int64(b[0])<<8 | int64(b[1]<<0))
f.m = uint64(0 |
int64(b[2])<<56 |
int64(b[3])<<48 |
int64(b[4])<<40 |
int64(b[5])<<32 |
int64(b[6])<<24 |
int64(b[7])<<16 |
int64(b[8])<<8 |
int64(b[9])<<0,
)
return f
}
// NewFloat80FromBits returns a new 80-bit floating-point value based on the
// sign, exponent and mantissa bits.
func NewFloat80FromBits(se uint16, m uint64) Float80 {
return Float80{
se: se,
m: m,
}
}