Kiindulásként adott már egy Raspberry 3B+, ami számos funkciót lát el a nappalinkban fizikailag rejtve.
Ehhez legutóbb szerkintettem egy relémodult, amiből eddig még csak az egyik relét használtam a Settop box lekapcsolására.
https://ubuntu.hu/blog/46078-barkacs-25kwhev/29
Az egyik funkciója ennek a Raspberrynek, hogy a hálózaton kihelyezett hangkártyaként is viselkedik (Pulseadudio network sink, de emellett A2DP Bluetooth vevő is egyben), ami végül is most egy HiFiBerry-n keresztül szól.
Sajnos, az erősítőmnek a távirányítóján kipurcant a ki-/bekapcsológomb. Néha még sikerül úgy megnyomni, hogy működjön, de többnyire már nem. (Nem, nem az elem merült ki benne 🙂 )
Az utóbbi nagyjából 2 évben már kizárólag Spotify-t hallgatok rajta, szóval felmerült bennem, hogy milyen klassz is lenne, ha nem nekem kéne bekapcsolnom az erősítőt, hanem magától indulna, és kapcsolódna ki, amikor már nem kell.
Feltúrtam hát a virtuális LEGO-s dobozt, hogy milyen építőelemeket találok, amivel valahogy elérhetem, amit elképzeltem, vagy legalább valami hasonlót.
Amit találtam:
pactl subscribe
peak-detect.py
https://gitlab.com/menn0/peak-detect
aminek függősége:
https://github.com/Valodim/python-pulseaudio
(Ezt előbb telepíteni kell, hogy a peakdetect működjön: letöltés, majd setup.py install
)
Ez, amit most összeraktam, úgy működik, hogyha hang érkezik a HiFiBerry-re, az erősítőt azonnal bekapcsolja, ha a hang eltűnik, nagyjából 5 perc múlva kikapcsolja (direkt nem azonnal). Amúgy pontosan ezt álmodtam meg eredetileg 😁
És ahogy összeállt:
Kicsit beleberheltem a peakdetect-be, lényegében alaposan lebutítottam, mert nem kell a „bar”, meg a sorkitöltő space, elég a minta értéke, és nem kell túl sűrűn sem másodpercenként 2-3 untig sok, viszont a 8 bites minta kevés volt, mert extrém halk részeknél bár a zene szól, csupa 0-t hoz (például amit M. R. követett el, a Boléro eleje pont ilyen), kikommenteltem az induláskori tájékoztató üzeneteit, mert csak útban voltak nekem, és csak a hibaüzeneteit hagytam meg, ha esetleg..... ; most ilyen:
/opt/peakdetect.py
#!/usr/bin/python
import sys
from Queue import Queue
from ctypes import POINTER, c_ubyte, c_void_p, c_ulong, cast
# From https://github.com/Valodim/python-pulseaudio
from pulseaudio.lib_pulseaudio import *
SINK_NAME = 'alsa_output.platform-soc_sound.stereo-fallback' # edit to match your sink
METER_RATE = 2
#MAX_SAMPLE_VALUE = 127
#DISPLAY_SCALE = 1
#MAX_SPACES = MAX_SAMPLE_VALUE >> DISPLAY_SCALE
class PeakMonitor(object):
def __init__(self, sink_name, rate):
self.sink_name = sink_name
self.rate = rate
# Wrap callback methods in appropriate ctypefunc instances so
# that the Pulseaudio C API can call them
self._context_notify_cb = pa_context_notify_cb_t(self.context_notify_cb)
self._sink_info_cb = pa_sink_info_cb_t(self.sink_info_cb)
self._stream_read_cb = pa_stream_request_cb_t(self.stream_read_cb)
# stream_read_cb() puts peak samples into this Queue instance
self._samples = Queue()
# Create the mainloop thread and set our context_notify_cb
# method to be called when there's updates relating to the
# connection to Pulseaudio
_mainloop = pa_threaded_mainloop_new()
_mainloop_api = pa_threaded_mainloop_get_api(_mainloop)
context = pa_context_new(_mainloop_api, 'peak_demo')
pa_context_set_state_callback(context, self._context_notify_cb, None)
pa_context_connect(context, None, 0, None)
pa_threaded_mainloop_start(_mainloop)
def __iter__(self):
while True:
yield self._samples.get()
def context_notify_cb(self, context, _):
state = pa_context_get_state(context)
if state == PA_CONTEXT_READY:
# print "Pulseaudio connection ready..."
# Connected to Pulseaudio. Now request that sink_info_cb
# be called with information about the available sinks.
o = pa_context_get_sink_info_list(context, self._sink_info_cb, None)
pa_operation_unref(o)
elif state == PA_CONTEXT_FAILED :
print "Connection failed"
elif state == PA_CONTEXT_TERMINATED:
print "Connection terminated"
def sink_info_cb(self, context, sink_info_p, _, __):
if not sink_info_p:
return
sink_info = sink_info_p.contents
# print '-'* 60
# print 'index:', sink_info.index
# print 'name:', sink_info.name
# print 'description:', sink_info.description
if sink_info.name == self.sink_name:
# Found the sink we want to monitor for peak levels.
# Tell PA to call stream_read_cb with peak samples.
# print
# print 'setting up peak recording using', sink_info.monitor_source_name
# print
samplespec = pa_sample_spec()
samplespec.channels = 1
samplespec.format = PA_SAMPLE_S16LE
samplespec.rate = self.rate
pa_stream = pa_stream_new(context, "peak detect demo", samplespec, None)
pa_stream_set_read_callback(pa_stream,
self._stream_read_cb,
sink_info.index)
pa_stream_connect_record(pa_stream,
sink_info.monitor_source_name,
None,
PA_STREAM_PEAK_DETECT)
def stream_read_cb(self, stream, length, index_incr):
data = c_void_p()
pa_stream_peek(stream, data, c_ulong(length))
data = cast(data, POINTER(c_ulong))
for i in xrange(length):
# When PA_SAMPLE_U8 is used, samples values range from 128
# to 255 because the underlying audio data is signed but
# it doesn't make sense to return signed peaks.
self._samples.put(data[i])
pa_stream_drop(stream)
def main():
monitor = PeakMonitor(SINK_NAME, METER_RATE)
for sample in monitor:
# sample = sample >> DISPLAY_SCALE
#bar = '>' * sample
#spaces = ' ' * (MAX_SPACES - sample)
print '%s\n' % (sample),
sys.stdout.flush()
if __name__ == '__main__':
main()
Ez így most csak számokat irkál ki folyamatosan, ha nem szól semmi, akkor csupa 0-t.
Összekalapáltam egy scriptet, ami feliratkozik a Pulseaudio eseményeire, és kifejezetten a 2. nyelőre figyel, mert az a HiFiBerry.
Ha az állapota „RUNNING”-ra vált, akkor elindul egy ciklusban a peakdetect, hogy figyelje, van-e hang.
Erre azért van szükség, mert amikor a hálózatomon a gépek elindulnak, amelyiken be van állítva, hogy küldhet hangot a Raspberry-re, a Pulseaudio egy rövid időre futó állapotba rakja a nyelőt. Emiatt nem akartam azonnal bekapcsolni az erősítőt, a „RUNNING” alapján, mert akkor fölöslegesen sokszor lenne bekapcsolva nagyon rövid időre. Amikor elindul a monitorpeak függvény, az először csak számolja a 0-kat, amiket a peakdetec.py irkál ki. Ha valami megszólal, a peakdetect azonnal 0-tól eltérő számokat kezd irkálni, erre az erősítő be lesz kapcsolva.
Ha a zene elhallgat, újból csak a 0-kat számolja. Ha több, mint 1100 összejött (ez nagyjából 5 perc), kilép a ciklusból, kinyírja a peakdetect.py-t és kikacsolja az erősítőt.
Ez az elmélet, tegnap óta működik így a gyakorlatban, és nagyon tetszik 🙂
De még figyelgetem, előjön-e valami bug?
És a script, ami /opt/amplifierswitch.sh néven fut:
#! /bin/sh
eroltetobe ()
{
echo "0" > /sys/class/gpio/gpio22/value
touch /tmp/ero-be
}
eroltetoki ()
{
rm /tmp/ero-be
echo "1" > /sys/class/gpio/gpio22/value
}
monitorpeak ()
{
zeros=0
/opt/peakdetect.py | while IFS= read -r peakevent; do
case $peakevent in
0)
zeros=$((zeros+1))
;;
"Connection failed")
pkill peakdetect.py
break
;;
"Connection terminated")
pkill peakdetect.py
break
;;
*)
zeros=0
if [ ! -f /tmp/ero-be ]; then
echo "debug: erolteto be"
eroltetobe
fi
;;
esac
if [ $zeros -gt 1100 ]; then
pkill peakdetect.py
break
fi
done;
echo "debug: erolteto ki"
eroltetoki
}
sinkstate=""
pactl subscribe | while IFS= read -r pactlevent; do
echo "debug: $pactlevent"
if [ "$pactlevent" = "„módosítás” esemény ezen: 2. nyelő" ];
then
prevstate=$sinkstate
sinkstate=$(pactl list sinks short | grep platform-soc | awk '{print $7}')
#sinkstate: RUNNING IDLE SUSPENDED
echo "debug: $sinkstate"
case $sinkstate in
"RUNNING")
if [ "$prevstate" = "RUNNING" ]; then
#nem valtozott, még jöhet ide valami
echo "debug: Running volt elobb is"
else
#most indult el a sink
echo " debug: monitorpeak indul"
monitorpeak&
fi
;;
"IDLE")
echo "debug: sink idle"
;;
"SUSPENDED")
echo "debug: sink suspended"
;;
esac
fi
done
Ahhoz, hogy ez szépen a háttérben működjön, egy külön service-t csináltam belőle, itt kiemelném, hogy ugyanazon felhasználó nevében fut, mint a Pulseaudio démon:
/etc/systemd/system/amplifierswitcher.service tartalma:
[Unit]
Description=Amplifier Switcher
Requires=pulseaudio.service
After=pulseaudio
[Service]
Type=simple
User=pulse
ExecStart=/opt/amplifierswitch.sh
[Install]
WantedBy=multi-user.target
Szokásos: systemctl daemon-reload; systemctl enable amplifierswitcher
Annyi még, hogy ahhoz, hogy a kapcsolgatást tényleg meg tudja csinálni, a „pulse” felhasználót hozzá kell adni a „gpio” csoporthoz:
usermod -a -G gpio pulse
Most még tervezem, hogy felteszek egy lighttpd -t, és rityyentek rá valami egyszerű webes felületet, amin pulseaudio-tól függetlenül is be tudom kapcsolni, ha esetleg valami más műsorforrás miatt kéne....
Ahhoz majd shell scripteket fogok használni cgi-ként.