Hallo,
nach langem Profitieren in diesem Forum möchte ich auch mal etwas zurückgeben und meine Skripte vorstellen. Ich benutze Mpd auf dem Computer, auf dem ein archlinux installiert ist. Als Frontend für mpd benutze ich Mpdroid auf meinem Tablet. Da paßt es ganz hervorraged in diesen Thread.
Mein Ansatz ist der:
Mpd ruft die Tools von Frank (VolRace und PlayHRT) nicht direkt über eine pipe auf. Stattdessen stellt Mpd die Daten in eine Fifo, über einen MPD Client (kurz: mpc) wird ein anderes Skripts aufgerufen, das wiederum den fifo ausliest. Der Vorteil ist der, daß das mpc-Skript Infos zu Lautstärke und Sampling Rate von mpd bekommt, die dann als Parameter weitergereicht werden können.
Zuerst die mpd-Konfiguration:
Im "/etc/mpd.conf" wird die Bitrate auf 32 float gesetzt, die SampleRate wird unverändert durchgelassen. Die Daten werden in die Fifo '/dev/shm/play.raw' geschrieben.
Code: Alles auswählen
pid_file "/run/mpd/mpd.pid"
db_file "/home/mpd/mpd.db"
log_file "/tmp/mpd.log"
state_file "/var/lib/mpd/mpdstate"
playlist_directory "/NAS/playlists"
music_directory "/NAS/music"
user "mpd"
group "mpd"
# Buffer defaults to 4096 kB. but smaller sounds better.
audio_buffer_size "262"
buffer_before_play "16%"
filesystem_charset "UTF-8"
id3v1_encoding "UTF-8"
resampler {
plugin "soxr"
name "unused, zero padding only"
quality "very high"
}
audio_output {
type "fifo"
name "shared memory"
path "/dev/shm/play.raw"
mixer_type "null"
# samplerate unchanged, bitrate float (32 bit), 2 channels
format "*:f:2"
always_on "true"
}
Das mpc-Skript "mpc.py":
Das Skript lauscht als MPD client auf Events von mpd. Es tut 4 Dinge:
- beim Event "play" liest es die SampleRate aus und startet das playhrt-Skript "playFromFifo.sh" mit den passenden Parametern
- bei Events "play" oder "pause" stoppt es das playhrt-Skript "playFromFifo.sh"
- beim Event "volume" liest es die Lautstärke aus und setzt den passenden Wert in eine Datei /tmp/volrace.
- Es ist noch ein Feature zum Spielen dabei. Wenn in Mpd UI der Parameter 'Verbrauchsmodus' gesetzt ist, wird der Lautstärke-Schieberegler zum Ändern des zweiten Volrace-parameters 'race coefficient' anstelle der Lautstärke benutzt.
Mein mpc-Skript "mpc.py" ist in Python geschrieben, es setzt auf die python-Library 'python-mpd2' auf.
Code: Alles auswählen
#!/usr/bin/python
import subprocess
import signal
import os
import mpd
import time
import math
# Needed: mpd server
MPD_SERVER=u"localhost"
# Needed: Set the music directory as configured in mpd.conf
MUSIC_DIRECTORY = u"/NAS/music/"
# if you want set volume in a VOLRACE file on a remote server
VOLRACE_SERVER=u"mpd@192.168.1.16"
# with 3 parameters "samplerate" (e.g. "44100"), "bitdepth" (e.g. "32"), and the full file name
PLAY_CMD = u"/opt/scripts/playFromFifo.sh"
INITIAL_VOLUME = "0.1"
INITIAL_RACE_COEFF = "0"
volume=INITIAL_VOLUME
race_coeff = INITIAL_RACE_COEFF
client = mpd.MPDClient(use_unicode=True)
client.connect(MPD_SERVER, 6600)
with open("/tmp/VOLRACE", "w") as volumeFile:
volumeFile.write("%s 53 %s" % (INITIAL_VOLUME, INITIAL_RACE_COEFF))
event = None
playProcess = None
while True:
event = client.idle()
print("Event is %s" % event)
if event == ["mixer"]:
value = float(client.status()["volume"])/100
value = math.expm1(value)/1.72
consume = client.status()["consume"]
# for playing around with volrace: Set the race cofficient via MPD GUI
# If comsumption mode is not checked, set volume between 0 and 1.0
if consume == "0":
volume = value
# if consumption mode is checked, set race coeeficient between 0 and 0.25
if consume == "1":
race_coeff = value * 0.25
print("Setting volume=%s and race coefficient=%s" % (volume, race_coeff))
# set a local VOLRACE file
with open("/tmp/VOLRACE", "w") as volumeFile:
volumeFile.write("%s 53 %s" % (volume, race_coeff))
# Optional
# try to set volume in a VOLRACE file on a remote server VOLRACE_SERVER
try:
if not VOLRACE_SERVER is None:
volraceCmd = ['ssh', VOLRACE_SERVER, 'echo "' + str(volume/4) + '" > /tmp/VOLRACE']
print(volraceCmd)
volraceProcess = subprocess.check_output(volraceCmd)
except:
print("Error connecting to %s" % VOLRACE_SERVER)
volraceProcess = None
if event == ["player"]:
status = client.status()
state = status["state"]
currentsong = client.currentsong()
try:
file = MUSIC_DIRECTORY + currentsong["file"]
except:
file = None
print("command: %s , currentsong: %s" % (state, file))
if state == u"play":
if not playProcess is None:
os.killpg(os.getpgid(playProcess.pid), signal.SIGTERM)
# audio should look like: "44100:24:2"
audio = status["audio"]
audioArray = audio.split(':')
print("Sample rate and bit depth are: %s and %s" % (audioArray[0], audioArray[1]))
if not file is None:
playCmdList = [PLAY_CMD, audioArray[0], audioArray[1], file]
playProcess = subprocess.Popen(playCmdList, shell=False, preexec_fn=os.setsid)
if state == u"pause" or state == u"stop":
if not playProcess is None:
os.killpg(os.getpgid(playProcess.pid), signal.SIGTERM)
playProcess = None
Das playhrt-Skript "playFromFifo.sh":
Dieses Skript ruft letzendlich playhrt auf. Die 2 Parameter Samplerate und Filename werden als Parameter übergeben. Mein Skript verzichtet auf Upsampling, stattdessen wird Brutefir parametrisiert abhängig von der SampleRate aufgerufen. Auch die Parameter für playhrt sind unterschiedlich je nach SampleRate.
Das Skript liest die Daten aus der Fifo, und schleust sie über verschiedene Zwischenstufen (Brutefir und volrace) weiter an playhrt, in meinem Fall läuft playhrt auf einem anderen Server, einem Beaglebone Green, auf dem ebenfalls archlinux läuft.
Code: Alles auswählen
#!/bin/bash
CARD="default:CARD=UAC2"
EXTRABYTESBUF=0
EXTRABYTESPLAY=0
VERBOSE="--verbose --verbose"
AUDIOCOMPUTER="mpd@192.168.1.50"
# Sample format of DAC
SAMPLEFORMAT=S32_LE
HOST="192.168.1.102"
PORT=5570
PLAYHRTLOGFILE=/tmp/playhrt.log
ORIG_RATE=$1
ORIG_BITLENGTH=$2
FILENAME=$3
echo "Original Bitdepth is ${ORIG_BITLENGTH}"
echo "Original Sample rate is ${ORIG_RATE}"
echo "File name is ${FILENAME}"
# Overwrite the parameter. mpd reports the original bitlength. But in mpd.conf we set it to float (32 bit)
ORIG_BITLENGTH=32
# no oversampling
SAMPLERATE=${ORIG_RATE}
# For parameter HW_BUFFER, set playhrt parameter "--verbose --verbose"
# and see content of file PLAYHRTLOGFILE. Choose smallest value.
if [ "${ORIG_RATE}" = "96000" ]; then
# 96000 * 2 * 4
BYTESPERSECOND=768000
LOOPSPERSECOND=1000
BLOCKSIZE=768
HW_BUFFER=4096
BRUTEFIRCONFIG=/opt/filters/96000.txt
elif [ "${ORIG_RATE}" = "48000" ]; then
# 48000 * 2 * 4
BYTESPERSECOND=384000
LOOPSPERSECOND=1000
BLOCKSIZE=384
HW_BUFFER=2048
BRUTEFIRCONFIG=/opt/filters/48000.txt
elif [ "${ORIG_RATE}" = "88200" ]; then
# 88200 * 2 * 4
BYTESPERSECOND=705600
LOOPSPERSECOND=1600
BLOCKSIZE=441
HW_BUFFER=7526
BRUTEFIRCONFIG=/opt/filters/88200.txt
elif [ "${ORIG_RATE}" = "44100" ]; then
# 44100 * 2 *4
BYTESPERSECOND=352800
LOOPSPERSECOND=800
BLOCKSIZE=441
HW_BUFFER=3763
BRUTEFIRCONFIG=/opt/filters/44100.txt
elif [ "${ORIG_RATE}" = "192000" ]; then
# 192000 * 2 * 4
BYTESPERSECOND=1536000
LOOPSPERSECOND=1000
BLOCKSIZE=1536
HW_BUFFER=8192
BRUTEFIRCONFIG=/opt/filters/192000.txt
fi
echo "bytespersecond is ${BYTESPERSECOND}"
rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}*
rm -f /dev/shm/bl{A,B,C} /dev/shm/sem.bl{A,B,C}*
trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT
# mpd fills the fifo with format FLOAT_LE (32 bit)
cat /dev/shm/play.raw | \
chrt -f 97 writeloop --block-size=${BLOCKSIZE} --file-size=36864 --shared /blA /blB /blC &
sleep 0.1
# volrace reads format FLOAT_LE (32 bit), outputs FLOAT64_LE.
# brutefir reads format FLOAT64_LE, and outputs S32_LE as needed by DAC
chrt -f 97 catloop --block-size=${BLOCKSIZE} --shared /blA /blB /blC | \
chrt -f 98 volrace --float-input --param-file=/tmp/VOLRACE | \
chrt -f 98 brutefir -quiet ${BRUTEFIRCONFIG} | \
chrt -f 99 writeloop --block-size=${BLOCKSIZE} --file-size=9216 --shared /bl1 /bl2 /bl3 &
sleep 0.3
REMOTE="nohup sleep 0.2 >/dev/null 2>/dev/null </dev/null ;\
nohup chrt -f 99 playhrt ${VERBOSE} \
--host=${HOST} --port=${PORT} \
--sample-rate=${SAMPLERATE} \
--sample-format=${SAMPLEFORMAT} \
--device=${CARD} \
--extra-bytes-per-second=${EXTRABYTESPLAY} \
--loops-per-second=${LOOPSPERSECOND} \
--hw-buffer=${HW_BUFFER} \
>/dev/null 2>${PLAYHRTLOGFILE} </dev/null "
REMOTE='sh -c "( ( '${REMOTE}' ) & )"'
ssh -f ${AUDIOCOMPUTER} "${REMOTE}"
chrt -f 99 bufhrt \
--bytes-per-second=${BYTESPERSECOND} --loops-per-second=${LOOPSPERSECOND} \
--input-size=${BLOCKSIZE} --port-to-write=${PORT} \
--extra-bytes-per-second=${EXTRABYTESBUF} \
--shared /bl1 /bl2 /bl3 \
${VERBOSE}
Zur Verbindung zwischen Computer und Beaglebone benutze ich ssh. Das ist am einfachsten, wenn der User 'mpd' auf beiden Rechnern existiert und das mpc-Skript als User 'mpd' gestartet wird, dann wird auch das playhrt-Skript als User 'mpd' aufgerufen.
Bei mir wird mpd automatisch per Start-service gestartet. Das mpc-Skript starte ich zur Zeit noch manuell, um alle Konsolenausgaben überprüfen zu können. Wenn es völlig stabil läuft, wird es umgestellt und über einen Start-service gestartet werden.
Möge dieser Beitrag als Inspiration für andere Versuche nützlich sein.
Cheers, Volker.