From daf46e5d41b3a507008de8ebf3f070a8f3dc38d8 Mon Sep 17 00:00:00 2001 From: bloeys Date: Mon, 27 Jun 2022 01:54:22 +0400 Subject: [PATCH] Properly stream wav files+reduce effect of oto bug --- test_audio_files/camera.mp3 | Bin 0 -> 10168 bytes wav_streamer.go | 92 ++++++++++++++++++++++++++++++++++++ wavy.go | 66 ++++++++++++++++++++++---- wavy_test.go | 11 +++++ 4 files changed, 159 insertions(+), 10 deletions(-) create mode 100755 test_audio_files/camera.mp3 create mode 100755 wav_streamer.go diff --git a/test_audio_files/camera.mp3 b/test_audio_files/camera.mp3 new file mode 100755 index 0000000000000000000000000000000000000000..01b8140a78f9a722156d2d49f694dace99a09e7c GIT binary patch literal 10168 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 ws.PCMStart { + + n, err = ws.Dec.Seek(offset, whence) + if err != nil { + return n, err + } + } else { + n = ws.PCMStart + } + } + + ws.Pos = n + return n, err +} + +func (ws *WavStreamer) Size() int64 { + return ws.Dec.PCMLen() +} + +func NewWavStreamer(f *os.File, wavDec *wav.Decoder) (*WavStreamer, error) { + + err := wavDec.FwdToPCM() + if err != nil { + return nil, err + } + + currPos, err := wavDec.Seek(0, 1) + if err != nil { + return nil, err + } + + return &WavStreamer{ + F: f, + Dec: wavDec, + Pos: currPos, + PCMStart: currPos, + mutex: sync.Mutex{}, + }, nil +} diff --git a/wavy.go b/wavy.go index 669ffd1..a3a57ca 100644 --- a/wavy.go +++ b/wavy.go @@ -234,12 +234,14 @@ func (s *Sound) IsPlaying() bool { //percent is clamped [0,1], so passing <0 is the same as zero, and >1 is the same as 1 func (s *Sound) SeekToPercent(percent float64) { + percent = clamp01F64(percent) + s.Data.Seek(int64(float64(s.Info.Size)*percent), io.SeekStart) + + //NOTE: Due to https://github.com/hajimehoshi/oto/issues/171, it is safer to seek before reset so we don't seek while a read is happening. + //This can still happen though if for example sound was paused midway then seeked, as read would be getting called if !s.IsPlaying() { s.Player.Reset() } - - percent = clamp01F64(percent) - s.Data.Seek(int64(float64(s.Info.Size)*percent), io.SeekStart) } //SeekToTime moves the current position of the sound to the given duration. @@ -250,10 +252,6 @@ func (s *Sound) SeekToPercent(percent float64) { //t is clamped between [0, totalTime] func (s *Sound) SeekToTime(t time.Duration) { - if !s.IsPlaying() { - s.Player.Reset() - } - byteCount := ByteCountFromPlayTime(t) if byteCount < 0 { byteCount = 0 @@ -262,6 +260,10 @@ func (s *Sound) SeekToTime(t time.Duration) { } s.Data.Seek(byteCount, io.SeekStart) + + if !s.IsPlaying() { + s.Player.Reset() + } } func (s *Sound) IsClosed() bool { @@ -381,7 +383,7 @@ func NewSoundStreaming(fpath string) (s *Sound, err error) { }, } - err = soundFromReaderSeeker(file, s) + err = soundFromFile(file, s) if err != nil { return nil, getLoadingErr(fpath, err) } @@ -389,6 +391,48 @@ func NewSoundStreaming(fpath string) (s *Sound, err error) { return s, nil } +func soundFromFile(f *os.File, s *Sound) error { + + if s.Info.Type == SoundType_MP3 { + + dec, err := mp3.NewDecoder(f) + if err != nil { + return err + } + + s.Data = dec + s.Player = Ctx.NewPlayer(dec) + s.Info.Size = dec.Length() + } else if s.Info.Type == SoundType_WAV { + + ws, err := NewWavStreamer(f, wav.NewDecoder(f)) + if err != nil { + return err + } + + s.Data = ws + s.Player = Ctx.NewPlayer(ws) + s.Info.Size = ws.Size() + } else if s.Info.Type == SoundType_OGG { + + soundData, _, err := oggvorbis.ReadAll(f) + if err != nil { + return err + } + + sb := &SoundBuffer{Data: F32ToUnsignedPCM16(soundData)} + s.Data = sb + s.Player = Ctx.NewPlayer(sb) + s.Info.Size = int64(len(sb.Data)) + } + + if s.Data == nil { + panic("invalid sound type. This is probably a bug!") + } + + return nil +} + //NewSoundMem loads the entire sound file into memory func NewSoundMem(fpath string) (s *Sound, err error) { @@ -410,7 +454,7 @@ func NewSoundMem(fpath string) (s *Sound, err error) { }, } - err = soundFromReaderSeeker(bytesReader, s) + err = decodeSoundFromReaderSeeker(bytesReader, s) if err != nil { return nil, getLoadingErr(fpath, err) } @@ -422,7 +466,9 @@ func getLoadingErr(fpath string, err error) error { return fmt.Errorf("failed to load '%s' with err '%s'", fpath, err.Error()) } -func soundFromReaderSeeker(r io.ReadSeeker, s *Sound) error { +//decodeSoundFromReaderSeeker reads and decodes till EOF, and places the final +//PCM16 data in a buffer, thus producing an in-memory sound +func decodeSoundFromReaderSeeker(r io.ReadSeeker, s *Sound) error { if s.Info.Type == SoundType_MP3 { diff --git a/wavy_test.go b/wavy_test.go index 8519528..81f14b5 100755 --- a/wavy_test.go +++ b/wavy_test.go @@ -113,6 +113,17 @@ func TestSound(t *testing.T) { } s.PlaySync() + //Streaming wav + s, err = wavy.NewSoundStreaming(wavFPath) + if err != nil { + t.Errorf("Failed to load streaming sound with path '%s'. Err: %s\n", tadaFilepath, err) + return + } + s.SeekToPercent(0.0) + s.PlaySync() + s.SeekToPercent(0.0) + s.PlaySync() + //Ogg const oggFPath = "./test_audio_files/camera.ogg" s, err = wavy.NewSoundMem(oggFPath)