From 5c836c4f7046bfccdce4bd08ffa35eea30dfbea3 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Wed, 27 Jan 2021 18:09:38 +0100 Subject: [PATCH] add tests, implement frame size --- main.go | 88 ++++++++++++++++++++++++++++++--------------- main_test.go | 100 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 152 insertions(+), 36 deletions(-) diff --git a/main.go b/main.go index 3c891ac..434cee0 100644 --- a/main.go +++ b/main.go @@ -12,9 +12,8 @@ const fname = "/home/rob/share/home-office/example.mp3" type ( AudioVersionId int - LayerIndex int - - header []byte + Layer int + header []byte ) const ( @@ -25,14 +24,14 @@ const ( ) const ( - LayerIndexReserved LayerIndex = iota - LayerIndexIII - LayerIndexII - LayerIndexI + LayerReserved Layer = iota + LayerIII + LayerII + LayerI ) var ( - bitRates = [5][16]uint32{ + bitRates = [5][16]int{ {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}, @@ -40,22 +39,21 @@ var ( {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, } - sampleRates = [4][4]uint32{ + sampleRates = [4][4]int{ {11025, 12000, 8000, 0}, {0, 0, 0, 0}, {22050, 24000, 16000, 0}, {44100, 48000, 32000, 0}, } - samplesPerFrame = [4][4]uint32{ - {0, 384, 1152, 576}, + samplesPerFrame = [4][4]int{ + {0, 576, 1152, 384}, {0, 0, 0, 0}, - {384, 1152, 576}, - {384, 1152, 1152}, + {0, 576, 1152, 384}, + {0, 1152, 1152, 384}, } ) -// TODO: tests func (h header) HasSyncWord() bool { return h[0] == 0xff && h[1]&0xe0 == 0xe0 } @@ -64,20 +62,42 @@ func (h header) AudioVersionId() AudioVersionId { return AudioVersionId((h[1] >> 3) & 0x3) } -func (h header) LayerIndex() LayerIndex { - return LayerIndex((h[1] >> 1) & 0x3) +func (h header) Layer() Layer { + return Layer((h[1] >> 1) & 0x3) } func (h header) IsProtected() bool { return h[1]&1 == 1 } -func (h header) SampleRate() uint32 { +func (h header) String() string { + return fmt.Sprintf("%08b", h) +} + +func (h header) SamplesPerFrame() int { + return samplesPerFrame[h.AudioVersionId()][h.Layer()] +} + +func (h header) SampleRate() int { i := h.AudioVersionId() j := (h[2] >> 2) & 0x3 return sampleRates[i][j] } +func (h header) PaddingBytes() int { + if isPadded := (h[2] >> 1) & 1; isPadded == 0 { + return 0 + } + switch h.Layer() { + case LayerI: + return 4 + case LayerII, LayerIII: + return 1 + default: + return 0 + } +} + func (h header) IsValid() bool { if !h.HasSyncWord() { return false @@ -85,27 +105,36 @@ func (h header) IsValid() bool { if h.AudioVersionId() == AudioVersionReserved { return false } - if h.LayerIndex() == LayerIndexReserved { + if h.Layer() == LayerReserved { return false } if h.BitRate() == 0 { return false } + if h.SampleRate() == 0 { + return false + } return true } -func (h header) BitRate() uint32 { +func (h header) Len() int { + // TODO premultiply bitrates? + bps := float64(h.SamplesPerFrame()) / 8 + return int((bps*float64(h.BitRate()*1000))/float64(h.SampleRate()) + float64(h.PaddingBytes())) +} + +func (h header) BitRate() int { // TODO: improve this var j int switch h.AudioVersionId() { case AudioVersionMPEG1: - switch h.LayerIndex() { - case LayerIndexIII: + switch h.Layer() { + case LayerIII: j = 2 - case LayerIndexII: + case LayerII: j = 1 - case LayerIndexI: + case LayerI: j = 0 default: return 0 @@ -113,10 +142,10 @@ func (h header) BitRate() uint32 { case AudioVersionReserved: return 0 case AudioVersionMPEG2, AudioVersionMPEG25: - switch h.LayerIndex() { - case LayerIndexIII, LayerIndexII: + switch h.Layer() { + case LayerIII, LayerII: j = 4 - case LayerIndexI: + case LayerI: j = 3 default: return 0 @@ -145,10 +174,11 @@ func main() { log.Fatal(err) } - h := header(buf) - if h.IsValid() { - fmt.Printf("Got header: %08b, audio version: %d, layer index: %d, bitrate: %d\n", h, h.AudioVersionId(), h.LayerIndex(), h.BitRate()) + if h := header(buf); h.IsValid() { + fmt.Printf("Got header: %v, audio version: %d, layer index: %d, bitrate: %d, samplerate: %d, samplesPerFrame: %d, padding: %d, size: %d\n", h, h.AudioVersionId(), h.Layer(), h.BitRate(), h.SampleRate(), h.SamplesPerFrame(), h.PaddingBytes(), h.Len()) + bufreader.Discard(h.Len() - 4) } else { + fmt.Println("Discarding a byte") bufreader.Discard(3) } i++ diff --git a/main_test.go b/main_test.go index 955e520..8495d4a 100644 --- a/main_test.go +++ b/main_test.go @@ -60,22 +60,32 @@ func TestAudioVersionId(t *testing.T) { assert.Equal(t, AudioVersionMPEG25, h.AudioVersionId()) } -func TestLayerIndex(t *testing.T) { +func TestLayer(t *testing.T) { bytes := []byte{0xff, 0xe0, 0, 0} h := header(bytes) - assert.Equal(t, LayerIndexReserved, h.LayerIndex()) + assert.Equal(t, LayerReserved, h.Layer()) bytes = []byte{0xff, 0xe2, 0, 0} h = header(bytes) - assert.Equal(t, LayerIndexIII, h.LayerIndex()) + assert.Equal(t, LayerIII, h.Layer()) bytes = []byte{0xff, 0xe4, 0, 0} h = header(bytes) - assert.Equal(t, LayerIndexII, h.LayerIndex()) + assert.Equal(t, LayerII, h.Layer()) bytes = []byte{0xff, 0xe6, 0, 0} h = header(bytes) - assert.Equal(t, LayerIndexI, h.LayerIndex()) + assert.Equal(t, LayerI, h.Layer()) +} + +func TestPaddingBytes(t *testing.T) { + bytes := []byte{0xff, 0xe0, 0x2, 0} + h := header(bytes) + assert.Equal(t, 1, h.PaddingBytes()) + + bytes = []byte{0xff, 0xe0, 0x0, 0} + h = header(bytes) + assert.Equal(t, 0, h.PaddingBytes()) } func TestIsProtected(t *testing.T) { @@ -88,11 +98,87 @@ func TestIsProtected(t *testing.T) { assert.False(t, h.IsProtected()) } +func TestSamplesPerFrame(t *testing.T) { + testCases := []struct { + name string + bytes []byte + expSamplesPerFrame int + }{ + { + "MPEG1 LayerI", + []byte{0xff, 0xfe, 0, 0}, + 384, + }, + { + "MPEG1 LayerII", + []byte{0xff, 0xfc, 0, 0}, + 1152, + }, + { + "MPEG1 LayerIII", + []byte{0xff, 0xfa, 0, 0}, + 1152, + }, + { + "MPEG1 LayerReserved", + []byte{0xff, 0xf8, 0, 0}, + 0, + }, + { + "MPEG2 LayerI", + []byte{0xff, 0xf6, 0, 0}, + 384, + }, + { + "MPEG2 LayerII", + []byte{0xff, 0xf4, 0, 0}, + 1152, + }, + { + "MPEG2 LayerIII", + []byte{0xff, 0xf2, 0, 0}, + 576, + }, + { + "MPEG2 LayerReserved", + []byte{0xff, 0xf0, 0, 0}, + 0, + }, + { + "MPEG25 LayerI", + []byte{0xff, 0xe6, 0, 0}, + 384, + }, + { + "MPEG25 LayerII", + []byte{0xff, 0xe4, 0, 0}, + 1152, + }, + { + "MPEG25 LayerIII", + []byte{0xff, 0xe2, 0, 0}, + 576, + }, + { + "MPEG25 LayerReserved", + []byte{0xff, 0xe0, 0, 0}, + 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + h := header(tc.bytes) + assert.Equal(t, tc.expSamplesPerFrame, h.SamplesPerFrame()) + }) + } +} + func TestBitRate(t *testing.T) { testCases := []struct { name string bytes []byte - expBitRate uint32 + expBitRate int }{ { "empty header", @@ -228,7 +314,7 @@ func TestSampleRate(t *testing.T) { testCases := []struct { name string bytes []byte - expSampleRate uint32 + expSampleRate int }{ { "MPEG1 44.1k",