[Overview] [Player] [RACE] [LoCo] [DRC]
audacious
and aplay
play_simple
--extra-bytes-per-second
parameter for playhrt
writeloop
and catloop
volrace
listen_loop
script)ssh
)--extra-bytes-per-second
parameter for bufhrt
bufhrt
improvefile
These utilities should help to achieve "ultimate" (ok, that's impossible, but we can try to get closer) sound quality when one or several computers running Linux are used for playback of music files. I mainly have in mind playback via a "high end" stereo system, but I have used some of these programs also for improving playback on a notebook.
The aim is not to provide yet another graphical user interface to your music collection. In fact, the programs I provide are meant to be used via the command line or within custom scripts. (Of course, they could be called by fancier user interfaces.)
While developing these programs I was surprised to learn how much influence playback software can have on sound quality. The software is just as important as the computer hardware and the other components of your stereo setup. If the manufacturer of your DAC or soundcard claims it were immune against "jitter", do not believe it!
The software here is free software, it comes with full source code, you can change and redistribute it, and it can be downloaded and used without costs. The precise conditions are described in the GNU GPL. Of course, in particular: using the software distributed here is completely on your own risk. As with any new component in your stereo system, keep the volume very low during first tests.
This package is developed on Linux systems and is mainly meant to be run on Linux. (But some code may compile and can be used on other systems as well.)
The software is available via this git
-repository.
For bug reports, problem reports and suggestions use its
issue
tracker.
Installation instructions are in the file INSTALL
inside
the code directory. This includes a description of software packages
needed for the installation.
When all needed packages are installed on your Linux system, the installation should just look like:
git clone https://github.com/frankl-audio/frankl_stereo.git cd frankl_stereo make REFRESH=X8664 # on x86 machines, or make REFRESH=AA64 # on arch64 machines (e.g. Raspberry Pi 4)The compiled programs are then in the subdirectory
./bin/
.
Copy or link them somewhere in your standard
PATH (e.g., /usr/local/bin/
).
Music stored on a computer is encoded in a sequence of bits, 0's and 1's. To play it these bits need to be sent to a DAC or soundcard which translates them to an analogue wave form which is then amplified and made audible through speakers or headphones. So, do we "just" need to get the correct 0's and 1's quickly enough to the DAC to get an optimal playback on a given system? Unfortunately, it is not that easy. The point is that in real hardware the 0's and 1's are also stored and transfered by analogue (e.g., electrical or magnetic) signals. (With "analogue" I mean here something that can have many states, e.g. a voltage, a charge, a magnetisation, which physically represent one of the two abstract states 0 or 1.) Let me illustrate my vague understanding of this with some (made up) pictures. Say, we want to transfer some bits by an electrical signal, each bit is encoded by a voltage which is for a certain amount of time above or below some threshold. The ideal signal I have in mind would look like this to encode bits 1101000101:
But a real electrical signal cannot have infinitely steep slopes, maybe it looks more like that:
Well, probably the power supply for this signal is far from perfect, maybe this is more realistic:
And for some components in a computer even the following heavily distorted looking version is still good enough to correctly(!) recover or interpret the bits from the signal:
There are also other signal depending distortions like reflections in a cable.
Similar remarks apply to the storage of bits on a hard disk by magnetisation of particles along a track on the disk. Or to bits stored on an SSD or USB-stick in charged cells. Or to bits stored in computer memory (RAM) whose cells we can imagine as small caps which are charged above or below some threshold to store a 1 or 0, and the charge is steadily leaking and needs to be refreshed regularly.
The strength of digital storage and processing is that distortions as imagined above do not matter; as long as all components can correctly recognize a 0 or 1, absolutely no information is lost.
A DAC chip will also work "correctly" within a certain specification if the bits in the incoming signal can be correctly recognized. But in practice the audible quality of the outgoing signal does depend on the wave form of the incoming signal, and not just on the bits it encodes! One aspect is the precise time when the DAC chip notices that the incoming signal is switching from above a threshold to below or vice versa. With the perfect square wave such switches occur only at precise equidistant points of time. With a wave form as in the second example above the switches are also recognized clearly, but there is a variation of the time differences between the switches. These timing errors are called jitter and higher jitter leads to worse sound quality of the analogue audio output signal. With the more distorted wave forms also the time when a switch is recognized is less clear. Another aspect is that distortions of the digital signal, say from a not so clean power supply, cannot be filtered out completely by the DAC chip, there will also be some corresponding distortion on the output wave form.
Summary: Bits in a computer and DAC are stored and transferred by various forms of analogue signals. The task of this project is to experiment if and how we can get a cleaner signal to the DAC by player software.
Starting from the vague and largely simplified model of data storage and transport in computer and DAC as explained above, I experimented with two main ideas for the programs distributed here.
The first idea is to process and transfer audio data in chunks of
constant size, each within an interval of time of constant length.
For this we use the high resolution timers on modern computer hardware which
are made available in Linux by
clock_nanosleep
and related functions.
For example, the program playhrt
which sends audio data to
a DAC or soundcard has a main loop where in each round a chunk of data is read,
then the program sleeps until a certain point of (wall clock) time and after
wakeup sends the data to the hardware driver. So, the hardware driver
receives (almost) constant sized chunks of input data in very regular
time intervals.
The second idea is to refresh the representation of audio data in RAM or on a hard disk by (logically unnecessary) reading and rewriting of data. In the model explained above the hope is to increase the amplitude of the signals (and so also the steepness of slopes and so to reduce the related jitter).
I'm aware that the model I have explained above is very much simplified. In reality, the processing of data is very complicated. The processed bits are transformed between various physical representations and encodings, data are buffered in various places on hard- and software level (e.g., several levels of caches on a harddisk, network card or in RAM, software buffers in application programs and in hardware drivers, and so on). Having this in mind it seems completely hopeless to produce audible differences between programs which all perform a "bit-perfect" music playback. Fortunately, I started my experiments despite such doubts. (Ok, and encouraged by knowing that others are trying similar things for the Windows or Mac platform, e.g. jplay, MQn, Amarra.)
I have evaluated and compared various versions of my (and other)
programs only by listening tests. The starting reference were the standard
Linux programs audacious
and
aplay
.
It would be nice to do some sort of technical measurements, which underpin and explain audible differences. But that is probably extremely challenging and certainly outside my technical possibilities.
I was repeatedly surprised how significant audible differences can be in this context. And this not only in my "high-end" stereo setup, but even on a notebook computer or monitor with its lower quality speakers behind the display.
Here is an overview of the programs and example scripts provided by this project.
After installation you can run each program with option --help
to read its full documentation
(or click on the function names in the overview below).
The first two, playhrt
and bufhrt
are the heart
of the project.
playhrt
bufhrt
cat64
libsndfile
library) and outputs its samples in 64-bit
floating point formatresample_soxr
sox
only
uses 32-bit integer encoding internally); this includes the functionality of
cat64
and volrace
writeloop
and catloop
volrace
cptoshm
and
shmcat
highrestest
playhrt
and
bufhrt
indicate that a high res timer is used)
play_XXXX
improvefile
bufhrt
can be used to "improve" music files stored on a hard disk
We assume that you are on a Linux system with the programs provided here
installed. Furthermore, you should be able to play sounds via a soundcard or
external sound device with some installed player program (e.g., audacious
).
You should also have a few other programs: sox
,
aplay
,
flac
,
and maybe
brutefir
(if you want to play with convolving), or some version of
nc
(if you
want a two-computer playback setup).
On Debian and related distributions (Debian, Ubuntu, Mint, Raspian, ...)
you may use (as root user, or with a sudo
in front):
apt-get install sox libsox-fmt-mp3 brutefir flac netcat-openbsd
On other systems there is certainly a similar command.
Furthermore, we assume that your Linux kernel supports the high resolution
timers of modern hardware. This is usually the case, you can test it by
calling the small utility progam highrestest
which is contained
in this package. It will print some information.
We provide example scripts which demonstrate the use of the programs in this package. Since a few parameters are system dependent it may be necessary to adjust them before a script works on your system. We comment on a few parameters which can be adjusted close to the top of each script.
hw:0.0
is often alright. If not, use
aplay -l
to find out the correct card and device number.
We suggest to use the hardware devices directly (and not some virtual or
plughw-devices). This is used for the --device
option of
playhrt
. --extra-bytes-per-second
option of playhrt
and
bufhrt
, respectively. This is to synchronize the speed of
different computers or of a computer and a DAC or soundcard.
It is explained below how to find good values.Introduce more such variables yourself if you want to play with other
parameters. For some sound cards the default value of --hw-buffer
may not be valid.
All scripts discussed here can also be found in the subdirectory
scripts
of the software distribution.
audacious
and aplay
Say, you want to play a music file mymusic.flac
which is in
44.1/16 format as music from CDs (other formats instead of flac should also
work, e.g., mymusic.wav
or even mymusic.mp3
).
First check if your sound device is working with (you may use any other
player you know instead of audacious
):
audacious mymusic.flac
Now use aplay
. But don't let aplay
decode the
flac file. Do the decoding with sox
such that it sends
the raw audio data to its standard output (denoted by -
).
Send this output through a
pipe |
to the standard input of aplay
.
Then aplay
must be told the format of the input (raw input with
samples in S16_LE (16 bit CD) format, two channels and a sample rate of 44100).
Adjust the -D
parameter if your sound card is not card 0 and
device 0, find the correct parameter of your card with aplay -l
.
sox mymusic.flac -t raw - | aplay -t raw -f S16_LE -c 2 -r 44100 -D hw:0,0
Stop the playback by pressing Ctrl-C.
Use the manpage of
sox
and the
manpage of aplay
if you want to learn more about these
programs.
The sox
program can also decode many other formats. For
flac-files you can alternatively use flac
which has
--skip
and --until
options to decode only a part
of a file. Here is an example (a backslash before a newline character means
that the next line should be appended, this allows to format very long input
lines nicely).
flac --totally-silent --force-raw-format --sign=signed --endian=little \ -d -c mymusic.flac | aplay -t raw -f S16_LE -c 2 -r 44100 -D hw:0,0
pulseaudio
pulseaudio
there can be problem with shown commands if they want to access a hardware device (because the sound server is blocking it). In that case use the
player command with pasuspender
prepended, e.g., instead of
playhrt
use pasuspender -- playhrt
.
play_simple
Now use the program playhrt
from this project instead of
aplay
. Here is the first simple shell script.
#!/bin/bash ######################################################################### ## frankl (C) 2015 play_simple ## ## See README or http://frankl.luebecknet.de/stereoutils/player.html ## for explanations. ######################################################################### CARD="hw:0,0" EXTRABYTESPLAY=0 VERBOSE="--verbose --verbose" sox "$1" -t raw -r 44100 -c 2 -e signed -b 16 - | playhrt \ --stdin \ --device=${CARD} \ --sample-rate=44100 \ --sample-format=S16_LE \ --loops-per-second=1000 \ --mmap \ --non-blocking-write \ ${VERBOSE} \ --extra-bytes-per-second=${EXTRABYTESPLAY} \ --hw-buffer=16384
Here $1
is substituted by the first argument of the call of
this script, that is mymusic.flac
if we call:
play_simple mymusic.flac
Compared to the earlier example we now tell sox
that it should
generate output with two channels, a sample rate of 44100 and with 16 bit
samples. This will also work if the file we want to play is not in 44.1/16
format, sox
will resample to 44.1/16 if needed.
The script calls playhrt
with the recommended options
--mmap
and --non-blocking-write
. Here
playhrt
computes from the options how many bytes are needed by
the sound device per second (44100 x 2 x 2 = 176400 bytes), and it writes
exactly this amount per second to the memory of the driver of your sound
device. Chunks of (almost) the same
size are written 1000 times per second with quite precisely 1/1000 of a
second time distance. In practice it often happens that your computer and
your sound device have a slightly different idea about how long a second is.
In the next section we describe how to synchronize the speed of
playhrt
to your sound device.
--extra-bytes-per-second
parameter for playhrt
If playhrt
is called with --mmap
and with double
--verbose --verbose
argument it will print lines like
playhrt: average available buffer: 8495 (1031175 sec 282972195 nsec)
In the beginning the available buffer should be a bit bigger than half
of the --hw-buffer-size
. When it gets too large or small
playhrt
will try to adjust its speed once and print a
hint about a sensible value for the --extra-bytes-per-second
parameter. If at the end of playback the available buffer size is
much different compared to
the beginning then playhrt
will also print a hint for
a better value of the --extra-bytes-per-second
parameter. This will work more precisely with longer files, and it may
be necessary to improve that parameter in several iterations.
writeloop
and catloop
Sometimes it can be useful to decouple two programs (or chains of programs) via a buffer. For example, the first program reads its data from a not so reliable network or the data are processed in a long chain of different programs, but the second program needs to read data regularly.
Here is a variant of the simple player script where the decoded data are
first written into a buffer with writeloop
and then
they are read with catloop
and sent to playhrt
.
For the buffer there are two possibilities. You can either use two or
more regular files, usually on a ramdisk; or you can use blocks of shared
memory which has the advantage that it avoids file system overhead and
additional buffering by the operating system. We use the second variant,
the convention is to specify names for these shared memory blocks starting
with a slash /
. The first command is
sent into background with &
, such that the second is also
started.
#!/bin/bash ######################################################################### ## frankl (C) 2015 play_writecatloop ## ## See README or http://frankl.luebecknet.de/stereoutils/player.html ## for explanations. ######################################################################### CARD="hw:0,0" EXTRABYTESPLAY=0 VERBOSE="--verbose --verbose" trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}* sox "$1" -t raw -r 44100 -c 2 -e signed -b 16 - | \ writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 & # maybe the start of the background command takes a little time sleep 0.1 catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \ playhrt \ --stdin \ --device=${CARD} \ --sample-rate=44100 \ --sample-format=S16_LE \ --loops-per-second=1000 \ --mmap \ --non-blocking-write \ ${VERBOSE} \ --extra-bytes-per-second=${EXTRABYTESPLAY} \ --hw-buffer=16384
The shared memory blocks can be seen as
virtual files in the directory /dev/shm
. There are also
files sem.bl1*
which are used for synchronization between
the two programs.
The script can be interrupted with Ctrl-C. Without the
trap
line the background processes would remain running.
volrace
In this example we introduce volume control during playback and
the RACE algorithm. The parameters are
given in the file /tmp/VOLRACE
which is created when
it does not exist. Once the music is running you can edit that file
(or change or overwrite it by some script)
to change the parameters on the fly (and volrace
will fade to the new values). If the file contains only one parameter
then RACE is disabled. The first parameter for the volume can be negative
in which case the phase of the music will be inverted.
The default parameters may be interesting on a notebook with builtin
speakers, it creates a 3D-image as in
Ambiophonics.
In a standard
stereo setup try 0.6 13 0.22
, for headphones use only the
first volume parameter or set the third parameter to 0.0.
This script also demonstrates how sox
can convert
the music to 64-bit floating point samples, this is needed by volrace,
and would also be useful for other filters (e.g., a convolver for room
correction). And vice versa it shows how to cut the samples to 16-bit
integers with the application of dithering and noise shaping.
Alternatively, instead of sox
one could also use
cat64
or resample_soxr
from this package.
#!/bin/bash ######################################################################### ## frankl (C) 2015 play_volrace ## ## See README or http://frankl.luebecknet.de/stereoutils/player.html ## for explanations. ######################################################################### CARD="hw:0,0" EXTRABYTESPLAY=0 VERBOSE="--verbose --verbose" trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}* if ! test -e /tmp/VOLRACE ; then echo "0.6 4 0.75" > /tmp/VOLRACE fi sox "$1" -t raw -r 44100 -c 2 -e float -b 64 - | \ volrace --param-file=/tmp/VOLRACE | \ sox -t raw -r 44100 -c 2 -e float -b 64 - \ -t raw -e signed -b 16 - dither -f high-shibata | \ writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 & # maybe the start of the background command takes a little time sleep 0.1 catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \ playhrt \ --stdin \ --device=${CARD} \ --sample-rate=44100 \ --sample-format=S16_LE \ --loops-per-second=1000 \ --mmap \ --non-blocking-write \ ${VERBOSE} \ --extra-bytes-per-second=${EXTRABYTESPLAY} \ --hw-buffer=16384
In this example we apply further processing to the music signal before playing it. First we resample any input to a specified sample rate (192000) and then we use a convolver, which may be fed with a room correction filter and/or a LoCo filter, or which may implement a crossover in the digital domain (in the latter case you would need a multi channel sound card for the output).
Resampling should be done with high quality, in our example script we use
resample_soxr
from this package (this is true 64-bit processing,
while for example sox
would only use 32-bit integers internally).
Adjust the value of the variable SAMPLERATE
if you want other
output than 192000.
The convolving is done with
brutefir
which is called with a configuration file. You can find an example of such a
configuration file on the LoCo
page. Specify your config file in the variable BRUTEFIRCONFIG
in the script. In the example we assume that brutefir
produces
output samples in S32_LE
format.
Of course, if you just want to try the upsampling and no convolving then
delete or comment the line with brutefir
in the script.
A further change compared to the previous example scripts is that we
first copy the music file into RAM and play it from there.
Of course this could be done with simple copying into a ramdisk, like
(assuming /tmp
is a ramdisk):
FILENAME=`basename $1` cp $1 /tmp/${FILENAME}
and then playing /tmp/${FILENAME}
instead of
$1
.
But in our example we use the programs cptoshm
and
shmcat
provided by this package (and assuming that we
want to play a flac-file). This avoids some operating
system and file system overhead.
#!/bin/bash ######################################################################### ## frankl (C) 2015 play_upsample_convolve ## ## See README or http://frank_l.bitbucket.org/stereoutils/player.html ## for explanations. ######################################################################### CARD="hw:0,0" EXTRABYTESPLAY=0 SAMPLERATE=192000 # configure brutefir with output format needed by your DAC # (here we assume S32_LE) BRUTEFIRCONFIG=VERBOSE="--verbose --verbose" # find input sample rate - only needed when resample_soxr is not used below ORIGRATE=`sox --i "$1" | grep "Sample Rate" | cut -d: -f2 | sed -e "s/ //g"` trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT cptoshm --file="$1" --shmname=/play.flac rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}* if ! test -e /tmp/VOLRACE ; then echo "0.6 4 0.75" > /tmp/VOLRACE fi # old version, without resample_soxr #shmcat --shmname=/play.flac | \ # sox -t flac - -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - | \ # sox -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - \ # -t raw - vol 0.8 rate -v -I ${SAMPLERATE} | \ # volrace --param-file=/tmp/VOLRACE | \ # brutefir ${BRUTEFIRCONFIG} -quiet | \ # writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 & resample_soxr --shmname=/play.flac --param-file=/cache/VOLRACE \ --outrate=${SAMPLERATE} --fading-length=100000 | \ brutefir ${BRUTEFIRCONFIG} -quiet | \ writeloop --block-size=1024 --file-size=4096 --shared /bl1 /bl2 /bl3 & # allow some startup time for background command sleep 0.5 # if this is started from an environment that uses the pulseaudio # sound server, you can substitute 'playhrt' below by # 'pasuspender -- playhrt' catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \ playhrt \ --stdin \ --device=${CARD} \ --sample-rate=${SAMPLERATE} \ --sample-format=S32_LE \ --loops-per-second=1000 \ --mmap \ --non-blocking-write \ ${VERBOSE} \ --extra-bytes-per-second=${EXTRABYTESPLAY} \ --hw-buffer=8192
listen_loop
script)In my experience it can be good for the sound quality to do the processing of music data on one sufficiently powerful machine (call this the convolver machine) and to do the actual playing on another dedicated machine (call this the audio machine). The data are transfered between the machines via the (preferably Ethernet) network.
We demonstrate how to achieve this using the program bufhrt
from this package. It buffers incoming data and can send them over the
network to another machine. Similarly to what playhrt
does,
the data are sent in chunks of (almost) constant size within short
time intervals of constant length. If parameters are chosen carefully
it can be avoided that the network buffers on the two machines are filled
up, thereby avoiding that writing or reading data is blocking processes.
The first variant works by starting once a
simple script listen_loop
on the audio machine
that runs a loop which listens on the network for some input and then executes
this input. (Warning: do not use this on a computer which can be connected
from untrusted machines because everyone with network connection to the
audio machine can start arbitrary commands on it.)
The communication between the two computers is done with the
UNIX utility nc
(also called netcat
).
The script looks as follows:
#!/bin/bash ######################################################################### ## frankl (C) 2015 listen_loop ## ## See README or http://frankl.luebecknet.de/stereoutils/player.html ## for explanations. ######################################################################### while true; do command=`nc -l -p 5501` eval "$command" done
Login to the audio machine and start this script in the background.
You may do this in a
tmux
or
screen
session, then you can logout and login again and still check the output of
the remote commands.
listen_loop &
A corresponding player script on the convolver machine sends an
appropriate call of playhrt
to be executed on the audio machine. We modify
play_upsample_convolve
from above to get an example.
Instead of playhrt
we now call
bufhrt
on the convolver machine.
The other example scripts from above can be adjusted similarly to
play on a remote machine.
HINT: there is an error in this script: change
shmcat /play.flac 32768
to
shmcat --shmname=/play.flac
#!/bin/bash ######################################################################### ## frankl (C) 2015 play_remote ## ## See README or http://frankl.luebecknet.de/stereoutils/player.html ## for explanations. ######################################################################### CARD="hw:0,0" VERBOSE="--verbose --verbose" EXTRABYTESBUF=10 EXTRABYTESPLAY=0 AUDIOCOMPUTER="<put here name of your audio machine>" SAMPLERATE=192000 SAMPLEFORMAT=S32_LE # this is 192000 * 4 * 2 (32 bit samples, 2 channels) BYTESPERSECOND=1536000 BRUTEFIRCONFIG="<path to your brutefir config>" HOST=`hostname` PORT=5570 # find input sample rate ORIGRATE=`sox --i "$1" | grep "Sample Rate" | cut -d: -f2 | sed -e "s/ //g"` trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT cptoshm --file="$1" --shmname=/play.flac rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}* if ! test -e /tmp/VOLRACE ; then echo "0.6 4 0.75" > /tmp/VOLRACE fi shmcat /play.flac 32768 | sox -t flac - -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - | \ sox -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - \ -t raw - vol 0.8 rate -v -I ${SAMPLERATE} | \ volrace --param-file=/tmp/VOLRACE | \ brutefir ${BRUTEFIRCONFIG} -quiet | \ writeloop --block-size=1536 --file-size=9216 --shared /bl1 /bl2 /bl3 & sleep 0.5 # tell audio computer what to do, short sleep as startup time for sender REMOTE="sleep 0.3;\ playhrt ${VERBOSE} --host=${HOST} --port=${PORT} \ --sample-rate=${SAMPLERATE} \ --sample-format=${SAMPLEFORMAT} \ --loops-per-second=1000 \ --device=${CARD} --hw-buffer=7680 \ --extra-bytes-per-second=${EXTRABYTESPLAY} \ --non-blocking-write --mmap " echo "${REMOTE}" | nc -q 0 ${AUDIOCOMPUTER} 5501 # instead of # catloop --block-size=1024 --shared /bl1 /bl2 /bl3 | \ # we use the --shared option of bufhrt. bufhrt --bytes-per-second=${BYTESPERSECOND} \ --loops-per-second=2000 --port=${PORT} \ --extra-bytes-per-second=${EXTRABYTESBUF} \ ${VERBOSE} \ --shared /bl1 /bl2 /bl3
A few more comments on the play_remote
script.
If bufhrt
is called with --shared
option then the
size of the shared memory chunks should be divisible by the number of
bytes to be sent per loop (in our example we send 1536 bytes per loop and
the size of the shared memory blocks is 6*1536 = 9216 bytes).
ssh
)In my setup the audio computer is only connected to the convolver
computer, and the listen_loop
approach above is no security
risk. For a more secure access to the audio computer one can use the
secure shell ssh
.
There are several possibilities to configure ssh
such that
no password is needed for login. Consult the web if you do not know how to
do this.
The remote player script becomes a little more tricky in this case,
because remote background processes started via ssh
are by
default terminated when the shell terminates. To avoid this the remote
programs must be called with nohup
and standard input and
output must be redirected. Here is the
play_remote_ssh
script as alternative to
play_remote
above.
HINT: there is an error in this script: change
shmcat /play.flac 32768
to
shmcat --shmname=/play.flac
#!/bin/bash ######################################################################### ## frankl (C) 2015 play_remote_ssh ## ## See README or http://frankl.luebecknet.de/stereoutils/player.html ## for explanations. ######################################################################### CARD="hw:0,0" EXTRABYTESBUF=200 EXTRABYTESPLAY=0 VERBOSE="--verbose --verbose" AUDIOCOMPUTER=<name of your audio computer> SAMPLERATE=192000 SAMPLEFORMAT=S32_LE # this is 192000 * 4 * 2 (32 bit samples, 2 channels) BYTESPERSECOND=1536000 BRUTEFIRCONFIG=<path to your brutefir configuration> HOST=`hostname` PORT=5570 PLAYHRTLOGFILE=/tmp/playhrt.log # find input sample rate ORIGRATE=`sox --i "$1" | grep "Sample Rate" | cut -d: -f2 | sed -e "s/ //g"` trap "trap - SIGTERM && kill -- -$$ " SIGINT SIGTERM EXIT cptoshm --file="$1" --shmname=/play.flac rm -f /dev/shm/bl{1,2,3} /dev/shm/sem.bl{1,2,3}* if ! test -e /tmp/VOLRACE ; then echo "0.6 4 0.75" > /tmp/VOLRACE fi shmcat /play.flac 32768 | sox -t flac - -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - | \ sox -t raw -r ${ORIGRATE} -c 2 -e float -b 64 - \ -t raw - vol 0.8 rate -v -I ${SAMPLERATE} | \ volrace --param-file=/tmp/VOLRACE | \ brutefir ${BRUTEFIRCONFIG} -quiet | \ writeloop --block-size=1536 --file-size=9216 --shared /bl1 /bl2 /bl3 & sleep 0.5 # tell audio computer what to do REMOTE="nohup sleep 0.8 >/dev/null 2>/dev/null </dev/null ;\ nohup playhrt ${VERBOSE} --host=${HOST} --port=${PORT} \ --sample-rate=${SAMPLERATE} \ --sample-format=${SAMPLEFORMAT} \ --loops-per-second=1000 \ --device=${CARD} --hw-buffer=7680 \ --extra-bytes-per-second=${EXTRABYTESPLAY} \ --non-blocking-write --mmap \ >/dev/null 2>${PLAYHRTLOGFILE} </dev/null " #echo "${REMOTE}" | nc -q 0 ${AUDIOCOMPUTER} 5501 REMOTE='sh -c "( ( '${REMOTE}' ) & )"' echo "${REMOTE}" ssh ${AUDIOCOMPUTER} "${REMOTE}" bufhrt --bytes-per-second=${BYTESPERSECOND} \ --loops-per-second=2000 --port-to-write=${PORT} \ --extra-bytes-per-second=${EXTRABYTESBUF} \ ${VERBOSE} \ --shared /bl1 /bl2 /bl3
--extra-bytes-per-second
parameter for bufhrt
The parameters of the bufhrt
utility should be chosen such
that the program writes out exactly the amount of data needed by the
receiving side. In the remote player examples above we can use
the UNIX command netstat -tpn
on the convolver and on the
audio machine repeatedly while the music is playing (on the convolver
machine we look at the entry of the Send-Q
column for
bufhrt
and on the audio machine at the entry of
the Recv-Q
column for playhrt
. If these values
remain roughly constant while the music is playing all settings are fine.
If not, here is an example what to do: Say,
the value on the audio computer decreases (respectively increases) by about
3600 bytes during 5 minutes. Then we can add (respectively subtract) 12 to the
current value of the --extra-bytes-per-second
parameter of
bufhrt
(5 minutes are 300 seconds and 300 * 12 = 3600).
Further finetuning is possible by playing around with explicitly setting
input and output buffer sizes, see parameters --in-net-buffer-size
of playhrt
and --out-net-buffer-size
of
bufhrt
. It could be good to use small buffers, but keep in mind
that an uninterrupted data flow is more important.
bufhrt
When I used a convolver machine further away from the stereo system I have
made good experience with using a small computer like a Raspberry Pi as
buffer between the convolver and audio machine. The buffering was done by
running bufhrt
with input and output from and to the network.
(In my current setup everything is close to the stereo system and only one short high quality network cable is used between convolver and audio machine.)
improvefile
In some experiments I noticed that two bit-identical copies of the same music file on the same SSD, harddisk or other medium can sound differently(!). To my surprise this can still be true when the files are heavily processed (upsampled, convolved, ...) for playback. My guess is that on a hard disk there is a huge bandwidth of possibilities how the same bit pattern can be represented by magnetized particles on a harddisk (similarly for voltages in memory cells of SSD, USB-stick or memory card) and that different representations influence the timing and amplitude of the output signal of the harddisk when the data are read. (I do not think that fragmentation of the file system or error correction plays a role here). There is a longer thread on this topic in this forum (in German).
After a lot of testing I can improve the sound quality of music files on
the SSD in my stereo system quite significantly with the help of
bufhrt
. This is done as in the following script
improvefile
.
The current version and further changes of the underlying code came out of discussions and
extensive listening tests by several users (thanks for all the testing and the feedback!)/
#!/bin/bash ######################################################################### ## frankl (C) 2015-2024 ## ## USAGE: ## improvefile## This script generates a (bit-identical) copy of a file to ## the file . ## ######################################################################### if test -e "$2" ; then echo "the file $2 already exists, please delete it first" exit fi ## This utility is mainly meant for music files (and maybe certain filter and ## program files for music playback). ## With these example parameters the output file will be written ## with 8 MB/sec. These will be written in 1024 loops, that is ## 8 kB per loop. ## Before writing out the data, they are copied 3 times to a temporary ## buffer and back, this is done with 24 MB/sec. ## Once a second the written data are synced to the media containing ## the file. bufhrt --interval --file $1 --outfile=$2 \ --buffer-size=536870912 --loops-per-second=1024 \ --bytes-per-second=8388608 --number-copies=3 \ --ram-loops-per-second=1024 --ram-bytes-per-second=25165824 \ --dsyncs-per-second=1 ## You may experiment with these parameters. ## Of course, you can vary the bytes and loops per second for faster ## or slower writing. ## Or try more, but faster copies (like --number-copies=16 or higher ## and without the --ram... options). ## The sychronization could be done more often (like ## --dsyncs-per-second=32) ## For best results you should reduce the --bytes-per-second, very good ## results on some flash media (CF, SD cards, SSD) were achieved with low ## values like 131072 - of course this takes a long time. ## It can be sensible (but is not needed) that the --bytes-per-second ## are divisible by the value of --loops-per-second and that the number of ## bytes written per loop is a multiple of 512 (the usual block size on ## flash cards, SSDs). But you could also try non-divisible values. ## Here are example parameters found in a cooperation of several users. ## The output is written very slowly, so some patience is needed. So far, ## they yield the best results on the authors playback system. # bufhrt --interval --file="$1" --outfile="$2" \ # --buffer-size=419430400 --loops-per-second=10 \ # --bytes-per-second=188044 --number-copies=10 \ # --ram-loops-per-second=100 --ram-bytes-per-second=1880440 \ # --dsyncs-per-second=1 ## For best results you should use a computer optimized for audio ## (PC or small computers from Odroid or Raspberry Pi with audiophile ## power supply). If you have several CPU cores, try to isolate some ## of them such that they can be used for audio programs exclusively. ## E.g., if CPU 2 is isolated, start bufhrt on it with high priority: ## chrt -f 60 taskset -c 2 bufhrt .....
The script needs to be used only once (not before every playback). The effect on sound quality will probably decrease when the file is copied (with some system program) to another disk or sent over the network to another computer.
The script uses the --interval
option of
bufhrt
. This causes that the program in intervals only reads
data until the (large) buffer is filled and then writes out the buffer
(without reading more input) in
well timed blocks which are refreshed in RAM before writing. If you have a
lot of RAM you can increase the --buffer-size
and you can
experiment with the --bytes-per-second
parameter (which should
be somewhat lower than the maximal write speed of your disk).
I use bufhrt
with similar arguments to store pre-convolved
files on my SSD (that is, in a script like
play_upsample_convolve
above I substitute the final call of
playhrt
with a call to bufhrt
with output file on
the harddisk).
In general it can be advantageous to run as few processes as possible on your convolver and/or audio computer. Stop all jobs and processes which are not needed. If possible, use your audio computer remotely with no graphical user interface running or monitor connected.
It can be useful to give playhrt
and bufhrt
a
high process priority with chrt
. (If you are not allowed
to do this on your system, start your player program with sudo
or change the setuid bit of chrt
once with
sudo chmod 4755 `which chrt`
.) For example, you could
change in one of the scripts above the call playhrt ...
to
chrt -f 70 playhrt ...
.
If you want to copy many music files into RAM you either need a huge
ramdisk or a large space for shared memory blocks.
To create a large ramdisk add (as root or with sudo
)
a line like
none /ramdisk tmpfs defaults,size=800M,mode=1777,exec,nosuid 0 0
in your file /etc/fstab
. If not yet done create the directory
with sudo mkdir /ramdisk
and mount with
sudo mount /ramdisk
(the latter is automatic on reboot).
Adjust the size to some value which is somewhat smaller than the amount of
physical RAM.
To enlarge the space that can be used for shared memory, use a command like
sysctl -w "kernel.shmmax=1782579200"
or some even higher value if you have more than 2GB of RAM. It depends on
your system setup how to set this automatically after reboot. (On my Debian
systems one can put a line kernel.shmmax=1782579200
in a file
/etc/sysctl.d/shmmax.conf
to achieve this.
On a multicore processor it is possible to prescribe the core on which
a process is running, using the taskset
utility. For example,
on a 4-core processor (cores 0,1,2,3) one could put brutefir
and bufhrt
or playhrt
on different cores
(with taskset -a -c 1 brutefir ...
or
chrt -r 99 taskset -c 3 bufhrt ...
and so on.
The speed of modern processors (or the separate cores) is usually
adjusted dynamically by the hardware depending on the load. On some systems
this dynamic change of the speed can be avoided and the speed can be
prescribed by the user. For example, on my convolver machine I delegate the
speed setting to userspace by sudo cpufreq-set -g userspace
.
In the beginning of my player scripts I push up the speed with
cpufreq-set -f 1.2GHz
and at the end I set it back to 0.25GHz
(use cpufreq-info
to find out the possible values).
A very useful trick is to make some CPU cores unavailable to system
programs with the isolcpus
parameter to the Linux kernel. For
example, on my convolver computer, a Raspberry Pi 4 with four CPU cores
numbered 0, 1, 2, 3, I use the kernel parameter isolcpus=1,2,3
.
Then the system only uses core 0 for all processes, except when they are started
explicitly with taskset
on one of the cores 1, 2 or 3.
This way I can reserve cores 1, 2, 3 exclusively for the audio programs.