faiface / beep

A little package that brings sound to any Go application. Suitable for playback and audio-processing.
MIT License
2.07k stars 152 forks source link

Unable to reinitialize speakers on Linux #146

Open whereswaldon opened 2 years ago

whereswaldon commented 2 years ago

This small sample program panics on Linux:

package main

import (
    "fmt"
    "time"

    "github.com/faiface/beep"
    "github.com/faiface/beep/speaker"
)

func main() {
    format := beep.Format{
        SampleRate:  44100,
        NumChannels: 2,
        Precision:   2,
    }
    if err := speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)); err != nil {
        panic(fmt.Errorf("initializing speakers: %w", err))
    }
    speaker.Close()
    if err := speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)); err != nil {
        panic(fmt.Errorf("initializing speakers: %w", err))
    }
    speaker.Close()
}

I believe there must be a race or some kind of failed cleanup responsible, but I'm not certain.

The exact crash is:

fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x1a55 pc=0x7f9158d54e6a]

runtime stack:
runtime.throw({0x4ada0a?, 0x4?})
        /home/chris/.local/lib/go-1.18/src/runtime/panic.go:992 +0x71
runtime.sigpanic()
        /home/chris/.local/lib/go-1.18/src/runtime/signal_unix.go:802 +0x3a9

goroutine 6 [syscall]:
runtime.cgocall(0x48d430, 0xc000048e90)
        /home/chris/.local/lib/go-1.18/src/runtime/cgocall.go:157 +0x5c fp=0xc000048e68 sp=0xc000048e30 pc=0x4050dc
github.com/hajimehoshi/oto._Cfunc_snd_pcm_drop(0x1a1a0c0)
        _cgo_gotypes.go:134 +0x4c fp=0xc000048e90 sp=0xc000048e68 pc=0x48a6cc
github.com/hajimehoshi/oto.(*driver).Close.func1(0xc000090000?)
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/driver_linux.go:177 +0x46 fp=0xc000048ec8 sp=0xc000048e90 pc=0x48b946
github.com/hajimehoshi/oto.(*driver).Close(0x5f5e100?)
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/driver_linux.go:177 +0x1e fp=0xc000048ee8 sp=0xc000048ec8 pc=0x48b81e
github.com/hajimehoshi/oto.(*driverWriter).Close(0xc000078150)
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:170 +0xc9 fp=0xc000048f38 sp=0xc000048ee8 pc=0x489d49
github.com/hajimehoshi/oto.(*Context).Close(0xc00000e030)
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:106 +0xa5 fp=0xc000048f80 sp=0xc000048f38 pc=0x4897c5
github.com/hajimehoshi/oto.NewContext.func1()
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:89 +0x8f fp=0xc000048fe0 sp=0xc000048f80 pc=0x48968f
runtime.goexit()
        /home/chris/.local/lib/go-1.18/src/runtime/asm_amd64.s:1571 +0x1 fp=0xc000048fe8 sp=0xc000048fe0 pc=0x45fb21
created by github.com/hajimehoshi/oto.NewContext
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:84 +0x2af

goroutine 1 [sleep]:
time.Sleep(0x5f5e100)
        /home/chris/.local/lib/go-1.18/src/runtime/time.go:194 +0x12e
github.com/hajimehoshi/oto.(*driverWriter).Close(0xc0001b4000)
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:169 +0xb7
github.com/hajimehoshi/oto.(*Context).Close(0xc0001b6000)
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:106 +0xa5
github.com/faiface/beep/speaker.Close()
        /home/chris/Code/go/pkg/mod/github.com/faiface/beep@v1.1.0/speaker/speaker.go:73 +0x85
main.main()
        /tmp/testsound/main.go:24 +0x54

goroutine 18 [semacquire]:
sync.runtime_SemacquireMutex(0x0?, 0xfe?, 0x4c6b10?)
        /home/chris/.local/lib/go-1.18/src/runtime/sema.go:71 +0x25
sync.(*Mutex).lockSlow(0xc0001b4020)
        /home/chris/.local/lib/go-1.18/src/sync/mutex.go:162 +0x165
sync.(*Mutex).Lock(...)
        /home/chris/.local/lib/go-1.18/src/sync/mutex.go:81
github.com/hajimehoshi/oto.(*driverWriter).Write(0xc0001b4000, {0xc00012e000?, 0x100?, 0x8000?})
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:136 +0x97
io.copyBuffer({0x4c6b50, 0xc0001b4000}, {0x4c6b70, 0xc0001b0040}, {0x0, 0x0, 0x0})
        /home/chris/.local/lib/go-1.18/src/io/io.go:428 +0x204
io.Copy(...)
        /home/chris/.local/lib/go-1.18/src/io/io.go:385
github.com/hajimehoshi/oto.NewContext.func1()
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:85 +0x49
created by github.com/hajimehoshi/oto.NewContext
        /home/chris/Code/go/pkg/mod/github.com/hajimehoshi/oto@v0.7.1/context.go:84 +0x2af
exit status 2

Am I doing something wrong in how I reinitialize this? My use-case is an application that only needs to play sound sometimes. Leaving the speaker running eats 5% of the system's CPU time to process silence, so I want to shut down the speakers when they're not needed. When I try to re-initialized the speakers (even after a delay), I get a crash like this.

System info (Arch Linux):

$ go version
go version go1.18 linux/amd64
$ uname -a
Linux vendetta 5.17.3-arch1-1 #1 SMP PREEMPT Thu, 14 Apr 2022 01:18:36 +0000 x86_64 GNU/Linux
laustbn commented 2 years ago

I can't reproduce the crash on Mac, but perhaps that's not surprising since the (Linux) audio driver shows up in the stack trace.

Instead of streaming silence, have you considered blocking in your streamer or simply taking the mutex that the speaker exposes? (speaker.Lock()). That should keep it from doing anything, including polling empty streamers. I don't know this introduces bad side effects, but perhaps worth a shot.