Hi,
I'm trying to play 32-channel WAV file on Raspberry Pi 3:
At the same time I'm piping the signal to the named pipe using ALSA 'file' plugin:
I'm reading from the pipe using the following Python script:
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'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
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
}
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)
Can you add a debug into the python loop to make sure the read is really non blocking and the fifo does not get filled up, blocking the writer - the aplay process?
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.
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:
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:
The script output in this case is like this:
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?
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)
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
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)
.....
Code:
.....
300000
105536
300000
300000
105536
300000
300000
64576
[Errno 11] Resource temporarily unavailable
300000
300000
300000
300000
252160
0
0
0
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).
* 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.
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:
Then till the end (another 45 sec) I saw this kind of messages:
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
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
}
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
...
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
...
Thanks
Is it possible to split the signal into separate channels and send each channel to the separate 'file' plugin?
I don't have any useful ideas to contribute with here, but I'm really curious.. What are you actually trying to do, and why do you have so many channels?
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.
Ok I see. Could you perhaps add an Alsa binding to PeppyMeter? That way it could capture directly from Alsa and you wouldn't need any pipes or files.
I found an example that looks pretty simple: pyalsaaudio/recordtest.py at master * larsimmisch/pyalsaaudio * GitHub
I found an example that looks pretty simple: pyalsaaudio/recordtest.py at master * larsimmisch/pyalsaaudio * GitHub
Thank you for the example. I'm just not sure what the following code in the example means?
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.
Code:
inp.setrate(44100)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
inp.setchannels(1)
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.Thank you for the example. I'm just not sure what the following code in the example means?
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.Code:inp.setrate(44100) inp.setformat(alsaaudio.PCM_FORMAT_S16_LE) inp.setchannels(1)
There might be other ways to do it, but this is the first one that comes to mind.
Continuous stream to SD card, 87MB in 30 seconds, maybe it's a performance limit of the storage. Can you try storing to a tmpfs instead to check?
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?
Continuous stream to SD card, 87MB in 30 seconds, maybe it's a performance limit of the storage. Can you try storing to a tmpfs instead to check?
Originally I faced the issue with the named pipe which is a memory buffer as far as I understand.
I don't have any such config ready unfortunately, but a large part of it is here: Asoundrc - AlsaProject
Specifically this part:
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.
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.
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.
- Home
- Source & Line
- PC Based
- Piping multi-channel signal