-
Notifications
You must be signed in to change notification settings - Fork 15
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
speaker.Init occupies 5% of the CPU, even if no audio is played #137
Comments
Hi there 👋 I'm not entirely sure this is fixable. But we can debug a bit to find where the CPU load is coming from. :) Could you run the following programs and report your CPU usage?
package main
import (
"fmt"
"time"
"github.com/gopxl/beep/speaker"
)
func main() {
fmt.Println("[Speaker test through Beep]")
err := speaker.Init(44100, 44100/30)
if err != nil {
return
}
time.Sleep(time.Second * 60)
}
package main
import (
"fmt"
"time"
"github.com/ebitengine/oto/v3"
)
func main() {
fmt.Println("[Speaker test through Oto with player]")
context, readyChan, err := oto.NewContext(&oto.NewContextOptions{
SampleRate: 44100,
ChannelCount: 2,
Format: oto.FormatSignedInt16LE,
BufferSize: time.Second / 30,
})
if err != nil {
panic(err)
}
<-readyChan
player := context.NewPlayer(&silenceReader{})
player.Play()
fmt.Println("Playing silence for 60s...")
time.Sleep(time.Second * 60)
}
type silenceReader struct {
}
func (s *silenceReader) Read(p []byte) (n int, err error) {
for i := range p {
p[i] = 0
}
return len(p), nil
}
package main
import (
"fmt"
"time"
"github.com/ebitengine/oto/v3"
)
func main() {
fmt.Println("[Speaker test through Oto without player]")
context, readyChan, err := oto.NewContext(&oto.NewContextOptions{
SampleRate: 44100,
ChannelCount: 2,
Format: oto.FormatSignedInt16LE,
BufferSize: time.Second / 30,
})
if err != nil {
panic(err)
}
<-readyChan
fmt.Println("Playing silence for 60s...")
time.Sleep(time.Second * 60)
} Do you know if the 5% CPU is of a single core or over all your cores? How does it compare to playing an audio file through another app? |
OK I've looked further into this and it may be fixable by suspending Oto's context and/or player. But I would still appreciate your response on my previous comment because that might lead to performance improvements when sound is playing. Edit: suspending the context at the right time may actually be quite difficult. Hmmmm |
Looked even deeper. On Darwin resuming the audio context could fail because Siri or something else is blocking it. I don't think auto-suspending the context is a good idea in that case. I don't know how that would impact a game for example. My profiler says that the bulk of the CPU load is in sending the samples away to the driver (on Linux at least). My current thought is to expose the |
Also suffering from a similar issue. What I observed is that: // File:
// assets/notification_1.ogg: Ogg data, Vorbis audio, mono, 48000 Hz, ~239920 bps
// Around 5% of CPU usage in a Macbook M1 Pro
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/8))
// Around 2% of CPU usage, and high lag (~500ms), but for my purposes this amount of lag is acceptable
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/2))
// Around 1.5% of CPU usage
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second))
// Around 1.2% of CPU usage
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second*2)) As it is clear in the example above, I am using
It would definitely work for my application if I could call I tried to manually call |
Would you be able to run a CPU profiler on it? PR's for the Suspend/Resume functionality are welcomed. |
I am new to Go and have zero idea on how to run a profiler, but if you have any good documentation on how to do so I can try. |
To be honest I just click on the profile button in the Goland IDE. But Go has easy and good profiling tools as well and it can export the profiler data: https://go.dev/blog/pprof It's possible to add the to-be-profiled code to a test and run the test command with the correct flags, or add the profiling code to an existing program like so: package main
import (
"flag"
"fmt"
"log"
"os"
"runtime/pprof"
"time"
"github.com/gopxl/beep/speaker"
)
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal(err)
}
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal(err)
}
defer pprof.StopCPUProfile()
}
fmt.Println("[Speaker test through Beep]")
err := speaker.Init(44100, 44100/30)
if err != nil {
log.Fatal(err)
}
time.Sleep(time.Second * 10)
} This can be run using
|
Just for fun. This is what it looks like for me after running You'll see that most time is spent in the |
Here you go: beep.prof.zip. |
For my particular case, since most of time the program is doing absolutely nothing (just waiting for a ticker to tick every 20 minutes to actually do something), I would still like to have the possibility to just suspend the context of the speaker so it can use ~0% CPU, until I need to resume the speaker to do work again. So even if the CPU usage could be reduced, I would appreciate the possibility of suspending/resuming the context. |
Definitely! |
|
Perfect, thank you. I am testing this in my application and it is working perfectly. Also I really like that I can combine the func speakerResume() {
err := speaker.Resume()
if err != nil {
log.Printf("Error while resuming speaker: %v\n", err)
}
}
func speakerSuspend() {
err := speaker.Suspend()
if err != nil {
log.Printf("Error while suspending speaker: %v\n", err)
}
}
func PlaySound() {
speakerResume()
speaker.Play(beep.Seq(
buffer.Streamer(0, buffer.Len()),
beep.Callback(speakerSuspend),
))
} |
Cool! Just a sidenote. Suspend will pretty immediately stop the speaker from sending samples to the driver while the callback gets called after all samples are consumed by the speaker, but they are still in the speaker package's buffer and haven't reached the driver yet. So it may cut off the last bufferSize of samples. If you notice it, just add a sleep of the same duration as the buffer in the callback before suspending. |
Question for everyone: would it be desirable if speaker.Suspend() stops consuming samples but waits with suspending until the whole buffer is send to the driver? So suspend will mean "stop consuming" instead of "stop sending to the driver". (I'm not sure if this is possible with Oto currently but lets assume it is for now) |
I think as long it is documented (with examples preferably), for me the current behavior is fine, and probably more flexible too. It is not like adding |
I don't see the point of the extra flexibility. If you need more sleep you could always add it. But are there reasons to not need any sleep? Manually sleeping isn't the most difficult but anything that makes the API more intuitive seems like a good thing. (and I can decide later if it's worth the effort). But maybe I'm a bit of a perfectionist. I do really appreciate your input. Tnx! |
Related to the CPU usage question of the author: ebitengine/oto#229 |
@thiagokokada New version got released just now. |
os:macos 12.6
When the audio is not playing, I see that the idle wake-up thread is constantly increasing in the activity monitor software on the Mac
I don't know why it doesn't play audio and still takes up CPU to calculate
Is there any way to solve it? Thank you
The text was updated successfully, but these errors were encountered: