ALSA를 사용하여 시작 시 systemd 서비스 실행

ALSA를 사용하여 시작 시 systemd 서비스 실행

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.

관련 정보