Removed main dependencies, output float32 instead of bytes, rename package
This commit is contained in:
parent
c4ad2cec0a
commit
42e7219a3f
|
@ -1,6 +1,6 @@
|
|||
# go-mp3
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/hajimehoshi/go-mp3?status.svg)](http://godoc.org/github.com/hajimehoshi/go-mp3)
|
||||
[![GoDoc](https://godoc.org/git.gammaspectra.live/S.O.N.G/go-mp3?status.svg)](http://godoc.org/git.gammaspectra.live/S.O.N.G/go-mp3)
|
||||
|
||||
An MP3 decoder in pure Go based on [PDMP3](https://github.com/technosaurus/PDMP3).
|
||||
|
||||
|
|
|
@ -17,12 +17,12 @@ package mp3
|
|||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkDecode(b *testing.B) {
|
||||
buf, err := ioutil.ReadFile("example/classic.mp3")
|
||||
buf, err := os.ReadFile("testdata/classic.mp3")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
@ -35,8 +35,12 @@ func BenchmarkDecode(b *testing.B) {
|
|||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if _, err := ioutil.ReadAll(d); err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
data := make([]float32, 0, 64)
|
||||
for {
|
||||
if _, err := d.ReadFloat(data); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
33
decode.go
33
decode.go
|
@ -18,23 +18,23 @@ import (
|
|||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frame"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/consts"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/frame"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/frameheader"
|
||||
)
|
||||
|
||||
// A Decoder is a MP3-decoded stream.
|
||||
//
|
||||
// Decoder decodes its underlying source on the fly.
|
||||
type Decoder struct {
|
||||
source *source
|
||||
sampleRate int
|
||||
length int64
|
||||
frameStarts []int64
|
||||
buf []byte
|
||||
frame *frame.Frame
|
||||
pos int64
|
||||
bytesPerFrame int64
|
||||
source *source
|
||||
sampleRate int
|
||||
length int64
|
||||
frameStarts []int64
|
||||
buf []float32
|
||||
frame *frame.Frame
|
||||
pos int64
|
||||
samplesPerFrame int64
|
||||
}
|
||||
|
||||
func (d *Decoder) readFrame() error {
|
||||
|
@ -54,8 +54,7 @@ func (d *Decoder) readFrame() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Read is io.Reader's Read.
|
||||
func (d *Decoder) Read(buf []byte) (int, error) {
|
||||
func (d *Decoder) ReadFloat(buf []float32) (int, error) {
|
||||
for len(d.buf) == 0 {
|
||||
if err := d.readFrame(); err != nil {
|
||||
return 0, err
|
||||
|
@ -94,7 +93,7 @@ func (d *Decoder) Seek(offset int64, whence int) (int64, error) {
|
|||
d.pos = npos
|
||||
d.buf = nil
|
||||
d.frame = nil
|
||||
f := d.pos / d.bytesPerFrame
|
||||
f := d.pos / d.samplesPerFrame
|
||||
// If the frame is not first, read the previous ahead of reading that
|
||||
// because the previous frame can affect the targeted frame.
|
||||
if f > 0 {
|
||||
|
@ -108,7 +107,7 @@ func (d *Decoder) Seek(offset int64, whence int) (int64, error) {
|
|||
if err := d.readFrame(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.buf = d.buf[d.bytesPerFrame+(d.pos%d.bytesPerFrame):]
|
||||
d.buf = d.buf[d.samplesPerFrame+(d.pos%d.samplesPerFrame):]
|
||||
} else {
|
||||
if _, err := d.source.Seek(d.frameStarts[f], 0); err != nil {
|
||||
return 0, err
|
||||
|
@ -163,8 +162,8 @@ func (d *Decoder) ensureFrameStartsAndLength() error {
|
|||
return err
|
||||
}
|
||||
d.frameStarts = append(d.frameStarts, pos)
|
||||
d.bytesPerFrame = int64(h.BytesPerFrame())
|
||||
l += d.bytesPerFrame
|
||||
d.samplesPerFrame = int64(h.SamplesPerFrame())
|
||||
l += d.samplesPerFrame
|
||||
|
||||
framesize, err := h.FrameSize()
|
||||
if err != nil {
|
||||
|
|
Binary file not shown.
|
@ -1,65 +0,0 @@
|
|||
// Copyright 2017 Hajime Hoshi
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/oto/v2"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3"
|
||||
)
|
||||
|
||||
func run() error {
|
||||
f, err := os.Open("classic.mp3")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
d, err := mp3.NewDecoder(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, ready, err := oto.NewContext(d.SampleRate(), 2, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-ready
|
||||
|
||||
p := c.NewPlayer(d)
|
||||
defer p.Close()
|
||||
p.Play()
|
||||
|
||||
fmt.Printf("Length: %d[bytes]\n", d.Length())
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
if !p.IsPlaying() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
Binary file not shown.
4
go.mod
4
go.mod
|
@ -1,5 +1,3 @@
|
|||
module github.com/hajimehoshi/go-mp3
|
||||
module git.gammaspectra.live/S.O.N.G/go-mp3
|
||||
|
||||
go 1.14
|
||||
|
||||
require github.com/hajimehoshi/oto/v2 v2.3.1
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1,4 +0,0 @@
|
|||
github.com/hajimehoshi/oto/v2 v2.3.1 h1:qrLKpNus2UfD674oxckKjNJmesp9hMh7u7QCrStB3Rc=
|
||||
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
@ -16,8 +16,6 @@ package bits_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
)
|
||||
|
||||
func TestBits(t *testing.T) {
|
||||
|
|
|
@ -19,12 +19,12 @@ import (
|
|||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"github.com/hajimehoshi/go-mp3/internal/imdct"
|
||||
"github.com/hajimehoshi/go-mp3/internal/maindata"
|
||||
"github.com/hajimehoshi/go-mp3/internal/sideinfo"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/bits"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/consts"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/frameheader"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/imdct"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/maindata"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/sideinfo"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -115,8 +115,8 @@ func (f *Frame) SamplingFrequency() (int, error) {
|
|||
return f.header.SamplingFrequencyValue()
|
||||
}
|
||||
|
||||
func (f *Frame) Decode() []byte {
|
||||
out := make([]byte, f.header.BytesPerFrame())
|
||||
func (f *Frame) Decode() []float32 {
|
||||
out := make([]float32, f.header.SamplesPerFrame())
|
||||
nch := f.header.NumberOfChannels()
|
||||
for gr := 0; gr < f.header.Granules(); gr++ {
|
||||
for ch := 0; ch < nch; ch++ {
|
||||
|
@ -128,7 +128,7 @@ func (f *Frame) Decode() []byte {
|
|||
f.antialias(gr, ch)
|
||||
f.hybridSynthesis(gr, ch)
|
||||
f.frequencyInversion(gr, ch)
|
||||
f.subbandSynthesis(gr, ch, out[consts.SamplesPerGr*4*gr:])
|
||||
f.subbandSynthesis(gr, ch, out[consts.SamplesPerGr*gr:])
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
@ -621,7 +621,7 @@ var synthDtbl = [512]float32{
|
|||
0.000015259, 0.000015259, 0.000015259, 0.000015259,
|
||||
}
|
||||
|
||||
func (f *Frame) subbandSynthesis(gr int, ch int, out []byte) {
|
||||
func (f *Frame) subbandSynthesis(gr int, ch int, out []float32) {
|
||||
u_vec := make([]float32, 512)
|
||||
s_vec := make([]float32, 32)
|
||||
|
||||
|
@ -653,29 +653,19 @@ func (f *Frame) subbandSynthesis(gr int, ch int, out []byte) {
|
|||
for j := 0; j < 512; j += 32 {
|
||||
sum += u_vec[j+i]
|
||||
}
|
||||
// sum now contains time sample 32*ss+i. Convert to 16-bit signed int
|
||||
samp := int(sum * 32767)
|
||||
if samp > 32767 {
|
||||
samp = 32767
|
||||
} else if samp < -32767 {
|
||||
samp = -32767
|
||||
}
|
||||
s := int16(samp)
|
||||
idx := 4 * (32*ss + i)
|
||||
|
||||
idx := 2 * (32*ss + i)
|
||||
|
||||
if nch == 1 {
|
||||
// We always run in stereo mode and duplicate channels here for mono.
|
||||
out[idx] = byte(s)
|
||||
out[idx+1] = byte(s >> 8)
|
||||
out[idx+2] = byte(s)
|
||||
out[idx+3] = byte(s >> 8)
|
||||
out[idx] = sum
|
||||
out[idx+1] = sum
|
||||
continue
|
||||
}
|
||||
if ch == 0 {
|
||||
out[idx] = byte(s)
|
||||
out[idx+1] = byte(s >> 8)
|
||||
out[idx] = sum
|
||||
} else {
|
||||
out[idx+2] = byte(s)
|
||||
out[idx+3] = byte(s >> 8)
|
||||
out[idx+1] = sum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/consts"
|
||||
)
|
||||
|
||||
// A mepg1FrameHeader is MPEG1 Layer 1-3 frame header
|
||||
|
@ -122,8 +122,8 @@ func (f FrameHeader) LowSamplingFrequency() int {
|
|||
return 1
|
||||
}
|
||||
|
||||
func (f FrameHeader) BytesPerFrame() int {
|
||||
return consts.SamplesPerGr * f.Granules() * 4
|
||||
func (f FrameHeader) SamplesPerFrame() int {
|
||||
return consts.SamplesPerGr * f.Granules() * 2
|
||||
}
|
||||
|
||||
func (f FrameHeader) Granules() int {
|
||||
|
|
|
@ -17,7 +17,7 @@ package huffman
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/bits"
|
||||
)
|
||||
|
||||
var huffmanTable = []uint16{
|
||||
|
|
|
@ -17,11 +17,11 @@ package maindata
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"github.com/hajimehoshi/go-mp3/internal/huffman"
|
||||
"github.com/hajimehoshi/go-mp3/internal/sideinfo"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/bits"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/consts"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/frameheader"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/huffman"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/sideinfo"
|
||||
)
|
||||
|
||||
func readHuffman(m *bits.Bits, header frameheader.FrameHeader, sideInfo *sideinfo.SideInfo, mainData *MainData, part_2_start, gr, ch int) error {
|
||||
|
|
|
@ -18,10 +18,10 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"github.com/hajimehoshi/go-mp3/internal/sideinfo"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/bits"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/consts"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/frameheader"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/sideinfo"
|
||||
)
|
||||
|
||||
type FullReader interface {
|
||||
|
|
|
@ -18,9 +18,9 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3/internal/bits"
|
||||
"github.com/hajimehoshi/go-mp3/internal/consts"
|
||||
"github.com/hajimehoshi/go-mp3/internal/frameheader"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/bits"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/consts"
|
||||
"git.gammaspectra.live/S.O.N.G/go-mp3/internal/frameheader"
|
||||
)
|
||||
|
||||
type FullReader interface {
|
||||
|
|
0
testdata/classic.mp3
vendored
Normal file
0
testdata/classic.mp3
vendored
Normal file
0
testdata/mpeg2.mp3
vendored
Normal file
0
testdata/mpeg2.mp3
vendored
Normal file
Loading…
Reference in a new issue