Ogg streaming + better tests

This commit is contained in:
bloeys
2022-06-27 03:36:39 +04:00
parent daf46e5d41
commit 2af3583f6f
4 changed files with 132 additions and 28 deletions

75
ogg_streamer.go Executable file
View File

@ -0,0 +1,75 @@
package wavy
import (
"io"
"os"
"sync"
"github.com/jfreymuth/oggvorbis"
)
var _ io.ReadSeeker = &OggStreamer{}
type OggStreamer struct {
F *os.File
Dec *oggvorbis.Reader
//TODO: This is currently needed because of https://github.com/hajimehoshi/oto/issues/171
//We should be able to delete once its resolved
mutex sync.Mutex
}
func (ws *OggStreamer) Read(outBuf []byte) (floatsRead int, err error) {
ws.mutex.Lock()
readerBuf := make([]float32, len(outBuf)/2)
floatsRead, err = ws.Dec.Read(readerBuf)
F32ToUnsignedPCM16(readerBuf[:floatsRead], outBuf)
ws.mutex.Unlock()
return floatsRead * 2, err
}
func (ws *OggStreamer) Seek(offset int64, whence int) (int64, error) {
ws.mutex.Lock()
defer ws.mutex.Unlock()
//This is because ogg expects position in samples not bytes
offset /= BytesPerSample
switch whence {
case io.SeekStart:
if err := ws.Dec.SetPosition(offset); err != nil {
return 0, err
}
case io.SeekCurrent:
if err := ws.Dec.SetPosition(ws.Dec.Position() + offset); err != nil {
return 0, err
}
case io.SeekEnd:
if err := ws.Dec.SetPosition(ws.Dec.Length() + offset); err != nil {
return 0, err
}
}
return ws.Dec.Position() * BytesPerSample, nil
}
//Size returns number of bytes
func (ws *OggStreamer) Size() int64 {
return ws.Dec.Length() * BytesPerSample
}
func NewOggStreamer(f *os.File, dec *oggvorbis.Reader) *OggStreamer {
return &OggStreamer{
F: f,
Dec: dec,
mutex: sync.Mutex{},
}
}

View File

@ -36,13 +36,14 @@ func (ws *WavStreamer) Seek(offset int64, whence int) (int64, error) {
ws.mutex.Lock() ws.mutex.Lock()
defer ws.mutex.Unlock() defer ws.mutex.Unlock()
//This will only seek the underlying file but not the actual decoder because it can't seek
n, err := ws.Dec.Seek(offset, whence) n, err := ws.Dec.Seek(offset, whence)
if err != nil { if err != nil {
return n, err return n, err
} }
//The underlying parser can not seek back, so in that case we have to rewind the decoder //Since underlying decoder can't seek back, if the requested movement is back we have to rewind the decoder
//then seek forward. //then seek forward to the requested position.
if n < ws.Pos { if n < ws.Pos {
err = ws.Dec.Rewind() err = ws.Dec.Rewind()
@ -50,15 +51,14 @@ func (ws *WavStreamer) Seek(offset int64, whence int) (int64, error) {
return 0, err return 0, err
} }
//Anything before PCMStart is not valid sound, so the minimum seek back we allow is till PCMStart. //Anything before PCMStart is not valid sound, so the minimum seek back we allow is PCMStart
if n >= ws.PCMStart { if n < ws.PCMStart {
n = ws.PCMStart
} else {
n, err = ws.Dec.Seek(offset, whence) n, err = ws.Dec.Seek(offset, whence)
if err != nil { if err != nil {
return n, err return n, err
} }
} else {
n = ws.PCMStart
} }
} }
@ -66,6 +66,7 @@ func (ws *WavStreamer) Seek(offset int64, whence int) (int64, error) {
return n, err return n, err
} }
//Size returns number of bytes
func (ws *WavStreamer) Size() int64 { func (ws *WavStreamer) Size() int64 {
return ws.Dec.PCMLen() return ws.Dec.PCMLen()
} }
@ -77,6 +78,7 @@ func NewWavStreamer(f *os.File, wavDec *wav.Decoder) (*WavStreamer, error) {
return nil, err return nil, err
} }
//The actual data starts somewhat within the file, not at 0
currPos, err := wavDec.Seek(0, 1) currPos, err := wavDec.Seek(0, 1)
if err != nil { if err != nil {
return nil, err return nil, err

21
wavy.go
View File

@ -21,7 +21,6 @@ type SoundInfo struct {
Type SoundType Type SoundType
Mode SoundMode Mode SoundMode
//Size is the sound's size in bytes
Size int64 Size int64
} }
@ -415,15 +414,16 @@ func soundFromFile(f *os.File, s *Sound) error {
s.Info.Size = ws.Size() s.Info.Size = ws.Size()
} else if s.Info.Type == SoundType_OGG { } else if s.Info.Type == SoundType_OGG {
soundData, _, err := oggvorbis.ReadAll(f) oggReader, err := oggvorbis.NewReader(f)
if err != nil { if err != nil {
return err return err
} }
sb := &SoundBuffer{Data: F32ToUnsignedPCM16(soundData)} oggStreamer := NewOggStreamer(f, oggReader)
s.Data = sb
s.Player = Ctx.NewPlayer(sb) s.Data = oggStreamer
s.Info.Size = int64(len(sb.Data)) s.Player = Ctx.NewPlayer(oggStreamer)
s.Info.Size = oggStreamer.Size()
} }
if s.Data == nil { if s.Data == nil {
@ -510,7 +510,7 @@ func decodeSoundFromReaderSeeker(r io.ReadSeeker, s *Sound) error {
return err return err
} }
sb := &SoundBuffer{Data: F32ToUnsignedPCM16(soundData)} sb := &SoundBuffer{Data: F32ToUnsignedPCM16(soundData, nil)}
s.Data = sb s.Data = sb
s.Player = Ctx.NewPlayer(sb) s.Player = Ctx.NewPlayer(sb)
s.Info.Size = int64(len(sb.Data)) s.Info.Size = int64(len(sb.Data))
@ -599,9 +599,12 @@ func clamp01F64(x float64) float64 {
//F32ToUnsignedPCM16 takes PCM data stored as float32 between [-1, 1] //F32ToUnsignedPCM16 takes PCM data stored as float32 between [-1, 1]
//and returns a byte array of uint16, where each two subsequent bytes represent one uint16. //and returns a byte array of uint16, where each two subsequent bytes represent one uint16.
func F32ToUnsignedPCM16(fs []float32) []byte { func F32ToUnsignedPCM16(fs []float32, outBuf []byte) []byte {
if outBuf == nil {
outBuf = make([]byte, len(fs)*2)
}
outBuf := make([]byte, len(fs)*2)
for i := 0; i < len(fs); i++ { for i := 0; i < len(fs); i++ {
//Remap [-1,1]->[-32768, 32767], then re-interprets the int16 as a uint16. //Remap [-1,1]->[-32768, 32767], then re-interprets the int16 as a uint16.

View File

@ -7,19 +7,29 @@ import (
"github.com/bloeys/wavy" "github.com/bloeys/wavy"
) )
func TestSound(t *testing.T) { func TestWavy(t *testing.T) {
t.Run("Init", TestInit)
t.Run("MP3", MP3Subtest)
t.Run("Wav", WavSubtest)
t.Run("Ogg", OggSubtest)
}
func TestInit(t *testing.T) {
err := wavy.Init(wavy.SampleRate_44100, wavy.SoundChannelCount_2, wavy.SoundBitDepth_2) err := wavy.Init(wavy.SampleRate_44100, wavy.SoundChannelCount_2, wavy.SoundBitDepth_2)
if err != nil { if err != nil {
t.Errorf("Failed to init wavy. Err: %s\n", err) t.Errorf("Failed to init wavy. Err: %s\n", err)
return return
} }
}
func MP3Subtest(t *testing.T) {
const fatihaFilepath = "./test_audio_files/Fatiha.mp3" const fatihaFilepath = "./test_audio_files/Fatiha.mp3"
const tadaFilepath = "./test_audio_files/tada.mp3" const tadaFilepath = "./test_audio_files/tada.mp3"
const fatihaLenMS = 55484 const fatihaLenMS = 55484
//Streaming //Mp3 streaming
s, err := wavy.NewSoundStreaming(fatihaFilepath) s, err := wavy.NewSoundStreaming(fatihaFilepath)
if err != nil { if err != nil {
t.Errorf("Failed to load streaming sound with path '%s'. Err: %s\n", fatihaFilepath, err) t.Errorf("Failed to load streaming sound with path '%s'. Err: %s\n", fatihaFilepath, err)
@ -47,7 +57,7 @@ func TestSound(t *testing.T) {
return return
} }
//In-Memory //Mp3 in-memory
s, err = wavy.NewSoundMem(fatihaFilepath) s, err = wavy.NewSoundMem(fatihaFilepath)
if err != nil { if err != nil {
t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", fatihaFilepath, err) t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", fatihaFilepath, err)
@ -75,7 +85,7 @@ func TestSound(t *testing.T) {
return return
} }
//Memory 'tada.mp3' //'tada.mp3' memory
s, err = wavy.NewSoundMem(tadaFilepath) s, err = wavy.NewSoundMem(tadaFilepath)
if err != nil { if err != nil {
t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", tadaFilepath, err) t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", tadaFilepath, err)
@ -103,35 +113,49 @@ func TestSound(t *testing.T) {
s3 := wavy.ClipInMemSoundPercent(s2, 0, 0.25) s3 := wavy.ClipInMemSoundPercent(s2, 0, 0.25)
s3.LoopAsync(3) s3.LoopAsync(3)
s3.WaitLoop() s3.WaitLoop()
}
func WavSubtest(t *testing.T) {
//Wav
const wavFPath = "./test_audio_files/camera.wav" const wavFPath = "./test_audio_files/camera.wav"
s, err = wavy.NewSoundMem(wavFPath)
s, err := wavy.NewSoundMem(wavFPath)
if err != nil { if err != nil {
t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", wavFPath, err) t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", wavFPath, err)
return return
} }
s.PlaySync() s.PlaySync()
//Streaming wav //Wav streaming
s, err = wavy.NewSoundStreaming(wavFPath) s, err = wavy.NewSoundStreaming(wavFPath)
if err != nil { if err != nil {
t.Errorf("Failed to load streaming sound with path '%s'. Err: %s\n", tadaFilepath, err) t.Errorf("Failed to load streaming sound with path '%s'. Err: %s\n", wavFPath, err)
return return
} }
s.SeekToPercent(0.0)
s.PlaySync() s.PlaySync()
s.SeekToPercent(0.0) s.SeekToPercent(0.5)
s.PlaySync() s.PlaySync()
}
func OggSubtest(t *testing.T) {
//Ogg
const oggFPath = "./test_audio_files/camera.ogg" const oggFPath = "./test_audio_files/camera.ogg"
s, err = wavy.NewSoundMem(oggFPath) s, err := wavy.NewSoundMem(oggFPath)
if err != nil { if err != nil {
t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", oggFPath, err) t.Errorf("Failed to load memory sound with path '%s'. Err: %s\n", oggFPath, err)
return return
} }
s.PlaySync() s.PlaySync()
//Ogg streaming
s, err = wavy.NewSoundStreaming(oggFPath)
if err != nil {
t.Errorf("Failed to load streaming sound with path '%s'. Err: %s\n", oggFPath, err)
return
}
s.PlaySync()
s.SeekToPercent(.5)
s.PlaySync()
} }
func TestByteCountFromPlayTime(t *testing.T) { func TestByteCountFromPlayTime(t *testing.T) {