![ALSA를 사용하여 시작 시 systemd 서비스 실행](https://linux55.com/image/224349/ALSA%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC%20%EC%8B%9C%EC%9E%91%20%EC%8B%9C%20systemd%20%EC%84%9C%EB%B9%84%EC%8A%A4%20%EC%8B%A4%ED%96%89.png)
Raspberry Pi 시작 시 ALSA 사운드를 사용하는 Python systemd 서비스를 시작하려고 합니다.
이것은 /etc/systemd/system에 있는 Talkie.service 파일입니다:
Description=bondz-client
# Requires=sys-devices-platform-soc-soc:sound-sound-card1-controlC1.device
[Service]
User=user
Group=user
Type=simple
ExecStart=/usr/bin/python3 /home/romaing/Documents/Talkie_basic_sound_test.py
WorkingDirectory=/home/romaing/Documents/
StandardOutput=append:/var/log/bondz.log
StandardError=append:/var/log/bondz.log
[Install]
WantedBy=sound.target
서비스를 수동으로 시작하면 systemctl
제대로 작동하지만 시작할 때 로그에 이 오류가 표시되고 systemd가 중지됩니다. 로그 파일 내용은 다음과 같습니다.
2024-01-14 16:30:23,363 |Player| INFO:Device count = 0...
2024-01-14 16:30:23,364 |Player| INFO:Playing file tests/myrecording.wav...
2024-01-14 16:30:23,403 |Player| ERROR:Error playing tests/myrecording.wav
Traceback (most recent call last):
File "/home/romaing/Documents/Talkie_basic_sound_test.py", line 71, in playWaveFile
playStream = audio.open(
File "/usr/local/lib/python3.9/dist-packages/pyaudio/__init__.py", line 639, in open
stream = PyAudio.Stream(self, *args, **kwargs)
File "/usr/local/lib/python3.9/dist-packages/pyaudio/__init__.py", line 441, in __init__
self._stream = pa.open(**arguments)
OSError: [Errno -9996] Invalid output device (no default output device)
다음은 소리를 반복하는 간단한 Python 코드입니다.
from Player import Player
import io
import wave
import logging
import traceback
import pyaudio
player = Player()
logger = logging.getLogger("Player")
audio = pyaudio.PyAudio()
logging.basicConfig(
format="%(asctime)s |%(name)s| %(levelname)s:%(message)s", level=logging.INFO
)
def play():
playWaveFile("tests/myrecording.wav", play)
def playWaveFile(filepath, callback):
# info = audio.get_default_output_device_info()
logger.info(f"Device count = {audio.get_device_count()}... ")
logger.info(f"Playing file {filepath}... ")
try:
wave_file = wave.open(filepath, "rb")
playStream = audio.open(
format=audio.get_format_from_width(wave_file.getsampwidth()),
channels=wave_file.getnchannels(),
rate=wave_file.getframerate(),
output=True,
)
data = wave_file.readframes(1024)
while data:
playStream.write(data)
data = wave_file.readframes(1024)
# Cleanup
playStream.stop_stream()
playStream.close()
logger.info(f"Playing {filepath} done.")
callback()
except Exception as e:
logger.error(f"Error playing {filepath} ")
traceback.print_exc()
# self._statusManager.set_app_status(Status.ERROR)
play()
서비스가 시작될 때 사운드 카드가 로드되지 않는 것 같은데 sound.target이 문제를 해결할 것이라고 생각했지만 그렇지 않습니다... 이 주제에 대한 초보자로서 도움을 주시면 대단히 감사하겠습니다.
저는 Raspbian 11(Bulls Eye)을 사용하고 있습니다.
출력은 다음과 같습니다 aplay -l
.
**** List of PLAYBACK Hardware Devices ****
card 0: vc4hdmi [vc4-hdmi], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: seeed2micvoicec [seeed-2mic-voicecard], device 0: bcm2835-i2s-wm8960-hifi wm8960-hifi-0 [bcm2835-i2s-wm8960-hifi wm8960-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
1번 카드는 제가 사용해야 할 카드입니다.
답변1
첫째, 서비스가 "시작 단위 트리"에 존재하지 않기 After=sound.target
때문에 서비스 시작을 "보존"/"지연"할 수 없을 것으로 예상됩니다 .sound.target
systemd(수)는 트리에 있는 경우에만 한 유닛을 다른 유닛과 정렬할 수 있습니다.결정론적으로시작 중 특정 시점에 끌어들이도록 정의됩니다(또한 이와 같이 정의된 다른 장치에 의해).
sound.target
그러나 실제로는 그렇지 않습니다. 언제, 완전히 출시될지 여부사운드 카드가 나타나는 시기와 여부에 따라 다릅니다.(즉, 감지/열거). 분명한 것은 systemd가 부팅 프로세스를 시작할 때 사운드 카드 장치 장치가 있는지 여부를 (적어도 반드시 그런 것은 아님) 알지 못한다는 것입니다.
그러나 "잘못된" 주문은 장치가 (이 경우와 같이 다른 장치에 의해 multi-user.target
) 당겨지는 것을 막지는 못합니다.
아마도 사운드 카드가 나타난 후에 장치/서비스가 시작되도록 하는 유일한 올바른 방법은 사운드 카드를 만드는 것뿐입니다 WantedBy=sound.target
. 파일 섹션에서 변경을 수행한 후 비활성화-활성화 루프를 수행 해야 합니다 [Install]
. 그렇지 않으면 적용되지 않습니다.
systemd가 대상을 "시작"할 때 사운드 카드를 이미 사용할 수 있으므로 After=sound.target
전혀 필요하지 않습니다. (희망정상적인 상황에서는 가지고 있어도 무해합니다. 그러나 IMHO 정렬 메커니즘은 다소 취약합니다. 꼭 필요한지 확인하지 않는 한 주문하지 않는 것이 좋습니다. )
sound.target
한 번만 가져오므 로 참고하세요.ㅏ(즉, 모든) 사운드 카드가 나타납니다. 따라서 자신의 장치/서비스가 시작될 때 특정 사운드 카드를 사용할 수 있는지 확인해야 하는 경우 sound.target
원하는 사운드 카드가 실제로 항상 먼저 표시된다는 점을 모르는 한 이는 적합하지 않습니다.
sound.target
이 경우 일치할 수 있는 곳에서 pull과 유사한 udev 규칙을 사용해야 할 수도 있습니다 ENV{ID_PATH}
.
많은 배포판에서 audio
이 그룹에 속하지 않은 루트가 아닌 사용자는 사운드 장치에 액세스할 수 없습니다. 를 사용하여 실제 소유권/권한을 확인할 수 있습니다 ls -l /dev/snd/
.
그러나 systemd에는 다음과 같은 일종의 "사양/사용자 친화성 트릭"이 있습니다 uaccess
.ㅏ,두번째), 이는 로그인한 사용자를 위해 사운드 장치의 개발 노드에 ACL을 동적으로 추가합니다. (아마 그보다 더 복잡하겠지만, 모든 세션과 좌석이 무엇인지는 잘 모르겠고, 자세한 내용은 OP에 나와 있습니다.)
다음과 같은 것이 있기 때문입니다.
User=my-username
Group=
my-username
서비스 파일에서 그룹에 속하지 않은 경우 audio
해당 사용자로 로그인하지 않으면 프로세스가 사운드 장치에 액세스할 수 없습니다. (또는 그것조차도; 실제로 Group=(empty)
그것이 무엇인지 확인하지 않았습니다 .)
그러니까 꼭 my-username
허락( rw
?) 을 받으세요 /dev/snd/*
. audio
그룹 에 추가 하거나 Group=
또는 SupplementaryGroups=
에 설정하세요 audio
. 어떤 방법이든지 당신에게 효과가 있고 효과가 있습니다.
audio
또한 그룹이 시스템/배포판에 있다고 가정하지 마십시오 . 먼저 확인해 보세요.
답변2
나는 당신이 종속성에 대해 sound.target
옳다고 생각합니다 ( Requires=sound.target
사운드 지원이 필요하기 때문에 추가하고 싶지만). 문제가 무엇인지 완전히 확신할 수 없습니다. 일부 ALSA 구성 요소는 해결하는 데 시간이 걸릴 수 있지만 문제를 해결할 수 있는 몇 가지 간단한 방법이 있습니다.
서비스가 자동으로 다시 시작되도록 허용
서비스를 수동으로 다시 시작하는 대신 오류 발생 시 자동으로 다시 시작하도록 구성하세요.
Description=My Talkie App
After=network.target sound.target
[Service]
User=my-username
Group=
Type=simple
ExecStart=/usr/bin/python3 /home/romaing/Documents/Talkie.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
실패 시 5초마다 다시 시작을 시도합니다.
ExecStartPre 작업을 사용하여 사운드 장치를 기다립니다.
또는 필요한 오디오 장치를 사용할 수 있을 때까지 서비스를 차단할 수 있습니다. 그것은 다음과 같습니다:
Description=My Talkie App
After=network.target sound.target
[Service]
User=my-username
Group=
Type=simple
ExecStartPre=/usr/bin/timeout 300 /bin/sh -c 'while ! amixer -D sysdefault:CARD=USB > /dev/null 2>&1; do sleep 1; done'
ExecStart=/usr/bin/python3 /home/romaing/Documents/Talkie.py
[Install]
WantedBy=multi-user.target
식별된 카드를 사용할 수 있을 때까지 최대 5분 정도 기다립니다 sysdefault:CARD=USB
.