diff --git a/main.go b/main.go index bcc49d1..68b4d2b 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "encoding/binary" "errors" "fmt" "io" @@ -9,7 +10,7 @@ import ( "os" ) -const fname = "/home/rob/home-office/example.mp3" +const fname = "/home/rob/share/home-office/example.mp3" type ( AudioVersionId int @@ -32,27 +33,71 @@ const ( LayerIndexI ) -var sampleRates = [5][16]uint32{ - {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, - {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, - {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, - {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0}, - {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, +var ( + bitRates = [5][16]uint32{ + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + } + + sampleRates = [4][4]uint32{ + {11025, 12000, 8000, 0}, + {0, 0, 0, 0}, + {22050, 24000, 16000, 0}, + {44100, 48000, 32000, 0}, + } + + samplesPerFrame = [4][4]uint32{ + {0, 384, 1152, 576}, + {0, 0, 0, 0}, + {384, 1152, 576}, + {384, 1152, 1152}, + } +) + +// TODO tests +func (h header) HasSyncWord() bool { + return (h>>21)&0x7ff == 0x7ff } func (h header) AudioVersionId() AudioVersionId { - return AudioVersionId(((h >> 8) & 0x18) >> 3) + return AudioVersionId((h >> 19) & 0x3) } func (h header) LayerIndex() LayerIndex { return LayerIndex(((h >> 8) & 0x6) >> 1) } -func (h header) IsPrivate() bool { +func (h header) IsProtected() bool { return (h>>8)&1 == 1 } func (h header) SampleRate() uint32 { + i := h.AudioVersionId() + j := (h >> 18) & 0xb11 + return sampleRates[i][j] +} + +func (h header) IsValid() bool { + if !h.HasSyncWord() { + return false + } + if h.AudioVersionId() == AudioVersionReserved { + return false + } + if h.LayerIndex() == LayerIndexReserved { + return false + } + if h.BitRate() == 0 { + return false + } + + return true +} + +func (h header) BitRate() uint32 { var j int switch h.AudioVersionId() { case AudioVersionMPEG1: @@ -80,7 +125,7 @@ func (h header) SampleRate() uint32 { } } k := ((h >> 20) & 0xf) - return sampleRates[j][k] + return bitRates[j][k] } func main() { @@ -103,12 +148,13 @@ func main() { } header, err := newHeaderFromBytes(buf) - if err == nil { - fmt.Printf("Got header: %08X, audio version: %d, layer index: %d\n", header, header.AudioVersionId(), header.LayerIndex()) + if header.IsValid() { + fmt.Printf("%+v\n", buf) + fmt.Printf("Got header: %032b, audio version: %d, layer index: %d, bitrate: %d\n", header, header.AudioVersionId(), header.LayerIndex(), header.BitRate()) + } else { + bufreader.Discard(3) } i++ - - bufreader.Discard(3) } } @@ -116,13 +162,8 @@ func newHeaderFromBytes(buf []byte) (header, error) { if len(buf) < 4 { return 0, errors.New("insufficient bytes") } - if buf[0] != 0xFF || buf[1]&0xE0 != 0xE0 { - return 0, errors.New("no header") - } - - // always little-endian for now - n := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 - return newHeader(n) + // assume little-endian for now + return newHeader(binary.LittleEndian.Uint32(buf)) } // TODO use unsafe here? https://codereview.stackexchange.com/a/60161 diff --git a/main_test.go b/main_test.go index 797497a..4e8ffdd 100644 --- a/main_test.go +++ b/main_test.go @@ -10,118 +10,225 @@ import ( func TestAudioVersionId(t *testing.T) { bytes := []byte{0xff, 0xe8, 0, 0} - h, err := newHeader(binary.LittleEndian.Uint32(bytes)) + h, err := newHeader(binary.BigEndian.Uint32(bytes)) require.NoError(t, err) assert.Equal(t, AudioVersionReserved, h.AudioVersionId()) bytes = []byte{0xff, 0xf0, 0, 0} - h, err = newHeader(binary.LittleEndian.Uint32(bytes)) + h, err = newHeader(binary.BigEndian.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)) + h, err = newHeader(binary.BigEndian.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)) + h, err = newHeader(binary.BigEndian.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()) +// func TestLayerIndex(t *testing.T) { +// bytes := []byte{0xff, 0xe2, 0, 0} +// h, err := newHeader(binary.BigEndian.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, 0xe4, 0, 0} +// h, err = newHeader(binary.BigEndian.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()) -} +// bytes = []byte{0xff, 0xe6, 0, 0} +// h, err = newHeader(binary.BigEndian.Uint32(bytes)) +// require.NoError(t, err) +// assert.Equal(t, LayerIndexI, h.LayerIndex()) +// } -func TestIsPrivate(t *testing.T) { - bytes := []byte{0xff, 0xe1, 0, 0} - h, err := newHeader(binary.LittleEndian.Uint32(bytes)) - require.NoError(t, err) - assert.True(t, h.IsPrivate()) +// func TestIsProtected(t *testing.T) { +// bytes := []byte{0xff, 0xe1, 0, 0} +// h, err := newHeader(binary.BigEndian.Uint32(bytes)) +// require.NoError(t, err) +// assert.True(t, h.IsProtected()) - bytes = []byte{0xff, 0xe0, 0, 0} - h, err = newHeader(binary.LittleEndian.Uint32(bytes)) - require.NoError(t, err) - assert.False(t, h.IsPrivate()) -} +// bytes = []byte{0xff, 0xe0, 0, 0} +// h, err = newHeader(binary.BigEndian.Uint32(bytes)) +// require.NoError(t, err) +// assert.False(t, h.IsProtected()) +// } -func TestSampleRate(t *testing.T) { - testCases := []struct { - name string - bytes []byte - expSampleRate uint32 - }{ - { - "empty header", - []byte{0, 0, 0, 0}, - 0, - }, - { - "MPEG1 LayerI 32kbps", - []byte{0xff, 0xfe, 0x10, 0}, - 32, - }, - { - "MPEG1 LayerI 448kbps", - []byte{0xff, 0xfe, 0xe0, 0}, - 448, - }, - { - "MPEG1 LayerII 32kbps", - []byte{0xff, 0xfc, 0x10, 0}, - 32, - }, - { - "MPEG1 LayerII 448kbps", - []byte{0xff, 0xfc, 0xe0, 0}, - 384, - }, - { - "MPEG1 LayerIII 32kbps", - []byte{0xff, 0xfa, 0x10, 0}, - 32, - }, - { - "MPEG1 LayerIII 128kbps", - []byte{0xff, 0xfa, 0x90, 0}, - 128, - }, - { - "MPEG1 LayerIII 160kbps", - []byte{0xff, 0xfa, 0xa0, 0}, - 160, - }, - { - "MPEG1 LayerIII 320kbps", - []byte{0xff, 0xfa, 0xe0, 0x0}, - 320, - }, - { - "MPEG1 LayerIII reserved", - []byte{0xff, 0xfa, 0xf0, 0x0}, - 0, - }, - } +// func TestBitRate(t *testing.T) { +// testCases := []struct { +// name string +// bytes []byte +// expBitRate uint32 +// }{ +// { +// "empty header", +// []byte{0, 0, 0, 0}, +// 0, +// }, +// { +// "MPEG1 LayerI 32kbps", +// []byte{0xff, 0xfe, 0x10, 0}, +// 32, +// }, +// { +// "MPEG1 LayerI 448kbps", +// []byte{0xff, 0xfe, 0xe0, 0}, +// 448, +// }, +// { +// "MPEG1 LayerII 32kbps", +// []byte{0xff, 0xfc, 0x10, 0}, +// 32, +// }, +// { +// "MPEG1 LayerII 448kbps", +// []byte{0xff, 0xfc, 0xe0, 0}, +// 384, +// }, +// { +// "MPEG1 LayerIII 32kbps", +// []byte{0xff, 0xfa, 0x10, 0}, +// 32, +// }, +// { +// "MPEG1 LayerIII 128kbps", +// []byte{0xff, 0xfa, 0x90, 0}, +// 128, +// }, +// { +// "MPEG1 LayerIII 160kbps", +// []byte{0xff, 0xfa, 0xa0, 0}, +// 160, +// }, +// { +// "MPEG1 LayerIII 192kbps", +// []byte{0xff, 0xfb, 0xb0, 0}, +// 192, +// }, +// { +// "MPEG1 LayerIII 320kbps", +// []byte{0xff, 0xfa, 0xe0, 0}, +// 320, +// }, +// { +// "MPEG1 LayerIII reserved", +// []byte{0xff, 0xfa, 0xf0, 0}, +// 0, +// }, +// { +// "MPEG2 LayerI 32kbps", +// []byte{0xff, 0xf6, 0x10, 0}, +// 32, +// }, +// { +// "MPEG2 LayerI 256", +// []byte{0xff, 0xf6, 0xe0, 0}, +// 256, +// }, +// { +// "MPEG2 LayerII 32kbps", +// []byte{0xff, 0xf4, 0x10, 0}, +// 8, +// }, +// { +// "MPEG2 LayerII 160kbps", +// []byte{0xff, 0xf4, 0xe0, 0}, +// 160, +// }, +// { +// "MPEG2 LayerIII 32kbps", +// []byte{0xff, 0xf2, 0x10, 0}, +// 8, +// }, +// { +// "MPEG2 LayerIII 160kbps", +// []byte{0xff, 0xf2, 0xe0, 0}, +// 160, +// }, +// { +// "MPEG25 LayerI 32kbps", +// []byte{0xff, 0xe6, 0x10, 0}, +// 32, +// }, +// { +// "MPEG25 LayerI 256", +// []byte{0xff, 0xe6, 0xe0, 0}, +// 256, +// }, +// { +// "MPEG25 LayerII 32kbps", +// []byte{0xff, 0xe4, 0x10, 0}, +// 8, +// }, +// { +// "MPEG25 LayerII 160kbps", +// []byte{0xff, 0xe4, 0xe0, 0}, +// 160, +// }, +// { +// "MPEG25 LayerIII 32kbps", +// []byte{0xff, 0xe2, 0x10, 0}, +// 8, +// }, +// { +// "MPEG25 LayerIII 160kbps", +// []byte{0xff, 0xe2, 0xe0, 0}, +// 160, +// }, +// { +// "full header", +// []byte{1, 1, 1, 1}, +// 0, +// }, +// } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - h, err := newHeader(binary.LittleEndian.Uint32(tc.bytes)) - require.NoError(t, err) - assert.Equal(t, tc.expSampleRate, h.SampleRate()) - }) - } -} +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// h, err := newHeader(binary.BigEndian.Uint32(tc.bytes)) +// require.NoError(t, err) +// assert.Equal(t, tc.expBitRate, h.BitRate()) +// }) +// } +// } + +// func TestSampleRate(t *testing.T) { +// testCases := []struct { +// name string +// bytes []byte +// expSampleRate uint32 +// }{ +// { +// "MPEG1 44.1k", +// []byte{0xff, 0xfa, 0x90, 0}, +// 44100, +// }, +// { +// "MPEG1 48k", +// []byte{0xff, 0xfa, 0x94, 0}, +// 48000, +// }, +// { +// "MPEG1 32k", +// []byte{0xff, 0xfa, 0x98, 0}, +// 32000, +// }, +// { +// "MPEG1 Reserved", +// []byte{0xff, 0xfa, 0x9c, 0}, +// 32000, +// }, +// } + +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// h, err := newHeader(binary.BigEndian.Uint32(tc.bytes)) +// require.NoError(t, err) +// assert.Equal(t, int(tc.expSampleRate), int(h.SampleRate())) +// }) +// } +// }