Use libflac go bindings, updated audio to work with float32 slice channels instead of single values

This commit is contained in:
DataHoarder 2022-01-27 11:39:16 +01:00
parent 67b8ffe931
commit e222202792
9 changed files with 125 additions and 212 deletions

View file

@ -96,6 +96,24 @@ func TestHibiki(t *testing.T) {
return
}
/*
file2, err := os.Create("test_w.flac")
if err != nil {
t.Error(err)
return
}
defer file2.Close()
resampledStream, err := flacStream.DoResample(1, utilities.PANAKO_SAMPLE_RATE, audio.RESAMPLER_QUALITY_MEDIUM)
err = flacFormat.Encode(resampledStream, file2)
if err != nil {
log.Panic(err)
}
os.Exit(0)
*/
store := NewTestPanakoKeyValueStore()
strategy := panako.NewStrategy(store, false)
@ -115,27 +133,6 @@ func TestHibiki(t *testing.T) {
return
}
/*
file2, err := os.Create("test_w.raw")
if err != nil {
t.Error(err)
return
}
defer file2.Close()
resampledStream, err := radioStream.DoResample(1, utilities.PANAKO_SAMPLE_RATE, audio.RESAMPLER_QUALITY_MEDIUM)
c := resampledStream.GetAsChannel()
for {
f, more := <-c
if !more {
break
}
binary.Write(file2, binary.LittleEndian, f)
}
os.Exit(0)
*/
prints := strategy.StreamToFingerprints(radioStream)
flacTest, err := os.Open("test2.flac")

7
go.mod
View file

@ -4,13 +4,10 @@ go 1.18
require (
git.gammaspectra.live/S.O.N.G/goborator v0.0.0-20220126140813-481f5df7947e
github.com/cocoonlife/goflac v0.0.0-20170210142907-50ea06ed5a9d
github.com/dh1tw/gosamplerate v0.1.2
github.com/gammazero/deque v0.1.0
github.com/hajimehoshi/go-mp3 v0.3.2
github.com/mewkiz/flac v1.0.7
)
require (
github.com/icza/bitio v1.0.0 // indirect
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 // indirect
)
require github.com/cocoonlife/testify v0.0.0-20160218172820-792cc1faeb64 // indirect

19
go.sum
View file

@ -1,30 +1,19 @@
git.gammaspectra.live/S.O.N.G/goborator v0.0.0-20220126140813-481f5df7947e h1:dEzZ+/k/7BizZ+1FFkhtmmu4E2PhuDyq1Q3eelIMZOw=
git.gammaspectra.live/S.O.N.G/goborator v0.0.0-20220126140813-481f5df7947e/go.mod h1:ySjuueqe5HUqvf7lWS51Cy5UP2tgJWsezOv8UIm2arA=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/cocoonlife/goflac v0.0.0-20170210142907-50ea06ed5a9d h1:utj98F6D5jVv2tHYMsYzM6Z5sG71/W12Ivkd/SnFiN0=
github.com/cocoonlife/goflac v0.0.0-20170210142907-50ea06ed5a9d/go.mod h1:swNVb00X8NOH/qeHuqnqiyfecAnWlThLX+NbH8r6yHw=
github.com/cocoonlife/testify v0.0.0-20160218172820-792cc1faeb64 h1:LjPYdzoFSAJ5Tr/ElL8kzTJghXgpnOjJVbgd1UvZB1o=
github.com/cocoonlife/testify v0.0.0-20160218172820-792cc1faeb64/go.mod h1:LoCAz53rbPcqs8Da2BjB/yDy4gxMtiSQmqnYI/DGH+U=
github.com/dh1tw/gosamplerate v0.1.2 h1:oyqtZk67xB9B4l+vIZCZ3F0RYV/z66W58VOah11/ktI=
github.com/dh1tw/gosamplerate v0.1.2/go.mod h1:zooTyHpoR7hE+FLfdE3yjLHb2QA2NpMusNfuaZqEACM=
github.com/gammazero/deque v0.1.0 h1:f9LnNmq66VDeuAlSAapemq/U7hJ2jpIWa4c09q8Dlik=
github.com/gammazero/deque v0.1.0/go.mod h1:KQw7vFau1hHuM8xmI9RbgKFbAsQFWmBpqQ2KenFLk6M=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s=
github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8=
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/mewkiz/flac v1.0.7 h1:uIXEjnuXqdRaZttmSFM5v5Ukp4U6orrZsnYGGR3yow8=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2 h1:EyTNMdePWaoWsRSGQnXiSoQu0r6RS1eA557AwJhlzHU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View file

@ -50,13 +50,6 @@ func (s *Strategy) StoreStream(resourceId int64, stream *audio.Stream) float64 {
return 0
}
func (s *Strategy) StreamSliceToFingerprints(stream *audio.Stream, startTimeOffset, duration float64) []*Fingerprint {
stream.AdvanceSeconds(startTimeOffset) //This can err but it's fine
stream.SetMaxDuration(duration)
return s.StreamToFingerprints(stream)
}
func (s *Strategy) StreamToFingerprints(stream *audio.Stream) []*Fingerprint {
resample, err := stream.DoResample(1, utilities.PANAKO_SAMPLE_RATE, audio.RESAMPLER_QUALITY_MEDIUM)
if err != nil {

View file

@ -3,7 +3,8 @@ package flac
import (
"bytes"
"git.gammaspectra.live/S.O.N.G/Hibiki/utilities/audio"
flacLib "github.com/mewkiz/flac"
"git.gammaspectra.live/S.O.N.G/Hibiki/utilities/audio/format"
libflac "github.com/cocoonlife/goflac"
"io"
)
@ -14,97 +15,79 @@ func NewFormat() Format {
return Format{}
}
func (f Format) Open(r io.ReadSeeker) (*audio.Stream, error) {
stream, err := flacLib.Parse(r)
func (f Format) Open(r io.ReadSeekCloser) (*audio.Stream, error) {
decoder, err := libflac.NewDecoderReader(r)
if err != nil {
return nil, err
}
newChannel := make(chan float32)
newChannel := make(chan []float32)
go func() {
defer stream.Close()
defer decoder.Close()
defer close(newChannel)
frameNumber := 0
for {
currentFrame, err := stream.ParseNext()
currentFrame, err := decoder.ReadFrame()
if err != nil {
return
}
for sample := 0; sample < currentFrame.Subframes[0].NSamples; sample++ {
//Interleave samples
for _, subFrame := range currentFrame.Subframes {
//convert to f32 samples
newChannel <- float32(subFrame.Samples[sample]) / float32((int64(1)<<(currentFrame.BitsPerSample-1))-1)
}
//log.Printf("frame %d %d %d %d\n", frameNumber, currentFrame.Channels, currentFrame.Depth, currentFrame.Rate)
bitDepth := decoder.Depth
if currentFrame.Depth != 0 {
bitDepth = currentFrame.Depth
}
buf := make([]float32, len(currentFrame.Buffer))
for i, sample := range currentFrame.Buffer {
//convert to f32 samples
buf[i] = float32(sample) / float32((int64(1)<<(bitDepth-1))-1)
}
newChannel <- buf
frameNumber++
}
}()
return audio.NewStream(newChannel, int(stream.Info.NChannels), float64(stream.Info.SampleRate)), nil
return audio.NewStream(newChannel, decoder.Channels, float64(decoder.Rate)), nil
}
/*
func (f Format) Encode(stream *audio.Stream, writer io.Writer) error {
bitsPerSample := uint8(16)
func (f Format) Encode(stream *audio.Stream, writer format.WriteSeekCloser) error {
bitsPerSample := 16
encoder, err := flacLib.NewEncoder(writer, &meta.StreamInfo{
SampleRate: uint32(stream.GetSampleRate()),
NChannels: uint8(stream.GetChannels()),
BitsPerSample: bitsPerSample,
BlockSizeMin: 16,
BlockSizeMax: 65535,
FrameSizeMin: 0,
FrameSizeMax: 0,
})
encoder, err := libflac.NewEncoderWriter(writer, stream.GetChannels(), bitsPerSample, int(stream.GetSampleRate()))
if err != nil {
return err
}
defer encoder.Close()
for {
block, more := stream.GetBlock()
if stream.GetChannels() > 1 {
} else {
samples := make([]int32, len(block))
for i, v := range block {
samples[i] = int32(v * float32((int64(1)<<(bitsPerSample-1))-1))
}
err = encoder.WriteFrame(&frame.Frame{
Header: frame.Header{
BlockSize: uint16(len(samples)),
SampleRate: 0,
BitsPerSample: bitsPerSample,
HasFixedBlockSize: true,
},
Subframes: []*frame.Subframe{
{
SubHeader: frame.SubHeader{
Pred: frame.PredVerbatim,
},
Samples: samples,
NSamples: len(samples),
},
},
})
if err != nil {
return err
}
for block := range stream.GetAsBlockChannel() {
samples := make([]int32, len(block))
for i, v := range block {
samples[i] = int32(v * float32((int64(1)<<(bitsPerSample-1))-1))
}
err = encoder.WriteFrame(libflac.Frame{
Rate: int(stream.GetSampleRate()),
Channels: stream.GetChannels(),
Depth: bitsPerSample,
Buffer: samples,
})
if !more {
break
if err != nil {
return err
}
}
return nil
}*/
}
func (f Format) Identify(peek []byte, extension string) bool {
return bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 && extension == "flac"

View file

@ -10,10 +10,16 @@ type Format interface {
Identify(peek []byte, extension string) bool
// Open Opens a stream and decodes it into an audio.Stream
Open(r io.ReadSeeker) (audio.Stream, error)
Open(r io.ReadSeekCloser) (audio.Stream, error)
}
type WriteSeekCloser interface {
io.Writer
io.Closer
io.Seeker
}
type Encoder interface {
// Encode Receives a stream and encodes it into an io.Reader
Encode(stream *audio.Stream) (io.Reader, error)
// Encode Receives a stream and encodes it into a writer
Encode(stream *audio.Stream, writer WriteSeekCloser) error
}

View file

@ -15,24 +15,32 @@ func NewFormat() Format {
return Format{}
}
func (f Format) Open(r io.ReadSeeker) (*audio.Stream, error) {
func (f Format) Open(r io.ReadSeekCloser) (*audio.Stream, error) {
decoder, err := mp3Lib.NewDecoder(r)
if err != nil {
return nil, err
}
newChannel := make(chan float32)
newChannel := make(chan []float32)
go func() {
defer close(newChannel)
for {
sample := make([]float32, 2)
var i int16
err = binary.Read(decoder, binary.LittleEndian, &i)
if err != nil {
return
}
sample[0] = float32(i) / float32(math.MaxInt16)
err = binary.Read(decoder, binary.LittleEndian, &i)
if err != nil {
return
}
sample[1] = float32(i) / float32(math.MaxInt16)
newChannel <- float32(i) / float32(math.MaxInt16)
newChannel <- sample
}
}()

View file

@ -9,7 +9,6 @@ type Resampler struct {
converter gosamplerate.Src
ratio float64
channels int
buffer []float32
}
const (
@ -31,34 +30,12 @@ func NewResampler(from, to float64, channels, quality int) (Resampler, error) {
}, nil
}
func (r *Resampler) GetBlock() []float32 {
if len(r.buffer) > utilities.PANAKO_AUDIO_BLOCK_SIZE*r.channels {
s := r.buffer[0 : utilities.PANAKO_AUDIO_BLOCK_SIZE*r.channels]
r.buffer = r.buffer[utilities.PANAKO_AUDIO_BLOCK_SIZE*r.channels:]
return s
} else {
s := r.buffer
r.buffer = r.buffer[:0]
return s
}
func (r *Resampler) Process(block []float32) ([]float32, error) {
return r.converter.Process(block, r.ratio, false)
}
func (r *Resampler) Process(block []float32) error {
b, err := r.converter.Process(block, r.ratio, false)
if err != nil {
return err
}
r.buffer = append(r.buffer, b...)
return nil
}
func (r *Resampler) Finish() error {
b, err := r.converter.Process([]float32{}, r.ratio, true)
if err != nil {
return err
}
r.buffer = append(r.buffer, b...)
return nil
func (r *Resampler) Finish() ([]float32, error) {
return r.converter.Process([]float32{}, r.ratio, true)
}
func (r *Resampler) Reset() {

View file

@ -6,14 +6,13 @@ import (
)
type Stream struct {
source chan float32
channels int
sampleRate float64
samplesRead int
stopAtSample int
source chan []float32
channels int
sampleRate float64
buffer []float32
}
func NewStream(source chan float32, channels int, sampleRate float64) *Stream {
func NewStream(source chan []float32, channels int, sampleRate float64) *Stream {
return &Stream{
source: source,
channels: channels,
@ -46,45 +45,31 @@ func (s *Stream) GetAsChannel() chan float32 {
func (s *Stream) GetAsBlockChannel() chan []float32 {
newChannel := make(chan []float32)
go func() {
defer close(newChannel)
for {
v, more := s.GetBlock()
if !more {
return
for buf := range s.source {
s.buffer = append(s.buffer, buf...)
for len(s.buffer) >= utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels {
newChannel <- s.buffer[0 : utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels]
s.buffer = s.buffer[utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels:]
}
newChannel <- v
}
}()
return newChannel
}
func (s *Stream) Get() (float32, bool) {
if s.stopAtSample != 0 && s.samplesRead >= s.stopAtSample {
return 0, false
}
v, more := <-s.source
if more {
s.samplesRead++
}
return v, more
}
func (s *Stream) GetBlock() ([]float32, bool) {
buf := make([]float32, 0, utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels)
for {
f, more := s.Get()
if !more { //EOF
return buf, more
}
buf = append(buf, f)
if len(buf) == utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels {
return buf, true
var more bool
if len(s.buffer) == 0 {
s.buffer, more = <-s.source
if !more {
return 0, false
}
}
f := s.buffer[0]
s.buffer = s.buffer[1:]
return f, true
}
func (s *Stream) AdvanceSeconds(seconds float64) bool {
@ -98,10 +83,6 @@ func (s *Stream) AdvanceSeconds(seconds float64) bool {
return true
}
func (s *Stream) SetMaxDuration(seconds float64) {
s.stopAtSample = s.secondsIndex(seconds)
}
func (s *Stream) secondsIndex(seconds float64) int {
return int(seconds * s.sampleRate)
}
@ -120,13 +101,13 @@ func (s *Stream) DoResample(channels int, sampleRate float64, quality int) (*Str
return nil, err
}
newChannel := make(chan float32)
newChannel := make(chan []float32)
go func() {
defer rs.Delete()
defer close(newChannel)
handleTransform := func(buf []float32, end bool) error {
handleTransform := func(buf []float32) error {
if channels != s.channels && channels == 1 {
newBuf := make([]float32, 0, utilities.PANAKO_AUDIO_BLOCK_SIZE)
for i := 0; i < len(buf)/s.channels && i < utilities.PANAKO_AUDIO_BLOCK_SIZE; i++ {
@ -138,59 +119,41 @@ func (s *Stream) DoResample(channels int, sampleRate float64, quality int) (*Str
}
if len(newBuf) > 0 {
err = rs.Process(newBuf)
b, err := rs.Process(newBuf)
if err != nil {
return err
}
newChannel <- b
}
} else {
err = rs.Process(buf)
b, err := rs.Process(buf)
if err != nil {
return err
}
newChannel <- b
}
if end {
err = rs.Finish()
if err != nil {
return err
}
}
for {
b := rs.GetBlock()
if len(b) == 0 {
return nil
}
for _, d := range b {
newChannel <- d
}
}
return nil
}
var buf []float32
var more bool
var buffer []float32
for {
buf, more = s.GetBlock()
if !more { //EOF
break
}
if len(buf) == utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels {
err = handleTransform(buf, false)
for buf := range s.GetAsBlockChannel() {
buffer = append(buffer, buf...)
for len(buffer) >= utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels {
err = handleTransform(buffer[0 : utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels])
if err != nil {
return
}
buffer = buffer[utilities.PANAKO_AUDIO_BLOCK_SIZE*s.channels:]
}
}
err = handleTransform(buf, true)
b, err := rs.Finish()
if err != nil {
return
}
newChannel <- b
}()
return NewStream(newChannel, channels, sampleRate), nil