Determine samplerate LMS, squeezelite or like
Collapse
X
-
LMS 8.2 on Odroid-C4 - SqueezeAMP!, 5xRadio, 5xBoom, 2xDuet, 1xTouch, 1xSB3. Sonos PLAY:3, PLAY:5, Marantz NR1603, Foobar2000, ShairPortW, 2xChromecast Audio, Chromecast v1 and v2, Squeezelite on Pi, Yamaha WX-010, AppleTV 4, Airport Express, GGMM E5, RivaArena 1 & 3 -
Thanks both...
Yes i want to determine the output, or the played samplerate at squeezelite.
The chain is like this (I sometime confuse myself also) :
LMS --> squeezelite --> Loopback(alsa) --> camilladsp (DSP Engine) --> Hardware (soundcard, DAC etc...)
So grapping the samplerate at squeezelite output_alsa.c is useable.
I "hacked" the code so that it paste played samplerate e.g 44100, 88200, 96000 ... 192000 in a textfile named rate.txt
Me and my grown up son, did write (okay he did mostly ) a Pythonscript which grab the samplerate from the textfile.
The Python then choose the right filter (44100 ... 192000) and send via websocket the command to the dsp engine, so that the right filter can be used, for the correct samplerate...
It's like Alpha version 0.1, but working okay for now... I will have to make some real test on my "main" DAC to make sure it sounds right, and that all samplerates are useful.
Also there might be a better way of doing this, have to consider but i will try this first
I drop the Python here just for info! ::
Jesper.
from subprocess import *
import time
infile = "rate.txt"
from websocket import create_connection
ws = create_connection("ws://127.0.0.1:3011")
fin = open(infile, 'r');
old = fin.readlines();
while 1: #While true
fin = open(infile, 'r');
current = fin.readlines();
if(current != old ):
if int(current[0]) == 44100:
ws.send("setconfigname:/home/pi/Cfilters/rate_44100.yml")
ws.send("reload")
print("Changed to 44100")
elif int(current[0]) == 48000:
ws.send("setconfigname:/home/pi/Cfilters/rate_48000.yml")
ws.send("reload")
print("Changed to 48000")
elif int(current[0]) == 88200:
ws.send("setconfigname:/home/pi/Cfilters/rate_88200.yml")
ws.send("reload")
print("Changed to 88200")
elif int(current[0]) == 96000:
ws.send("setconfigname:/home/pi/Cfilters/rate_96000.yml")
ws.send("reload")
print("Changed to 96000")
elif int(current[0]) == 176400:
ws.send("setconfigname:/home/pi/Cfilters/rate_176400.yml")
ws.send("reload")
print("Changed to 176400")
elif int(current[0]) == 192000:
ws.send("setconfigname:/home/pi/Cfilters/rate_192000.yml")
ws.send("reload")
print("Changed to 192000")
elif int(current[0]) == 352800:
ws.send("setconfigname:/home/pi/Cfilters/rate_352800.yml")
ws.send("reload")
print("Changed to 352800")
else:
raise ValueError('Unknown rate detected')
old = current
time.sleep(0.05)Comment
-
LMS --> squeezelite ----(pipe)---> camilladsp (DSP Engine) --> ALSA -> HW
Me and my grown up son, did write (okay he did mostly ) a Pythonscript which grab the samplerate from the textfile.
The Python then choose the right filter (44100 ... 192000) and send via websocket the command to the dsp engine, so that the right filter can be used, for the correct samplerate...
Use something like pygtail or watchdog
Or even better, use a FIFO (named pipe) (or socket) instead of the file.
If you insist on polling, at least do it based on the file modification time.
Also: use a dict lookup to avoid the if...elif
or check if rate is valid and:
ws.send("setconfigname:/home/pi/Cfilters/rate_{}.yml".format(current[0]) )Various SW: Web Interface | Text Interface | Playlist Editor / Generator | Music Classification | Similar Music | Announce | EventTrigger | Ambient Noise Mixer | DB Optimizer | Image Enhancer | Chiptunes | LMSlib2go | ...
Various HowTos: build a self-contained LMS | Bluetooth/ALSA | Control LMS with any device | ...Comment
-
Morning...
RolandO, the chain you pasted is more correct thanks ::
As mentioned before, this approach is less than optimal (as it copies the audio data around (into ALSA, from ALSA, into ALSA again... - which introduces delays and potentially conversion issues) . It should be:
LMS --> squeezelite ----(pipe)---> camilladsp (DSP Engine) --> ALSA -> HW
I think this hack i did in alsa_output is only executed everytime player switches number & samplerate. (I can see that the file is only updated there).
We will change the script to youre suggestions :
Also: use a dict lookup to avoid the if...elif
or check if rate is valid and:
ws.send("setconfigname:/home/pi/Cfilters/rate_{}.yml".format(current[0]) )
Polling the file like this is not only costly in terms of resources, it also introduces a (variable) delay.
Use something like pygtail or watchdog
Or even better, use a FIFO (named pipe) (or socket) instead of the file.
If you insist on polling, at least do it based on the file modification time.
If right i should try to use another way like pygtail or watchdog or FIFO right ?
Regarding the polling, you said i should do it on the file modification time? - Can you explain for newbies ?
Also, if you should make a hack/script which should do this, how would you do it RolandO ??
Rgds; and thanks for the time you use on this; Jesper.Last edited by Jesperlykke; 2020-03-25, 06:16.Comment
-
Logic would be something like ...
Code:oldfilecontents = "" oldlastmodifiedtime = 0 newlastmodifiedtime = 0 oldlastmodifiedtime = last-modified-time (if file exists) loop: get last-modified-time (if file exists) if last-modified-time not-equal to saved last-modified-time then oldlastmodifiedtime = newlastmodifiedtime read file contents close file if new file contents not same as old file contents then do the stuff endif end-if sleep endloop:
Paul Webster
Author of "Now Playing" plugins covering Radio France (FIP etc), PlanetRadio (Bauer - Kiss, Absolute, Scala, JazzFM etc), KCRW, ABC Australia and CBC/Radio-Canada
and, via the extra "Radio Now Playing" plugin lots more - see https://forums.slimdevices.com/showt...Playing-pluginComment
-
That part of the suggestion was to put an IF in there somewhere that is based on the return from https://docs.python.org/library/os.p....path.getmtime
Logic would be something like ...
Code:oldfilecontents = "" oldlastmodifiedtime = 0 newlastmodifiedtime = 0 oldlastmodifiedtime = last-modified-time (if file exists) loop: get last-modified-time (if file exists) if last-modified-time not-equal to saved last-modified-time then oldlastmodifiedtime = newlastmodifiedtime read file contents close file if new file contents not same as old file contents then do the stuff endif end-if sleep endloop:
Sry. I'am lost, cant't figure out what that means? I see it in my head as to compare when rate.txt file was changed last time and then if it's not the same time execute again?
It just makes no sense in my head now ...
I think that the alsa_output.c is only changing my rate.txt when samplerate really is changing (i can see that in the rate.txt file) so i cant see why it is doing heavy work or do delay the stream whatsoever... But i'am properly wrong???
Please explain; JesperComment
-
In your current code you are opening the file each time you go around the loop - and that loop has a sleep of 0.01 (very short).
It is more work for the operating system to open the file each time. It is less work to check the modified time of the file first and only open it if the modified time has changed.
In any case - since this is more of a proof of concept then it does not matter very much if it is inefficient - unless it has an effect on the sound quality.
So - i think that the next thing to do is to do some listening tests and see if you are getting the hoped for improvement in sound.Paul Webster
Author of "Now Playing" plugins covering Radio France (FIP etc), PlanetRadio (Bauer - Kiss, Absolute, Scala, JazzFM etc), KCRW, ABC Australia and CBC/Radio-Canada
and, via the extra "Radio Now Playing" plugin lots more - see https://forums.slimdevices.com/showt...Playing-pluginComment
-
In your current code you are opening the file each time you go around the loop - and that loop has a sleep of 0.01 (very short).
It is more work for the operating system to open the file each time. It is less work to check the modified time of the file first and only open it if the modified time has changed.
In any case - since this is more of a proof of concept then it does not matter very much if it is inefficient - unless it has an effect on the sound quality.
So - i think that the next thing to do is to do some listening tests and see if you are getting the hoped for improvement in sound.
Jesper.Comment
-
I'm also still baffled why you insist on modifying squeezelite to write the sample rate to a file, even though it can already be configured to do so out of the box (see post #15).
Checking the file modification time is faster than reading the content.
Using pygtail or watchdog avoids polling altogether by waiting for a notification sent by the OS that the file has changed.
A name pipe is even better, since the python script simply waits until the sample rate can be read from it.
- chain is LMS --> squeezelite ----(pipe)---> camilladsp (DSP Engine) --> ALSA -> HW
- add code to squeezelite to write sample rate to a named pipe for each new track
- python script waits until the sample rate is written to pipe, sends "change config" command to camilladsp via web socket if sample rate changes
Or avoid all of this by processing everything on the server, as BruteFIR does (see post #6).
If I actually wanted to use camilladsp, I'd check if the BruteFIR plugin works on my setup, and then modify it to use camilladsp instead.
Or (more likely), just configure squeezelite to up-sample and pipe the data directly to camilladsp and be done with it.Various SW: Web Interface | Text Interface | Playlist Editor / Generator | Music Classification | Similar Music | Announce | EventTrigger | Ambient Noise Mixer | DB Optimizer | Image Enhancer | Chiptunes | LMSlib2go | ...
Various HowTos: build a self-contained LMS | Bluetooth/ALSA | Control LMS with any device | ...Comment
-
Probably b/c I pushed him to do so. Don't blame him I'm so used to have my modified version of squeezelite for the bridges and squeezeesp32 that I forgot some of the native logsLMS 8.2 on Odroid-C4 - SqueezeAMP!, 5xRadio, 5xBoom, 2xDuet, 1xTouch, 1xSB3. Sonos PLAY:3, PLAY:5, Marantz NR1603, Foobar2000, ShairPortW, 2xChromecast Audio, Chromecast v1 and v2, Squeezelite on Pi, Yamaha WX-010, AppleTV 4, Airport Express, GGMM E5, RivaArena 1 & 3Comment
-
If you want/need to do some development rather than have scripts or modded squeezelite, wouldn't it be better for camilla DSP to accept a WAV input (sample rate,width in header in line with data) rather than plain PCM so that from each data stream it can determine on the fly the audio format. It would then just be a drop into LMS conf file.Comment
-
If you want/need to do some development rather than have scripts or modded squeezelite, wouldn't it be better for camilla DSP to accept a WAV input (sample rate,width in header in line with data) rather than plain PCM so that from each data stream it can determine on the fly the audio format. It would then just be a drop into LMS conf file.
What do you mean when writingIt would then just be a drop into LMS conf file
Thanks all... really appreciate all the input's here... Thanks; Rgds; Jesper.Comment
-
Why not.
The WAV header is a fixed number (44?) of bytes at start of file or stream. All camilladsp would have to do is on startup read the WAV header, analyse it ( https://jawadsblog.wordpress.com/201...v-file-format/ ) and then use the values as if they are default or from command line / config file.
Audio DSP processing then starts at byte 45. Chunk length should be zero to indicate infinity - it is not required in a streaming environment.
You might even ask the developer to do it as a command line option (e.g. -w) ?
What do you mean when writing ... can you explain ?
Thanks all... really appreciate all the input's here... Thanks; Rgds; Jesper.
For example the AAC to FLAC change
Code:aac flc * * # IF [faad] -q -w -f 1 $FILE$ | [flac] -cs --totally-silent --compression-level-0 --ignore-chunk-sizes -
Code:aac flc * * # IF [faad] -q -w -f 1 $FILE$ | camilladsp camillaoptions -w | [flac] -cs --totally-silent --compression-level-0 --ignore-chunk-sizes -
Comment
-
Good explanation !!
I asked the dev. of the camilladsp the suggestion you wrote...
Excited to see what he say's about that ...
Everyone is so helpfull, so i am pleased
Jesper.Comment
-
If I actually wanted to use camilladsp, I'd check if the BruteFIR plugin works on my setup, and then modify it to use camilladsp instead.
Or (more likely), just configure squeezelite to up-sample and pipe the data directly to camilladsp and be done with it.
While still thinking and ran into other issues with this i need, i tried the route as RolandO also wrote about!
I've been installing the BrutefirDRC plugin and getting it to work as basic now.
======== brutefirwrapper rev 12 starting at 1585476309 2020-03-29 10:05:09
Traceback (most recent call last):
File "/usr/share/squeezeboxserver/Plugins/BrutefirDrc/Bin/brutefirwrapper", line 471, in read_tail
age = time.time()-os.stat(tail_filename).st_mtime
OSError: [Errno 2] No such file or directory: '/tmp/.BrutefirDrc-110/tail-dc_a6_32_18_8f_ab.pcm'
Output follows (0.026s)
TEST_output
44100
TEST_1
Output complete (0.027s)
BruteFIR v1.0m (November 2013) (c) Anders Torger
Internal resolution is 32 bit floating point.
Creating 4 FFTW plans of size 32768...finished.
Loading 3 coefficient sets...finished.
Warning: no support for clock cycle counter on this platform.
Timers for benchmarking may be unreliable.
Filters in process 0: 0 2
Filters in process 1: 1 3
Creating inverse inplace FFTW plan of size 65536 using wisdom...
I can now see the samplerate and some point in code and so...
Next step is to find where the audio actually is played in the code, and let this stream to the camilladsp capture [loopback].
Then if possible let squeezelite grab the output of camilladsp and play it directly on the hardware.
Output follows (0.026s)
TEST_output
44100
TEST_1
Output complete (0.027s) <----- I think brutefirdrc should stop here and let camilladsp handle it from here ?
----------------------------------------------------------------------------------------------------------------------------------------------
BruteFIR v1.0m (November 2013) (c) Anders Torger
Internal resolution is 32 bit floating point.
Creating 4 FFTW plans of size 32768...finished.
Loading 3 coefficient sets...finished.
Warning: no support for clock cycle counter on this platform.
Timers for benchmarking may be unreliable.
Filters in process 0: 0 2
Filters in process 1: 1 3
Creating inverse inplace FFTW plan of size 65536 using wisdom... <------ This takes ~34 seconds on my RPI4 to there actually is music !!
Is it a good idea or is it nogo
Jesper.Comment
Comment