Piping multi-channel signal

Hi,

I'm trying to play 32-channel WAV file on Raspberry Pi 3:

Code:
$ aplay untitled.wav
Playing WAVE 'untitled.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Channels 32
At the same time I'm piping the signal to the named pipe using ALSA 'file' plugin:

Code:
pcm.!default {
  type plug
  slave.pcm mypcm;
}
ctl.!default {
  type hw card 0
}
pcm.mypcm {
  type file
  slave.pcm "plughw:0,0"
  file /home/pi/myfifo
  format raw
}
I'm reading from the pipe using the following Python script:

Code:
import os
import time

pipe = os.open("/home/pi/myfifo",  os.O_RDONLY | os.O_NONBLOCK)
while True:
    try:
        data = os.read(pipe, 400000)
    except Exception as e:
        pass
    time.sleep(0.001)
The file length is 30 sec. The sound in speaker stops after 30 seconds but aplay still running for another 30 seconds. Is it because the 'file' plugin is not quick enough to handle the signal? Thanks!
 
I've slightly changed the script - reduced polling interval to 0.1 instead of 0.001 and added logging. It looks like polling interval is not affecting the result that much. More critical parameter is the buffer size for reading pipe.

Code:
import os
import time

pipe = os.open("/home/pi/myfifo", os.O_RDONLY | os.O_NONBLOCK)
print("pipe opened")
while True:
    try:
        data = os.read(pipe, 400000)
        print(str(len(data)))
    except Exception as e:
        print(e)
    time.sleep(0.1)
aplay works as before - sound is audible in speaker for 30 sec then aplay keeps running for another 30 sec.

Here is the final part of the script output:
Code:
.....
352768
352768
[Errno 11] Resource temporarily unavailable
352768
352768
352768
352768
[Errno 11] Resource temporarily unavailable
352768
352768
352768
352768
[Errno 11] Resource temporarily unavailable
[Errno 11] Resource temporarily unavailable
[Errno 11] Resource temporarily unavailable
[Errno 11] Resource temporarily unavailable
400000
400000
400000
211200
0
0
0
0 in the end shows up when aplay stopped running after about 1 min.
If I change the buffer size in script from 400000 to 300000 aplay logs these messages and sound stutters in the speaker:
Code:
$ aplay untitled.wav
Playing WAVE 'untitled.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Channels 32
underrun!!! (at least 29.283 ms long)
underrun!!! (at least 38.067 ms long)
underrun!!! (at least 37.132 ms long)
.....
The script output in this case is like this:
Code:
.....

300000
105536
300000
300000
105536
300000
300000
64576
[Errno 11] Resource temporarily unavailable
300000
300000
300000
300000
252160
0
0
0
So, I believe the script with 400000 buffer can handle the incoming stream from pipe and the issue is somewhere else, probably 'file' plugin? Maybe it makes sense to demux the signal (using some ALSA plugin) into 32 pipe streams per channel and start separate thread in the script for each pipe?
 
Last edited:
I would try:

* adding timestamps to the log

* outputting the existing 32ch stream to a regular file instead of a fifo. My 2 cents it will work OK

* running the aplay command in strace to see what syscalls are called after the sound is over and aplay is still running (the other 30 seconds).
 
As you recommended I redirected the stream from pipe to file and made some experiments. The behavior of the file output was the same as pipe output - 30 seconds with sound and 30 seconds without sound.

Code:
pcm.!default {
  type plug
  slave.pcm mypcm;
}
ctl.!default {
  type hw card 0
}
pcm.mypcm {
  type file
  slave.pcm "plughw:0,0"
  file /home/pi/aaa.wav
  format raw
}
The original WAV file size is 169,344,044 bytes. I started 'aplay' and after 30 seconds when the sound in speaker stopped I killed 'aplay' and checked the size of the generated file aaa.wav. It was almost half of the original file 87,133,696 bytes. If I don't kill 'aplay' it keeps running for about 1 min and the size of the generated file is almost the same as original (few bytes difference).

I also ran 'aplay' with strace. For about 15 seconds I see this kind of messages:
Code:
...
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32890) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32890) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32890) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32890) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32890) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32760) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32760) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32760) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32760) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32760) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32778) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32778) = 0
ioctl(4, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ea32778) = 0
...
Then till the end (another 45 sec) I saw this kind of messages:
Code:
...
write(5, "\300\315\275\315\277\315\277\315\300\315\276\315\277\315\303\315\277\315\302\315\277\315\300\315\302\315\276\315\302\315\300\315"..., 352768) = 352768
read(3, "\272\310\275\310\300\310\300\310\275\310\300\310\275\310\277\310\300\310\301\310\300\310\277\310\274\310\275\310\275\310\276\310"..., 352768) = 352768
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
write(5, "\300\310\276\310\275\310\275\310\300\310\274\310\300\310\277\310\273\310\275\310\273\310\276\310\275\310\275\310\274\310\300\310"..., 352768) = 352768
read(3, "\355\303\361\303\362\303\361\303\355\303\362\303\360\303\364\303\357\303\357\303\362\303\361\303\357\303\360\303\357\303\356\303"..., 352768) = 352768
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
poll([{fd=4, events=POLLOUT|POLLERR|POLLNVAL}], 1, -1) = 1 ([{fd=4, revents=POLLOUT}])
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
ioctl(4, SNDRV_PCM_IOCTL_SYNC_PTR, 0x75af38) = 0
...
I believe that proves that the bottleneck is not the Python script. More likely it's the 'file' plugin. If I run 'aplay' without 'file' plugin it works as expected and it stops after 30 seconds. It looks like 'file' plugin cannot handle the signal and it's buffering it. Or maybe OS io buffer is not big enough (?) I'm not sure if it's possible to fix this somehow.

Thanks
 
I'd like to see if it's possible to use PeppyMeter (modified for multiple channels) with mutli-channel Audio-Mixers (e.g. 48 channels). The meter can read the signal from the named pipe. The 'file' ALSA plugin can send PCM to the named pipe. But it looks like it lags when signal has 32 channels. So, I thought that maybe it's possible to split channels into separate "streams" and forward them to separate 'file' plugin so that it would send one channel to the separate pipe.
 
Thank you for the example. I'm just not sure what the following code in the example means?

Code:
inp.setrate(44100)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
inp.setchannels(1)
Does it mean that the code expects the signal in this format? If so, it cannot handle formats automatically and needs code change for any new format. It should work if the format is always the same and it's known in advance.
 
Thank you for the example. I'm just not sure what the following code in the example means?

Code:
inp.setrate(44100)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
inp.setchannels(1)
Does it mean that the code expects the signal in this format? If so, it cannot handle formats automatically and needs code change for any new format. It should work if the format is always the same and it's known in advance.
Yes that is the disadvantage of capturing like this. But that can be worked around. You need to put in a loopback device in order to have a device to capture from. You can put in a "plug" device to convert to the format and sample rate you want before passing it on to the input of the loopback.
There might be other ways to do it, but this is the first one that comes to mind.
 
Yes that is the disadvantage of capturing like this. But that can be worked around. You need to put in a loopback device in order to have a device to capture from. You can put in a "plug" device to convert to the format and sample rate you want before passing it on to the input of the loopback.
There might be other ways to do it, but this is the first one that comes to mind.

Do you have any example of this kind of configuration?
 
I don't have any such config ready unfortunately, but a large part of it is here: Asoundrc - AlsaProject

Specifically this part:
Code:
pcm_slave.sl3 {
        pcm "hw:1,0"
        format S16_LE
        channels 1
        rate 16000
}

pcm.complex_convert {
        type plug
        slave sl3
}

You need to load the snd-aloop kernel module (sudo modorobe snd-aloop), set the sl3 pcm to point to the loopback, and set the plug up as a slave the same way as the file device you already have. Then it should work to record from the other end of the loopback.
 
Originally I faced the issue with the named pipe which is a memory buffer as far as I understand.

Yes, but by writing to file you stated that the python is not the culprit. It can be that the delay in the file plugin output was caused by something else - the slow storage. I still think the problem is somewhere in timing the communication between your script, the fifo, and the file plugin. Troubleshooting step by step (while not introducing new obstacles) will find the problem, for sure.
 
I have played a bit with your setup on x86.

I think the problem is related to the python script + alsa period size

Your script uses non-blocking read, but fixed wait in the loop. FIFO has a limited size (64k). When it gets full, the file plugin will block. If the block takes too long, buffers in the downstream pcm slave device will underflow - xruns.

FIFO receives data in batches every period time. If the FIFO is empty when os.read tries to read from it, due to its nonblocking setup it will return with err 11 -EAGAIN python - What conditions result in an opened, nonblocking named pipe (fifo) being "unavailable" for reads? - Stack Overflow . However, instead of trying again soon it will wait in time.sleep()

I do not know exactly why on RPi the process takes so much longer, but trying with different aplay period time (-F) will yield different delays.

IMO the easiest solution would be reading the data in smaller chunks with regular blocking behaviour + no sleep in a separate python thread, feeding the data to python queue/dequeue and having your executive thread read the data from the queue. That will "rectify" the period-time cycles of the alsa writes to the pipe. Non-blocking read + sleep is unreliable, IMO.