bastibe / SoundCard

A Pure-Python Real-Time Audio Library
https://soundcard.readthedocs.io
BSD 3-Clause "New" or "Revised" License
689 stars 70 forks source link

'import soundcard' throws an error when run through a systemd service #133

Open Teufeuleu opened 3 years ago

Teufeuleu commented 3 years ago

Hey, I have the following code:

#test.py
import soundcard
while True:
    pass

It runs fine with python test.py

But it does not when I want to run it using a systemd service.

Here's the service definition:

[Unit]
Description=Soundcard test service
Requires=sound.target
After=sound.target

[Service]
ExecStart=python test.py
User=pi

[Install]
WantedBy=default.target

And the error it throws:

pi@raspberrypi:sudo systemctl start test.service
pi@raspberrypi:~ $ sudo systemctl status test.service
● caravane.service - Soundcard test service
   Loaded: loaded (/lib/systemd/system/test.service; disabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Mon 2021-07-26 20:58:23 CEST; 3s ago
  Process: 2649 ExecStart=/usr/bin/python /home/pi/test.py (code=exited, status=1/FAILURE)
 Main PID: 2649 (code=exited, status=1/FAILURE)

juil. 26 20:58:23 raspberrypi python[2649]:     import soundcard
juil. 26 20:58:23 raspberrypi python[2649]:   File "/home/pi/.local/lib/python3.7/site-packages/soundcard/__init__.py", line 4, in <module>
juil. 26 20:58:23 raspberrypi python[2649]:     from soundcard.pulseaudio import *
juil. 26 20:58:23 raspberrypi python[2649]:   File "/home/pi/.local/lib/python3.7/site-packages/soundcard/pulseaudio.py", line 261, in <module>
juil. 26 20:58:23 raspberrypi python[2649]:     _pulse = _PulseAudio()
juil. 26 20:58:23 raspberrypi python[2649]:   File "/home/pi/.local/lib/python3.7/site-packages/soundcard/pulseaudio.py", line 72, in __init__
juil. 26 20:58:23 raspberrypi python[2649]:     assert self._pa_context_get_state(self.context)==_pa.PA_CONTEXT_READY
juil. 26 20:58:23 raspberrypi python[2649]: AssertionError
juil. 26 20:58:23 raspberrypi systemd[1]: test.service: Main process exited, code=exited, status=1/FAILURE
juil. 26 20:58:23 raspberrypi systemd[1]: test.service: Failed with result 'exit-code'.

I tried with the sounddevice module instead of soundcard and I dont get this error so I guess the problem comes from soundcard and not PulseAudio?

Why all this: I'm making a sound installation, the main python program runs fine on the Raspberry (using multiple sound cards handled by the soundcard module, threading, ...), but I struggle to make it start at boot. It seems to run too early with rc.local or a crontab (because sound cards and/or pulseaudio itself are not initialized yet, I guess), so I tried with systemd but I faced the problem described above.

I'm still learning programming and how linux work so sorry if I missed something obvious and if the problem is actually unrelated to soundcard itself.

Chum4k3r commented 3 years ago

SoundDevice is a python wrapper for PortAudio, which does not use PulseAudio. Your problem seems to be on these two lines

juil. 26 20:58:23 raspberrypi python[2649]:     assert self._pa_context_get_state(self.context)==_pa.PA_CONTEXT_READY
juil. 26 20:58:23 raspberrypi python[2649]: AssertionError

And seems that it is what you think is the problem is, really is the problem. You are running it too early.

Teufeuleu commented 3 years ago

SoundDevice is a python wrapper for PortAudio, which does not use PulseAudio

My bad, I still get those mixed up.

You are running it too early.

I think I don't I get what "too early" refers to, because the copy-pasted errors are from me trying to manually start the service (sudo systemctl start test.service), after that the system has fully booted, and after that python test.py ran successfully.

bastibe commented 3 years ago

I would think that the service somehow does not have access to pulseaudio.

Can you check the value of self._pa_context_get_state(self.context) in this case? Perhaps that will tell you more.

Teufeuleu commented 3 years ago

Can you check the value of self._pa_context_get_state(self.context) in this case?

Well I learn by trying! I spent maybe 10h trying to get that damn value but finally I got it. After countless tries, I "simply" had to install the soundcard module globally instead of locally, then modify soundcard/pulseaudio.py to get it logging the said value, and make the service run as root instead of pi (because logging would not work otherwise for some reason).

Now the result: self._pa_context_get_state(self.context) = 5 when the error happens (test.py ran as a systemd service). Its value is 4 when everything runs fine (manually run python test.py).

So if I get it correctly from the pulseaudio documentation, self._pa_context_get_state(self.context) = 5 means that the pulseaudio context connection failed or was disconnected, but I can't go any further, and I can't tell if the issue comes from soundcard or how python/pulseaudio/systemd interact with each other.

bastibe commented 3 years ago

Thank you very much for going to such lengths in debugging this issue.

This does indeed point to some configuration issue in pulseaudio that prevents the systemd unit from accessing pulseaudio for some reason. I believe pulse communicates over a socket, which is (or can be) exposed through an environment variable. Perhaps checking that variable and/or socket might be a next step in debugging this issue.

Teufeuleu commented 3 years ago

Could you be a bit more specific? Apparently pulseaudio does uses some environment variables, but I don't understand how to get or set any of those. None of those appear in the output of the env command. It seems that the pulseaudio server socket is something like /run/user/1000/pulse but I'm not sure what to do with that. Is there a way I could "listen to" whatever goes through this socket?

I also ran pulseaudio -vvvvv which outputs a lot of info when I run python test.py or my other project files (everything goes fine), but absolutely nothing when starting the test.service.

bastibe commented 3 years ago

I'm not very well-versed in pulse, either, sorry. At least you could check whether your systemd unit can read that server socket. Beyond that, I'm not sure either.

ronny-rentner commented 2 years ago

@Teufeuleu: You could try to use pulseaudio's paplay from your service to see if it actually has access to pulseaudio and can play something. Then you know if the issue is with your pulseaudio setup or with SoundCard.

arvind-cp commented 2 years ago

@ronny-rentner @Teufeuleu : I have the same issue. It works fine on systemd service in ubuntu 18.04 with pulseaudio version 11.1 It fails on systemd service in ubuntu 20.04 pulseaudio version 13.9. soundcard library version is 0.4.1 on both machines. paplay seems to be fine though - both via systemd service as well as command line execution.

Update: After adding 'sound.target' as a dependency and sourcing these 2 env variables, it started working in systemd.

Environment="XDG_RUNTIME_DIR=/run/user/1000"
Environment="PULSE_RUNTIME_PATH=/run/user/1000/pulse/"
Tremeschin commented 5 months ago

I was also having this issue inside a Docker container, and managed to solve it by adding:

base.dockerfile:

FROM ubuntu:22.04
WORKDIR /app
# (...) Python deps, ffmpeg, etc
RUN apt install -y pulseaudio dbus
RUN adduser root pulse-access
COPY . /app
CMD /bin/bash entry.sh

entry.sh:

#!/bin/bash
pulseaudio --verbose --exit-idle-time=-1 --system --disallow-exit -D
python3 entry.py

entry.py:

import soundcard

print("Wahoo, no PA_CONTEXT_READY assertion error")

My directory of Dockerfiles and scripts for reference.

Related sources used [1], I didn't need most of that stuff and dummy sinks and sources 🧐