oto: Stutter on Raspberry Pi 400 using example

NOTE: I’m actively investigating this, but I thought I should put in an issue in case others are (or aren’t) seeing the same problem.

Raspberry Pi 400 (ARM v8) Default Pi OS that ships with Pi 400, but updated via apt to latest. HDMI audio. go 1.15.4

Effects:

  • go run example/main.go using master at time of posting ( 7f7e156100089346d897740cd0eba0a00c488751 ). The notes are not clear, they stutter quickly and continuously at the same interval.
    • If I increase bufferSizeInBytes in the NewContext call the stutter becomes less frequent, when I reach 80KB (the example starts at 4KB) it is an occasional pop every few seconds.
    • CPU usage is <10% on all cores when testing.
  • When using Oto via Ebiten to play mp3 music it plays slowly and with stutter - not sure if that’s indicative of buffer under-run (maybe specific to HDMI audio?)?

aplay plays wavs on the Pi without issue.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 16 (7 by maintainers)

Most upvoted comments

~/code/src/github.com/TheMightyGit $ git clone git@github.com:TheMightyGit/oto.git
Cloning into 'oto'...
remote: Enumerating objects: 90, done.
remote: Counting objects: 100% (90/90), done.
remote: Compressing objects: 100% (66/66), done.
remote: Total 822 (delta 55), reused 55 (delta 24), pack-reused 732
Receiving objects: 100% (822/822), 185.47 KiB | 1.13 MiB/s, done.
Resolving deltas: 100% (518/518), done.
~/code/src/github.com/TheMightyGit $ cd oto/
~/code/src/github.com/TheMightyGit/oto $ git checkout debug-buffer-sizes
Branch 'debug-buffer-sizes' set up to track remote branch 'debug-buffer-sizes' from 'origin'.
Switched to a new branch 'debug-buffer-sizes'
~/code/src/github.com/TheMightyGit/oto $ go run example/main.go
# github.com/hajimehoshi/oto
./driver_linux.go: In function ‘ALSA_hw_params’:
./driver_linux.go:51:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("buffer_size %d\n", *buffer_size);
         ^
./driver_linux.go:53:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("near buffer_size %d\n", *buffer_size);
         ^
./driver_linux.go:54:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("period_size %d\n", *period_size);
         ^
./driver_linux.go:56:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("near period_size %d\n", *period_size);
         ^
2020/11/22 08:09:23 Before ALSA_hw_params: 1024 1024
buffer_size 1024
near buffer_size 1024
period_size 1024
near period_size 341
2020/11/22 08:09:23 After ALSA_hw_params: 1024 341
~/code/src/github.com/TheMightyGit/oto $ 
~/code/src/github.com/TheMightyGit/oto $ git checkout rpi400-fix
Branch 'rpi400-fix' set up to track remote branch 'rpi400-fix' from 'origin'.
Switched to a new branch 'rpi400-fix'
~/code/src/github.com/TheMightyGit/oto $ go run example/main.go
# github.com/hajimehoshi/oto
./driver_linux.go: In function ‘ALSA_hw_params’:
./driver_linux.go:51:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("buffer_size %d\n", *buffer_size);
         ^
./driver_linux.go:53:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("near buffer_size %d\n", *buffer_size);
         ^
./driver_linux.go:54:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("period_size %d\n", *period_size);
         ^
./driver_linux.go:56:9: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘snd_pcm_uframes_t {aka long unsigned int}’ [-Wformat=]
  printf("near period_size %d\n", *period_size);
         ^
2020/11/22 08:10:13 Before ALSA_hw_params: 2048 1024
buffer_size 2048
near buffer_size 2048
period_size 1024
near period_size 682
2020/11/22 08:10:13 After ALSA_hw_params: 2048 682
~/code/src/github.com/TheMightyGit/oto $ 

Sounds the same - i.e., no buffer underrunning. There’s a click as each note disappears, but I would assume that’s due to a lack of zero crossing cutting.

I notice I’m getting a bunch of warnings that you’re not. Looks like a cast is missing somewhere perhaps.

@TheMightyGit Are you creating a PR, or are you still experimenting for a better fix?

@hajimehoshi

I was hoping @Asday would get a chance to try the above two branches on Linux desktop so I can confirm the theory that it doesn’t use buffers of the same size. At the moment I can’t tell if this is a general Linux problem that just happens to be fine on linux desktop, or if it’s specifically the RPi that’s at fault.

@Asday

$ git clone git@github.com:TheMightyGit/oto.git
$ cd oto
$ git checkout debug-buffer-sizes
$ go run example/main.go
<paste the output from this>
$ git checkout rpi400-fix
$ go run example/main.go
<paste the output from this, but does it also sound ok?>

thank you ❤️

For reference this is the output I get on the RPi 400 for branch debug-buffer-sizes (which stutters)…

2020/11/10 12:55:39 Before ALSA_hw_params: 1024 1024
buffer_size 1024
near buffer_size 1024
period_size 1024
near period_size 1024
2020/11/10 12:55:39 After ALSA_hw_params: 1024 1024

And this is the output on RPi 400 for branch rpi400-fix (no stutter)…

2020/11/10 12:58:06 Before ALSA_hw_params: 2048 1024
buffer_size 2048
near buffer_size 2048
period_size 1024
near period_size 1024
2020/11/10 12:58:06 After ALSA_hw_params: 2048 1024

I happen to be lucky enough to own a Linux desktop. If you’d like to throw together an SSCCE in the form of a git repo, I’ll gladly clone it and become a guinea pig.

If anyone else is playing around with this, this is what I did to ‘fix’ it…

diff --git a/driver_linux.go b/driver_linux.go
index 622fc0d..e4cb16e 100644
--- a/driver_linux.go
+++ b/driver_linux.go
@@ -94,6 +94,7 @@ func newDriver(sampleRate, numChans, bitDepthInBytes, bufferSizeInBytes int) (tr
        // buffer at once, we leave this value to bufferSize, because ALSA will change that
        // to the maximum viable number, obviously lower than bufferSize
        periodSize := bufferSize
+       bufferSize *= 2

        // choose the correct sample format according to bitDepthInBytes
        var format C.snd_pcm_format_t

I’ll keep investigating before suggesting a PR. I really need to test this on linux desktop.

wrote := C.snd_pcm_writei(p.handle, unsafe.Pointer(&p.buf[0]), C.snd_pcm_uframes_t(p.bufSamples))
if wrote == -C.EPIPE {

The pops and clicks definitely occur at the same time snd_pcm_writei returns -C.EPIPE - which indicates a buffer underrun from alsa (as suspected). But I’m no closer to working out why, yet.