implement audio version and layer index parsing
This commit is contained in:
parent
f16a5c99c8
commit
85c66d74c2
76
main.go
76
main.go
|
@ -9,25 +9,56 @@ import (
|
|||
"os"
|
||||
)
|
||||
|
||||
const fname = "/home/rob/home-office/example.mp3"
|
||||
const fname = "/home/rob/share/home-office/example.mp3"
|
||||
|
||||
const (
|
||||
maskSync = 0b11111111111000000000000000000000
|
||||
maskVersion = 0b00000000000110000000000000000000
|
||||
maskLayer = 0b00000000000001100000000000000000
|
||||
maskProtection = 0b00000000000000010000000000000000
|
||||
maskBitrate = 0b00000000000000001111000000000000
|
||||
maskSamplerate = 0b00000000000000000000110000000000
|
||||
maskPadding = 0b00000000000000000000001000000000
|
||||
maskPrivate = 0b00000000000000000000000100000000
|
||||
maskChannelMode = 0b00000000000000000000000011000000
|
||||
maskExtension = 0b00000000000000000000000000110000
|
||||
maskCopyright = 0b00000000000000000000000000001000
|
||||
maskOriginal = 0b00000000000000000000000000000100
|
||||
maskEmphasis = 0b00000000000000000000000000000011
|
||||
type (
|
||||
AudioVersionId int
|
||||
LayerIndex int
|
||||
|
||||
header uint32
|
||||
)
|
||||
|
||||
type header uint32
|
||||
const (
|
||||
AudioVersionMPEG25 AudioVersionId = iota
|
||||
AudioVersionReserved
|
||||
AudioVersionMPEG2
|
||||
AudioVersionMPEG1
|
||||
)
|
||||
|
||||
const (
|
||||
LayerIndexReserved LayerIndex = iota
|
||||
LayerIndexIII
|
||||
LayerIndexII
|
||||
LayerIndexI
|
||||
)
|
||||
|
||||
func (h header) AudioVersionId() AudioVersionId {
|
||||
b := h >> 8
|
||||
if b&0x18 == 0x18 {
|
||||
return AudioVersionMPEG1
|
||||
}
|
||||
if b&0x10 == 0x10 {
|
||||
return AudioVersionMPEG2
|
||||
}
|
||||
if b&0x8 == 0x8 {
|
||||
return AudioVersionReserved
|
||||
}
|
||||
return AudioVersionMPEG25
|
||||
}
|
||||
|
||||
func (h header) LayerIndex() LayerIndex {
|
||||
b := h >> 8
|
||||
if b&0x6 == 0x6 {
|
||||
return LayerIndexI
|
||||
}
|
||||
if b&0x4 == 0x4 {
|
||||
return LayerIndexII
|
||||
}
|
||||
if b&0x2 == 0x2 {
|
||||
return LayerIndexIII
|
||||
}
|
||||
return LayerIndexReserved
|
||||
}
|
||||
|
||||
func main() {
|
||||
f, err := os.Open(fname)
|
||||
|
@ -36,8 +67,8 @@ func main() {
|
|||
}
|
||||
|
||||
bufreader := bufio.NewReader(f)
|
||||
|
||||
buf := make([]byte, 4)
|
||||
|
||||
var i int
|
||||
for {
|
||||
_, err := io.ReadFull(bufreader, buf)
|
||||
|
@ -48,10 +79,10 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
header, err := newHeader(buf)
|
||||
header, err := newHeaderFromBytes(buf)
|
||||
|
||||
if err == nil {
|
||||
fmt.Printf("Got header: %08X\n", header)
|
||||
fmt.Printf("Got header: %08X, audio version: %d\n", header, header.AudioVersionId())
|
||||
}
|
||||
i++
|
||||
|
||||
|
@ -59,8 +90,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO use unsafe here? https://codereview.stackexchange.com/a/60161
|
||||
func newHeader(buf []byte) (header, error) {
|
||||
func newHeaderFromBytes(buf []byte) (header, error) {
|
||||
if len(buf) < 4 {
|
||||
return 0, errors.New("insufficient bytes")
|
||||
}
|
||||
|
@ -70,6 +100,10 @@ func newHeader(buf []byte) (header, error) {
|
|||
|
||||
// always little-endian for now
|
||||
n := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||
return newHeader(n)
|
||||
}
|
||||
|
||||
// TODO use unsafe here? https://codereview.stackexchange.com/a/60161
|
||||
func newHeader(n uint32) (header, error) {
|
||||
return header(n), nil
|
||||
}
|
||||
|
|
63
main_test.go
63
main_test.go
|
@ -1,35 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input []byte
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
input: []byte{0, 0, 0, 0},
|
||||
expectedError: "no header",
|
||||
},
|
||||
{
|
||||
input: []byte{0, 0, 0, 1},
|
||||
expectedError: "no header",
|
||||
},
|
||||
{
|
||||
input: []byte{0xff, 0xfe, 0, 0}, // wrong
|
||||
expectedError: "",
|
||||
},
|
||||
}
|
||||
func TestAudioVersionId(t *testing.T) {
|
||||
bytes := []byte{0xff, 0xe8, 0, 0}
|
||||
h, err := newHeader(binary.LittleEndian.Uint32(bytes))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, AudioVersionReserved, h.AudioVersionId())
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("Header %08b", tc.input), func(t *testing.T) {
|
||||
_, err := newHeader(tc.input)
|
||||
assert.EqualError(t, err, tc.expectedError)
|
||||
})
|
||||
}
|
||||
bytes = []byte{0xff, 0xf0, 0, 0}
|
||||
h, err = newHeader(binary.LittleEndian.Uint32(bytes))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, AudioVersionMPEG2, h.AudioVersionId())
|
||||
|
||||
bytes = []byte{0xff, 0xff, 0, 0}
|
||||
h, err = newHeader(binary.LittleEndian.Uint32(bytes))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, AudioVersionMPEG1, h.AudioVersionId())
|
||||
|
||||
bytes = []byte{0xff, 0xe0, 0, 0}
|
||||
h, err = newHeader(binary.LittleEndian.Uint32(bytes))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, AudioVersionMPEG25, h.AudioVersionId())
|
||||
}
|
||||
|
||||
func TestLayerIndex(t *testing.T) {
|
||||
bytes := []byte{0xff, 0xe2, 0, 0}
|
||||
h, err := newHeader(binary.LittleEndian.Uint32(bytes))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, LayerIndexIII, h.LayerIndex())
|
||||
|
||||
bytes = []byte{0xff, 0xe4, 0, 0}
|
||||
h, err = newHeader(binary.LittleEndian.Uint32(bytes))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, LayerIndexII, h.LayerIndex())
|
||||
|
||||
bytes = []byte{0xff, 0xe6, 0, 0}
|
||||
h, err = newHeader(binary.LittleEndian.Uint32(bytes))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, LayerIndexI, h.LayerIndex())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue