mirror of
https://github.com/wader/fq.git
synced 2024-11-22 07:16:49 +03:00
aiff: Add basic decoder
This commit is contained in:
parent
73ab18848c
commit
dc4a82eeed
@ -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),
|
||||
|
@ -7,6 +7,7 @@
|
||||
|[`aac_frame`](#aac_frame) |Advanced Audio Coding frame |<sub></sub>|
|
||||
|`adts` |Audio Data Transport Stream |<sub>`adts_frame`</sub>|
|
||||
|`adts_frame` |Audio Data Transport Stream frame |<sub>`aac_frame`</sub>|
|
||||
|`aiff` |Audio Interchange File Format |<sub></sub>|
|
||||
|`amf0` |Action Message Format 0 |<sub></sub>|
|
||||
|`apev2` |APEv2 metadata tag |<sub>`image`</sub>|
|
||||
|[`apple_bookmark`](#apple_bookmark) |Apple 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>|
|
||||
|
||||
|
880
doc/formats.svg
880
doc/formats.svg
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 142 KiB |
@ -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
|
||||
|
@ -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
117
format/riff/aiff.go
Normal 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
BIN
format/riff/testdata/sox.aiff
vendored
Normal file
Binary file not shown.
35
format/riff/testdata/sox.aiff.fqtest
vendored
Normal file
35
format/riff/testdata/sox.aiff.fqtest
vendored
Normal 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
215
internal/mathex/float80.go
Normal 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,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user