Skip to content

Commit

Permalink
Merge pull request #173 from gopxl/fix-flac-seek
Browse files Browse the repository at this point in the history
Fix `flac.Seek()`
  • Loading branch information
MarkKremer committed Aug 27, 2024
2 parents 14e6327 + f595a79 commit 6bb4a2a
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 73 deletions.
175 changes: 103 additions & 72 deletions flac/decode.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package flac

import (
"fmt"
"io"

"github.com/mewkiz/flac"
"github.com/mewkiz/flac/frame"
"github.com/pkg/errors"

"github.com/gopxl/beep/v2"
Expand Down Expand Up @@ -32,10 +32,17 @@ func Decode(r io.Reader) (s beep.StreamSeekCloser, format beep.Format, err error
} else {
d.stream, err = flac.New(r)
}
if err != nil {
return nil, beep.Format{}, errors.Wrap(err, "flac")
}

// Read the first frame
d.frame, err = d.stream.ParseNext()
if err != nil {
return nil, beep.Format{}, errors.Wrap(err, "flac")
}
d.hasFixedBlockSize = d.frame.HasFixedBlockSize

format = beep.Format{
SampleRate: beep.SampleRate(d.stream.Info.SampleRate),
NumChannels: int(d.stream.Info.NChannels),
Expand All @@ -47,96 +54,70 @@ func Decode(r io.Reader) (s beep.StreamSeekCloser, format beep.Format, err error
type decoder struct {
r io.Reader
stream *flac.Stream
buf [][2]float64
pos int
frame *frame.Frame
posInFrame int
err error
seekEnabled bool

hasFixedBlockSize bool
}

func (d *decoder) Stream(samples [][2]float64) (n int, ok bool) {
if d.err != nil {
if d.err != nil || d.frame == nil {
return 0, false
}
// Copy samples from buffer.
j := 0
for i := range samples {
if j >= len(d.buf) {
// refill buffer.
if err := d.refill(); err != nil {
d.pos += n

for len(samples) > 0 {
samplesLeft := int(d.frame.BlockSize) - d.posInFrame
if samplesLeft <= 0 {
// Read next frame
var err error
d.frame, err = d.stream.ParseNext()
if err != nil {
d.frame = nil
if err == io.EOF {
return n, n > 0
}
d.err = err
d.err = errors.Wrap(err, "flac")
return 0, false
}
j = 0
d.posInFrame = 0
continue
}
samples[i] = d.buf[j]
j++
n++

toFill := min(samplesLeft, len(samples))
d.decodeFrameRangeInto(d.frame, d.posInFrame, toFill, samples)
d.posInFrame += toFill
n += toFill
samples = samples[toFill:]
}
d.buf = d.buf[j:]
d.pos += n

return n, true
}

// refill decodes audio samples to fill the decode buffer.
func (d *decoder) refill() error {
// Empty buffer.
d.buf = d.buf[:0]
// Parse audio frame.
frame, err := d.stream.ParseNext()
if err != nil {
return err
}
// Expand buffer size if needed.
n := len(frame.Subframes[0].Samples)
if cap(d.buf) < n {
d.buf = make([][2]float64, n)
} else {
d.buf = d.buf[:n]
}
// Decode audio samples.
// decodeFrameRangeInto decodes the samples frame from the position `start` up to `start + num`
// and stores them in Beep's format into the provided slice `into`.
func (d *decoder) decodeFrameRangeInto(frame *frame.Frame, start, num int, into [][2]float64) {
bps := d.stream.Info.BitsPerSample
nchannels := d.stream.Info.NChannels
numChannels := d.stream.Info.NChannels
s := 1 << (bps - 1)
q := 1 / float64(s)
switch {
case bps == 8 && nchannels == 1:
for i := 0; i < n; i++ {
d.buf[i][0] = float64(int8(frame.Subframes[0].Samples[i])) * q
d.buf[i][1] = float64(int8(frame.Subframes[0].Samples[i])) * q
}
case bps == 16 && nchannels == 1:
for i := 0; i < n; i++ {
d.buf[i][0] = float64(int16(frame.Subframes[0].Samples[i])) * q
d.buf[i][1] = float64(int16(frame.Subframes[0].Samples[i])) * q
}
case bps == 24 && nchannels == 1:
for i := 0; i < n; i++ {
d.buf[i][0] = float64(int32(frame.Subframes[0].Samples[i])) * q
d.buf[i][1] = float64(int32(frame.Subframes[0].Samples[i])) * q
}
case bps == 8 && nchannels >= 2:
for i := 0; i < n; i++ {
d.buf[i][0] = float64(int8(frame.Subframes[0].Samples[i])) * q
d.buf[i][1] = float64(int8(frame.Subframes[1].Samples[i])) * q
}
case bps == 16 && nchannels >= 2:
for i := 0; i < n; i++ {
d.buf[i][0] = float64(int16(frame.Subframes[0].Samples[i])) * q
d.buf[i][1] = float64(int16(frame.Subframes[1].Samples[i])) * q

if numChannels == 1 {
samples1 := frame.Subframes[0].Samples[start:]
for i := 0; i < num; i++ {
v := float64(samples1[i]) * q
into[i][0] = v
into[i][1] = v
}
case bps == 24 && nchannels >= 2:
for i := 0; i < n; i++ {
d.buf[i][0] = float64(frame.Subframes[0].Samples[i]) * q
d.buf[i][1] = float64(frame.Subframes[1].Samples[i]) * q
} else {
samples1 := frame.Subframes[0].Samples[start:]
samples2 := frame.Subframes[1].Samples[start:]
for i := 0; i < num; i++ {
into[i][0] = float64(samples1[i]) * q
into[i][1] = float64(samples2[i]) * q
}
default:
panic(fmt.Errorf("support for %d bits-per-sample and %d channels combination not yet implemented", bps, nchannels))
}
return nil
}

func (d *decoder) Err() error {
Expand All @@ -148,18 +129,68 @@ func (d *decoder) Len() int {
}

func (d *decoder) Position() int {
return d.pos
if d.frame == nil {
return d.Len()
}

// Temporary workaround until https://github.com/mewkiz/flac/pull/73 is resolved.
if d.hasFixedBlockSize {
return int(d.frame.Num)*int(d.stream.Info.BlockSizeMax) + d.posInFrame
}

return int(d.frame.SampleNumber()) + d.posInFrame
}

// p represents flac sample num perhaps?
func (d *decoder) Seek(p int) error {
if !d.seekEnabled {
return errors.New("flac.decoder.Seek: not enabled")
}

// Temporary workaround until https://github.com/mewkiz/flac/pull/73 is resolved.
// frame.SampleNumber() doesn't work for the last frame of a fixed block size stream
// with the result that seeking to that frame doesn't work either. Therefore, if such
// a seek is requested, we seek to one of the frames before it and consume until the
// desired position is reached.
if d.hasFixedBlockSize {
lastFrameStartLowerBound := d.Len() - int(d.stream.Info.BlockSizeMax)
if p >= lastFrameStartLowerBound {
// Seek to & consume an earlier frame.
_, err := d.stream.Seek(uint64(lastFrameStartLowerBound - 1))
if err != nil {
return errors.Wrap(err, "flac")
}
for {
d.frame, err = d.stream.ParseNext()
if err != nil {
return errors.Wrap(err, "flac")
}
// Calculate the frame start position manually, because this doesn't
// work for the last frame.
frameStart := d.frame.Num * uint64(d.stream.Info.BlockSizeMax)
if frameStart+uint64(d.frame.BlockSize) >= d.stream.Info.NSamples {
// Found the desired frame.
d.posInFrame = p - int(frameStart)
return nil
}
}
}
}

// d.stream.Seek() doesn't seek to the exact position p, instead
// it seeks to the start of the frame p is in. The frame position
// is returned and stored in pos.
pos, err := d.stream.Seek(uint64(p))
d.pos = int(pos)
return err
if err != nil {
return errors.Wrap(err, "flac")
}
d.posInFrame = p - int(pos)

d.frame, err = d.stream.ParseNext()
if err != nil {
return errors.Wrap(err, "flac")
}

return nil
}

func (d *decoder) Close() error {
Expand Down
Loading

0 comments on commit 6bb4a2a

Please sign in to comment.