From 470a543ca1da475d049b93034c65156057ad5650 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Thu, 28 Jan 2021 00:01:05 +0100 Subject: [PATCH] refactor --- internal/mpeg/mpeg.go | 1 - main.go | 154 +------------------------- pkg/mpeg/mpeg.go | 153 +++++++++++++++++++++++++ main_test.go => pkg/mpeg/mpeg_test.go | 59 +++++----- 4 files changed, 186 insertions(+), 181 deletions(-) delete mode 100644 internal/mpeg/mpeg.go create mode 100644 pkg/mpeg/mpeg.go rename main_test.go => pkg/mpeg/mpeg_test.go (87%) diff --git a/internal/mpeg/mpeg.go b/internal/mpeg/mpeg.go deleted file mode 100644 index fb49537..0000000 --- a/internal/mpeg/mpeg.go +++ /dev/null @@ -1 +0,0 @@ -package mpeg diff --git a/main.go b/main.go index 2228cb9..d649194 100644 --- a/main.go +++ b/main.go @@ -6,160 +6,12 @@ import ( "io" "log" "os" + + "git.netflux.io/rob/avframes/pkg/mpeg" ) const fname = "/home/rob/share/home-office/example.mp3" -type ( - AudioVersionId int - Layer int -) - -type Decoder struct{} - -const ( - AudioVersionMPEG25 AudioVersionId = iota - AudioVersionReserved - AudioVersionMPEG2 - AudioVersionMPEG1 -) - -const ( - LayerReserved Layer = iota - LayerIII - LayerII - LayerI -) - -var ( - 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}, - {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]int{ - {11025, 12000, 8000, 0}, - {0, 0, 0, 0}, - {22050, 24000, 16000, 0}, - {44100, 48000, 32000, 0}, - } - - samplesPerFrame = [4][4]int{ - {0, 576, 1152, 384}, - {0, 0, 0, 0}, - {0, 576, 1152, 384}, - {0, 1152, 1152, 384}, - } -) - -type header []byte - -func (h header) HasSyncWord() bool { - return h[0] == 0xff && h[1]&0xe0 == 0xe0 -} - -func (h header) AudioVersionId() AudioVersionId { - return AudioVersionId((h[1] >> 3) & 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) 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 len(h) < 4 { - return false - } - if !h.HasSyncWord() { - return false - } - if h.AudioVersionId() == AudioVersionReserved { - return false - } - if h.Layer() == LayerReserved { - return false - } - if h.BitRate() == 0 { - return false - } - if h.SampleRate() == 0 { - return false - } - - return true -} - -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.Layer() { - case LayerIII: - j = 2 - case LayerII: - j = 1 - case LayerI: - j = 0 - default: - return 0 - } - case AudioVersionReserved: - return 0 - case AudioVersionMPEG2, AudioVersionMPEG25: - switch h.Layer() { - case LayerIII, LayerII: - j = 4 - case LayerI: - j = 3 - default: - return 0 - } - } - return bitRates[j][h[2]>>4] -} - func main() { f, err := os.Open(fname) if err != nil { @@ -179,7 +31,7 @@ func main() { log.Fatal(err) } - if h := header(buf); h.IsValid() { + if h := mpeg.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 { diff --git a/pkg/mpeg/mpeg.go b/pkg/mpeg/mpeg.go new file mode 100644 index 0000000..71e898a --- /dev/null +++ b/pkg/mpeg/mpeg.go @@ -0,0 +1,153 @@ +package mpeg + +import "fmt" + +type ( + AudioVersionId int + Layer int +) + +type Decoder struct{} + +const ( + AudioVersionMPEG25 AudioVersionId = iota + AudioVersionReserved + AudioVersionMPEG2 + AudioVersionMPEG1 +) + +const ( + LayerReserved Layer = iota + LayerIII + LayerII + LayerI +) + +var ( + 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}, + {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]int{ + {11025, 12000, 8000, 0}, + {0, 0, 0, 0}, + {22050, 24000, 16000, 0}, + {44100, 48000, 32000, 0}, + } + + samplesPerFrame = [4][4]int{ + {0, 576, 1152, 384}, + {0, 0, 0, 0}, + {0, 576, 1152, 384}, + {0, 1152, 1152, 384}, + } +) + +type Header []byte + +func (h Header) HasSyncWord() bool { + return h[0] == 0xff && h[1]&0xe0 == 0xe0 +} + +func (h Header) AudioVersionId() AudioVersionId { + return AudioVersionId((h[1] >> 3) & 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) 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 len(h) < 4 { + return false + } + if !h.HasSyncWord() { + return false + } + if h.AudioVersionId() == AudioVersionReserved { + return false + } + if h.Layer() == LayerReserved { + return false + } + if h.BitRate() == 0 { + return false + } + if h.SampleRate() == 0 { + return false + } + + return true +} + +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.Layer() { + case LayerIII: + j = 2 + case LayerII: + j = 1 + case LayerI: + j = 0 + default: + return 0 + } + case AudioVersionReserved: + return 0 + case AudioVersionMPEG2, AudioVersionMPEG25: + switch h.Layer() { + case LayerIII, LayerII: + j = 4 + case LayerI: + j = 3 + default: + return 0 + } + } + return bitRates[j][h[2]>>4] +} diff --git a/main_test.go b/pkg/mpeg/mpeg_test.go similarity index 87% rename from main_test.go rename to pkg/mpeg/mpeg_test.go index 52fff91..f8a5109 100644 --- a/main_test.go +++ b/pkg/mpeg/mpeg_test.go @@ -1,8 +1,9 @@ -package main +package mpeg_test import ( "testing" + "git.netflux.io/rob/avframes/pkg/mpeg" "github.com/stretchr/testify/assert" ) @@ -13,7 +14,7 @@ func TestHasSyncWord(t *testing.T) { expResult bool }{ { - "empty header", + "empty mpeg.Header", []byte{0, 0, 0, 0}, false, }, @@ -28,7 +29,7 @@ func TestHasSyncWord(t *testing.T) { true, }, { - "full header", + "full mpeg.Header", []byte{0xff, 0xff, 0xff, 0xff}, true, }, @@ -36,7 +37,7 @@ func TestHasSyncWord(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - h := header(tc.bytes) + h := mpeg.Header(tc.bytes) assert.Equal(t, tc.expResult, h.HasSyncWord()) }) } @@ -44,38 +45,38 @@ func TestHasSyncWord(t *testing.T) { func TestAudioVersionId(t *testing.T) { bytes := []byte{0xff, 0xe8, 0, 0} - h := header(bytes) - assert.Equal(t, AudioVersionReserved, h.AudioVersionId()) + h := mpeg.Header(bytes) + assert.Equal(t, mpeg.AudioVersionReserved, h.AudioVersionId()) bytes = []byte{0xff, 0xf0, 0, 0} - h = header(bytes) - assert.Equal(t, AudioVersionMPEG2, h.AudioVersionId()) + h = mpeg.Header(bytes) + assert.Equal(t, mpeg.AudioVersionMPEG2, h.AudioVersionId()) bytes = []byte{0xff, 0xff, 0, 0} - h = header(bytes) - assert.Equal(t, AudioVersionMPEG1, h.AudioVersionId()) + h = mpeg.Header(bytes) + assert.Equal(t, mpeg.AudioVersionMPEG1, h.AudioVersionId()) bytes = []byte{0xff, 0xe0, 0, 0} - h = header(bytes) - assert.Equal(t, AudioVersionMPEG25, h.AudioVersionId()) + h = mpeg.Header(bytes) + assert.Equal(t, mpeg.AudioVersionMPEG25, h.AudioVersionId()) } func TestLayer(t *testing.T) { bytes := []byte{0xff, 0xe0, 0, 0} - h := header(bytes) - assert.Equal(t, LayerReserved, h.Layer()) + h := mpeg.Header(bytes) + assert.Equal(t, mpeg.LayerReserved, h.Layer()) bytes = []byte{0xff, 0xe2, 0, 0} - h = header(bytes) - assert.Equal(t, LayerIII, h.Layer()) + h = mpeg.Header(bytes) + assert.Equal(t, mpeg.LayerIII, h.Layer()) bytes = []byte{0xff, 0xe4, 0, 0} - h = header(bytes) - assert.Equal(t, LayerII, h.Layer()) + h = mpeg.Header(bytes) + assert.Equal(t, mpeg.LayerII, h.Layer()) bytes = []byte{0xff, 0xe6, 0, 0} - h = header(bytes) - assert.Equal(t, LayerI, h.Layer()) + h = mpeg.Header(bytes) + assert.Equal(t, mpeg.LayerI, h.Layer()) } func TestPaddingBytes(t *testing.T) { @@ -118,7 +119,7 @@ func TestPaddingBytes(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - h := header(tc.bytes) + h := mpeg.Header(tc.bytes) assert.Equal(t, tc.expPadding, h.PaddingBytes()) }) } @@ -126,11 +127,11 @@ func TestPaddingBytes(t *testing.T) { func TestIsProtected(t *testing.T) { bytes := []byte{0xff, 0xe1, 0, 0} - h := header(bytes) + h := mpeg.Header(bytes) assert.True(t, h.IsProtected()) bytes = []byte{0xff, 0xe0, 0, 0} - h = header(bytes) + h = mpeg.Header(bytes) assert.False(t, h.IsProtected()) } @@ -204,7 +205,7 @@ func TestSamplesPerFrame(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - h := header(tc.bytes) + h := mpeg.Header(tc.bytes) assert.Equal(t, tc.expSamplesPerFrame, h.SamplesPerFrame()) }) } @@ -217,7 +218,7 @@ func TestBitRate(t *testing.T) { expBitRate int }{ { - "empty header", + "empty mpeg.Header", []byte{0, 0, 0, 0}, 0, }, @@ -332,7 +333,7 @@ func TestBitRate(t *testing.T) { 160, }, { - "full header", + "full mpeg.Header", []byte{1, 1, 1, 1}, 0, }, @@ -340,7 +341,7 @@ func TestBitRate(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - h := header(tc.bytes) + h := mpeg.Header(tc.bytes) assert.Equal(t, tc.expBitRate, h.BitRate()) }) } @@ -416,7 +417,7 @@ func TestSampleRate(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - h := header(tc.bytes) + h := mpeg.Header(tc.bytes) assert.Equal(t, int(tc.expSampleRate), int(h.SampleRate())) }) } @@ -462,7 +463,7 @@ func TestLen(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - h := header(tc.bytes) + h := mpeg.Header(tc.bytes) assert.Equal(t, tc.expLen, h.Len()) }) }