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この場合、一致する場所でプルに似た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また、グループがシステム/ディストリビューションにあると仮定しないでください。まず確認してみてください。

おすすめ記事