faiface / beep

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

High CPU usage after `speaker.Init()` called #159

Open psaffrey opened 1 year ago

psaffrey commented 1 year ago

First of all, thanks for Beep and the excellent documentation.

I've written a doorbell in Go that listens to an MQTT channel and then plays a ding-dong noise when a button is pressed. I can deploy the code to multiple hosts on different platforms, which is the appeal of using Go for this.

My code runs as a systemd unit, starts up and initialises the sound streams using Beep's speaker package. However, I've noticed that the baseline CPU usages is pretty high - I have two wav files open and this is using ~15% CPU - even on a puny Celeron box this seems excessive. I've written some minimal code to reproduce this:

package main

import (
    "os"
    "github.com/faiface/beep"
    "github.com/faiface/beep/wav"
    "github.com/faiface/beep/speaker"    
    "time"
)

func main() {
    path := os.Args[1]

    f, _ := os.Open(path)

    streamer, format, _ := wav.Decode(f)    

    speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/100))
    time.Sleep(30 * time.Second)
    done := make(chan bool)
    speaker.Play(beep.Seq(streamer, beep.Callback(func() {
        done <- true
    })))
    <-done

}

Save the snippet above as play.go and then run with go run play.go <wavfile>. During the time.Sleep (after init, but before the sound is actually played) you can see the usage in top.

I can reduce the CPU usage by reducing the sample rate in the Init call - with time.Second it was down to about 1% - but that still seems high since it should be completely idle. I also tried using buffering as per the wiki, but that didn't make any difference. Did I miss something here? I'm using go1.18.1.

MawKKe commented 1 year ago

It seems that speaker.Init() launches a goroutine that polls update() continuously: https://github.com/faiface/beep/blob/master/speaker/speaker.go#L52, even when nothing is playing. It can be stopped with Close(), but then the speaker needs to be re-Init() before next Play()

MarkKremer commented 1 year ago

What I think is happening is that either os.Open() or wav.Decode() returns an error. This results in the format.SampleRate being 0 and the internal buffer of the speaker having a size of 0. I suspect this messes with one of the loops in some way. Can you confirm this by adding error checks and retrying your code?

It may be a nice addition to add a valid sample rate check in speaker.Init. Although that could invite that we then have to do that everywhere else too.

psaffrey commented 10 months ago

Sorry to comment on a closed issue and to take such a long time to get back to this: I tried this:

package main

import (
    "fmt"
    "os"
    "github.com/faiface/beep"
    "github.com/faiface/beep/wav"
    "github.com/faiface/beep/speaker"    
    "time"
)

func main() {
    path := os.Args[1]

    f, err := os.Open(path)
    if (err != nil) {
        fmt.Println(err)
    }

    streamer, format, err := wav.Decode(f)    
    if (err != nil) {
        fmt.Println(err)
    }

    fmt.Println(format.SampleRate)

    speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/100))
    time.Sleep(300 * time.Second)
    done := make(chan bool)
    speaker.Play(beep.Seq(streamer, beep.Callback(func() {
        done <- true
    })))
    <-done

 }

Neither the os.Open() nor the wav.Decode() return an error. The SampleRate is reported as 44100, which seems about right. Is there anything else I can try?

MarkKremer commented 10 months ago

Hey psaffrey, faiface/beep isn't being maintained anymore but we maintain a fork over at gopxl/beep. I've copied your comment to our repo: https://github.com/gopxl/beep/issues/14#issuecomment-1906982051. Let's continue the conversation there! :)