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