Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flac.Seek() #173

Merged
merged 8 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading