diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2cf0876 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.vscode/ +*.o +squeezelite +squeezelite-pulse +tools/alsacap +tools/find_servers diff --git a/ChangeLog.txt b/ChangeLog.txt deleted file mode 100644 index 7b6571b..0000000 --- a/ChangeLog.txt +++ /dev/null @@ -1,137 +0,0 @@ -Version 1.0 - 15/2/13 -===================== -- initial release - -Version 1.1 - 12/4/13 -===================== - -Minor changes -- add timeout on slimproto connection to detect dead server -- fix issue with clipping on windows by disabling portaudio dither -- silence alsa error messages on linux alsa builds unless debugging is enabled -- hide some additional error messages unless debuging is enabled so usb dacs produce less error messages when turned off and on - -Version 1.2 - 6/7/13 -==================== - -Features -- support of upsampling via libsoxr - -Minor changes -- command line option for setting the service address now requires "-s" before the server address -- fixes a bug where the channels could become swapped when using S16_LE ALSA output -- falls back to polling for a new server if one is not found for more than 30 seconds -- fixes play of wav/aiff local files when the LocalPlayer plugin is active - -Version 1.3 - 6/10/13 -===================== - -Features -- support for wma/alac decode via ffmpeg library (requires compilation with -DFFMPEG) -- support for export of audio data to jivelite to enable visulizations on linux (requires compilation with -DVISEXPORT) - -Minor changes -- support async as well as sync resampling rates -- support on/off of audio device with portaudio -- improved gapless support for aac/mad when skipping to mid track (based on patches from Wouter Ellenbroek) -- various bug fixes - -Version 1.3.1 - 25/11/13 -======================== - -Minor changes -- support of compile time linking for distro packaging, uses -DLINKALL option - -Version 1.4 28/12/13 -==================== - -Features -- native support of dsd playback to dop capable dac or via conversion to pcm and resampling -- support dop in flac playback to dop dacs -- support of output to stdout - -Minor changes -- support of resampling only when sample rate is not natively supported -- fix problem with libmpg123 playback not playing to end of track -- add ablity for player name change to be stored locally in a file (to emulate hardware where name is stored on player) - -Version 1.5 12/1/14 -=================== - -Minor changes -- add configurable delay for switch between pcm and dop -- allow visexport to work with jivelite running as any user -- bug fixes for dsf playback, for status progress on windows using wdm-ks output, and to avoid 100% cpu -- change some logging levels for slimproto to aid readability - -Version 1.6 23/3/14 -=================== - -Minor changes -- add support for direct file playback on windows -- add configurable delay for switch between pcm sample rates -- support build on freebsd -- fix gapless playback on portaudio builds -- fix gapless playback for mp3 localfile case with tags at start of file - -Version 1.6.1 22/4/14 -===================== - -Minor changes -- fix bug with PA version changing sample rate between tracks -- fix crash when skipping in ogg while resampling -- fix typo - -Version 1.6.2 26/5/14 -===================== - -Minor changes -- fix XRUN on track change when resampling on low power cpus -- log command line to logfile when debugging enabled -- option to exclude codecs (-e) -- support parallel execution of libsoxr - -Version 1.6.3 14/6/14 -===================== - -Minor changes -- reduce time to start track when playing local files -- disable use of OPENMP when RESAMPLE build option defined, add new option RESAMPLE_MP to enable it - -Version 1.6.4 7/7/14 -==================== - -Minor changes -- improve synchronisation feedback accuracy - -Version 1.6.5 21/11/14 -====================== - -Minor changes -- fix problem opening ALSA device if 44100 is not supported -- trap setting of hw player mac address - -Version 1.7 1/1/15 -================== - -Minor changes -- allow player modelname to be set at compile or run time -- workaround alsa drivers reporting very large number of available frames -- fix clicks on localfile playback of AIFF files -- add -P option to store process id in a file -- improve error messages for command line parsing - -Version 1.7.1 10/1/15 -===================== - -Minor changes -- fix crash which could occur when resampling - -Version 1.8 1/2/15 -================== - -Features -- support for closing output device when idle with -C option -- support for basic IR input using LIRC on Linux -- support for volume adjustment or unmuting of alsa mixer -- support for inverting output polarity via LMS setting (requires recent 7.9 server) diff --git a/LICENSE.txt b/LICENSE.txt index 94af310..bb31dd1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,7 @@ Squeezelite - lightweight headless squeezebox emulator (c) Adrian Smith 2012-2015, triode1@btinternet.com + Ralph Irving 2015-2017, ralph_irving@hotmail.com Released under GPLv3 license: diff --git a/Makefile b/Makefile index dca2abd..eac4039 100644 --- a/Makefile +++ b/Makefile @@ -1,85 +1,168 @@ -# Cross compile support - create a Makefile which defines these three variables and then includes this Makefile... -CFLAGS ?= -Wall -fPIC -O2 $(OPTS) -LDFLAGS ?= -lasound -lpthread -lm -lrt +#Cross compile support - create a Makefile which defines these three variables and then includes this Makefile... +CFLAGS ?= -Wall -fPIC -O2 +CFLAGS += -fcommon +LDADD ?= -lpthread -lm -lrt EXECUTABLE ?= squeezelite # passing one or more of these in $(OPTS) enables optional feature inclusion OPT_DSD = -DDSD OPT_FF = -DFFMPEG +OPT_ALAC = -DALAC OPT_LINKALL = -DLINKALL OPT_RESAMPLE= -DRESAMPLE OPT_VIS = -DVISEXPORT OPT_IR = -DIR +OPT_GPIO = -DGPIO +OPT_RPI = -DRPI +OPT_NO_FAAD = -DNO_FAAD +OPT_SSL = -DUSE_SSL +OPT_NOSSLSYM= -DNO_SSLSYM +OPT_OPUS = -DOPUS +OPT_PORTAUDIO = -DPORTAUDIO +OPT_PULSEAUDIO = -DPULSEAUDIO SOURCES = \ main.c slimproto.c buffer.c stream.c utils.c \ - output.c output_alsa.c output_pa.c output_stdout.c output_pack.c decode.c \ - flac.c pcm.c mad.c vorbis.c faad.c mpg.c + output.c output_alsa.c output_pa.c output_stdout.c output_pack.c output_pulse.c decode.c \ + flac.c pcm.c mad.c vorbis.c mpg.c SOURCES_DSD = dsd.c dop.c dsd2pcm/dsd2pcm.c SOURCES_FF = ffmpeg.c +SOURCES_ALAC = alac.c alac_wrapper.cpp SOURCES_RESAMPLE = process.c resample.c SOURCES_VIS = output_vis.c SOURCES_IR = ir.c +SOURCES_GPIO = gpio.c +SOURCES_RPI = minimal_gpio.c +SOURCES_FAAD = faad.c +SOURCES_SSL = sslsym.c +SOURCES_OPUS = opus.c LINK_LINUX = -ldl +LINK_ALSA = -lasound +LINK_PORTAUDIO = -lportaudio +LINK_PULSEAUDIO = -lpulse +LINK_SSL = -lssl -lcrypto +LINK_ALAC = -lalac -LINKALL = -lFLAC -lmad -lvorbisfile -lfaad -lmpg123 -LINKALL_FF = -lavcodec -lavformat -lavutil +LINKALL = -lmad -lmpg123 -lFLAC -lvorbisfile -lvorbis -logg +LINKALL_FF = -lavformat -lavcodec -lavutil LINKALL_RESAMPLE = -lsoxr LINKALL_IR = -llirc_client +LINKALL_FAAD = -lfaad +LINKALL_OPUS = -lopusfile -lopus DEPS = squeezelite.h slimproto.h UNAME = $(shell uname -s) # add optional sources -ifneq (,$(findstring $(OPT_DSD), $(CFLAGS))) +ifneq (,$(findstring $(OPT_DSD), $(OPTS))) SOURCES += $(SOURCES_DSD) endif -ifneq (,$(findstring $(OPT_FF), $(CFLAGS))) +ifneq (,$(findstring $(OPT_FF), $(OPTS))) SOURCES += $(SOURCES_FF) endif -ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS))) +ifneq (,$(findstring $(OPT_ALAC), $(OPTS))) + SOURCES += $(SOURCES_ALAC) + DEPS += alac_wrapper.h +endif +ifneq (,$(findstring $(OPT_OPUS), $(OPTS))) + SOURCES += $(SOURCES_OPUS) +endif +ifneq (,$(findstring $(OPT_RESAMPLE), $(OPTS))) SOURCES += $(SOURCES_RESAMPLE) endif -ifneq (,$(findstring $(OPT_VIS), $(CFLAGS))) +ifneq (,$(findstring $(OPT_VIS), $(OPTS))) SOURCES += $(SOURCES_VIS) endif -ifneq (,$(findstring $(OPT_IR), $(CFLAGS))) +ifneq (,$(findstring $(OPT_IR), $(OPTS))) SOURCES += $(SOURCES_IR) +endif +ifneq (,$(findstring $(OPT_GPIO), $(OPTS))) + SOURCES += $(SOURCES_GPIO) +endif +ifneq (,$(findstring $(OPT_RPI), $(OPTS))) + SOURCES += $(SOURCES_RPI) +endif +# ensure GPIO is enabled with RPI +ifneq (,$(findstring $(OPT_RPI), $(OPTS))) +ifeq (,$(findstring $(SOURCES_GPIO), $(SOURCES))) + CFLAGS += $(OPT_GPIO) + SOURCES += $(SOURCES_GPIO) +endif +endif +ifeq (,$(findstring $(OPT_NO_FAAD), $(OPTS))) + SOURCES += $(SOURCES_FAAD) +endif +ifneq (,$(findstring $(OPT_SSL), $(OPTS))) + SOURCES += $(SOURCES_SSL) endif # add optional link options -ifneq (,$(findstring $(OPT_LINKALL), $(CFLAGS))) - LDFLAGS += $(LINKALL) -ifneq (,$(findstring $(OPT_FF), $(CFLAGS))) - LDFLAGS += $(LINKALL_FF) +ifneq (,$(findstring $(OPT_LINKALL), $(OPTS))) + LDADD += $(LINKALL) +ifneq (,$(findstring $(OPT_FF), $(OPTS))) + LDADD += $(LINKALL_FF) endif -ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS))) - LDFLAGS += $(LINKALL_RESAMPLE) +ifneq (,$(findstring $(OPT_OPUS), $(OPTS))) + LDADD += $(LINKALL_OPUS) endif -ifneq (,$(findstring $(OPT_IR), $(CFLAGS))) - LDFLAGS += $(LINKALL_IR) +ifneq (,$(findstring $(OPT_RESAMPLE), $(OPTS))) + LDADD += $(LINKALL_RESAMPLE) +endif +ifneq (,$(findstring $(OPT_IR), $(OPTS))) + LDADD += $(LINKALL_IR) +endif +ifeq (,$(findstring $(OPT_NO_FAAD), $(OPTS))) + LDADD += $(LINKALL_FAAD) +endif +ifneq (,$(findstring $(OPT_SSL), $(OPTS))) + LDADD += $(LINK_SSL) endif else # if not LINKALL and linux add LINK_LINUX ifeq ($(UNAME), Linux) - LDFLAGS += $(LINK_LINUX) + LDADD += $(LINK_LINUX) +endif +ifneq (,$(findstring $(OPT_NOSSLSYM), $(OPTS))) + LDADD += $(LINK_SSL) endif endif -OBJECTS = $(SOURCES:.c=.o) +ifneq (,$(findstring $(OPT_PULSEAUDIO), $(OPTS))) + LDADD += $(LINK_PULSEAUDIO) +else ifneq (,$(findstring $(OPT_PORTAUDIO), $(OPTS))) + LDADD += $(LINK_PORTAUDIO) +else + LDADD += $(LINK_ALSA) +endif + +ifneq (,$(findstring $(OPT_ALAC), $(OPTS))) + LDADD += $(LINK_ALAC) +endif + +OBJECTS = $(addsuffix .o,$(basename $(SOURCES))) all: $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) - $(CC) $(OBJECTS) $(LDFLAGS) -o $@ +ifneq (,$(findstring $(OPT_ALAC), $(OPTS))) + $(CXX) $(OBJECTS) $(LDFLAGS) $(LDADD) -o $@ +else + $(CC) $(OBJECTS) $(LDFLAGS) $(LDADD) -o $@ +endif $(OBJECTS): $(DEPS) +.cpp.o: + $(CXX) $(CXXFLAGS) $(CFLAGS) $(CPPFLAGS) $(OPTS) -Wno-multichar $< -c -o $@ + .c.o: - $(CC) $(CFLAGS) $(CPPFLAGS) $< -c -o $@ + $(CC) $(CFLAGS) $(CPPFLAGS) $(OPTS) $< -c -o $@ clean: rm -f $(OBJECTS) $(EXECUTABLE) + +print-%: + @echo $* = $($*) diff --git a/Makefile.armel b/Makefile.armel new file mode 100644 index 0000000..d6e81a2 --- /dev/null +++ b/Makefile.armel @@ -0,0 +1,24 @@ +# Makefile armel +OPTS = -DRESAMPLE -DFFMPEG -DVISEXPORT -DDSD -DIR -DGPIO -I/usr/local/include +CFLAGS ?= -s -Wall -fPIC -O3 -march=armv5te $(OPTS) +LDFLAGS ?= -s -lasound -lpthread -ldl -lrt -Wl,-rpath,/usr/local/lib +EXECUTABLE ?= squeezelite + +SOURCES = main.c slimproto.c utils.c buffer.c stream.c decode.c flac.c pcm.c mad.c vorbis.c output_alsa.c output.c output_pa.c output_pack.c output_stdout.c output_vis.c dop.c dsd.c dsd2pcm/dsd2pcm.c faad.c mpg.c resample.c process.c ffmpeg.c ir.c gpio.c + +DEPS = squeezelite.h slimproto.h dsd2pcm/dsd2pcm.h + +OBJECTS = $(SOURCES:.c=.o) + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) $(LDFLAGS) -o $@ + +$(OBJECTS): $(DEPS) + +.c.o: + $(CC) $(CFLAGS) $< -c -o $@ + +clean: + rm -f $(OBJECTS) $(EXECUTABLE) diff --git a/Makefile.freebsd b/Makefile.freebsd index a335ace..97aecb8 100644 --- a/Makefile.freebsd +++ b/Makefile.freebsd @@ -1,4 +1,5 @@ +OPTS = -DFFMPEG -DRESAMPLE -DGPIO -DUSE_SSL CPPFLAGS = -I/usr/local/include -I/usr/local/include/portaudio2 -LDFLAGS = -L/usr/local/lib -L/usr/local/lib/portaudio2 -lportaudio -lpthread -lm +LDFLAGS = -L/usr/local/lib /usr/local/lib/libportaudio.a -lm include Makefile diff --git a/Makefile.i386 b/Makefile.i386 new file mode 100644 index 0000000..6e58824 --- /dev/null +++ b/Makefile.i386 @@ -0,0 +1,10 @@ +# OSX 10.5 Intel 32-bit +OPTS = -DPORTAUDIO -DALAC -DOPUS -DRESAMPLE -DGPIO -DLINKALL -DVISEXPORT -DDSD -DUSE_SSL -I./include -I./include/opus -I./include/alac -O2 -I./include -isysroot /Developer/SDKs/MacOSX10.5.sdk -arch i386 + +LDFLAGS = -Wl,-syslibroot,/Developer/SDKs/MacOSX10.5.sdk -arch i386 -mmacosx-version-min=10.5 -L./lib + +LDADD = -lportaudio -lpthread -ldl -lm -framework CoreVideo -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon + +EXECUTABLE = squeezelite-i386 + +include Makefile diff --git a/Makefile.paoss b/Makefile.paoss new file mode 100644 index 0000000..108eb49 --- /dev/null +++ b/Makefile.paoss @@ -0,0 +1,22 @@ +# Cross compile support - create a Makefile which defines these three variables and then includes this Makefile... +CFLAGS ?= -Wall -DPORTAUDIO -DPA18API -fPIC -O3 -I`pwd`/include $(OPTS) +LDFLAGS ?= -lpthread -lm -ldl -lrt -L`pwd`/lib -lportaudio +EXECUTABLE ?= squeezelite-oss + +SOURCES = main.c slimproto.c buffer.c stream.c utils.c output.c output_alsa.c output_pa.c output_stdout.c output_pack.c output_vis.c decode.c flac.c pcm.c mad.c vorbis.c faad.c mpg.c dsd.c dop.c dsd2pcm/dsd2pcm.c ffmpeg.c process.c resample.c ir.c +DEPS = squeezelite.h slimproto.h + +OBJECTS = $(SOURCES:.c=.o) + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) $(LDFLAGS) -o $@ + +$(OBJECTS): $(DEPS) + +.c.o: + $(CC) $(CFLAGS) $< -c -o $@ + +clean: + rm -f $(OBJECTS) $(EXECUTABLE) diff --git a/Makefile.ppc b/Makefile.ppc new file mode 100644 index 0000000..54b578f --- /dev/null +++ b/Makefile.ppc @@ -0,0 +1,25 @@ +# OSX 10.4 PPC 32-bit +CC=powerpc-apple-darwin10-gcc +CXX=powerpc-apple-darwin10-gcc +CFLAGS ?= -s -Wall -fPIC -DOSXPPC -DLINKALL -O2 -I./include -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mcpu=G3 -arch ppc +LDFLAGS ?= -Wl,-syslibroot,/Developer/SDKs/MacOSX10.4u.sdk -arch ppc -mmacosx-version-min=10.3 -L./lib -lFLAC -lvorbisfile -lvorbis -logg -lmad -lfaad -lmpg123 -lpthread -ldl -lm -lportaudio -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon +EXECUTABLE ?= squeezelite-ppc + +SOURCES = main.c slimproto.c buffer.c stream.c utils.c output.c output_alsa.c output_pa.c output_stdout.c output_pack.c decode.c flac.c pcm.c mad.c vorbis.c faad.c mpg.c + +DEPS = squeezelite.h slimproto.h + +OBJECTS = $(SOURCES:.c=.o) + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) $(LDFLAGS) -o $@ + +$(OBJECTS): $(DEPS) + +.c.o: + $(CC) $(CFLAGS) $< -c -o $@ + +clean: + rm -f $(OBJECTS) $(EXECUTABLE) diff --git a/Makefile.ppc64 b/Makefile.ppc64 new file mode 100644 index 0000000..c573576 --- /dev/null +++ b/Makefile.ppc64 @@ -0,0 +1,25 @@ +# OSX 10.3 PPC 64-bit +CC=powerpc-apple-darwin10-gcc +CXX=powerpc-apple-darwin10-gcc +CFLAGS ?= -s -Wall -fPIC -DOSXPPC -DLINKALL -O2 -I./include64 -isysroot /Developer/SDKs/MacOSX10.5.sdk -m64 -arch ppc64 -mmacosx-version-min=10.3 +LDFLAGS ?= -m64 -Wl,-syslibroot,/Developer/SDKs/MacOSX10.5.sdk -arch ppc64 -mmacosx-version-min=10.3 -L./lib64 -lFLAC -lvorbisfile -lvorbis -logg -lmad -lfaad -lmpg123 -lpthread -ldl -lm -lportaudio -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon +EXECUTABLE ?= squeezelite-ppc64 + +SOURCES = main.c slimproto.c buffer.c stream.c utils.c output.c output_alsa.c output_pa.c output_stdout.c output_pack.c decode.c flac.c pcm.c mad.c vorbis.c faad.c mpg.c + +DEPS = squeezelite.h slimproto.h + +OBJECTS = $(SOURCES:.c=.o) + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) $(LDFLAGS) -o $@ + +$(OBJECTS): $(DEPS) + +.c.o: + $(CC) $(CFLAGS) $< -c -o $@ + +clean: + rm -f $(OBJECTS) $(EXECUTABLE) diff --git a/Makefile.pulse b/Makefile.pulse new file mode 100644 index 0000000..661adb5 --- /dev/null +++ b/Makefile.pulse @@ -0,0 +1,6 @@ +# Make with PulseAudio rather than direct alsa +OPTS += -DPULSEAUDIO +LDADD = -lrt -lpulse -lpthread -lm +EXECUTABLE = squeezelite-pulse + +include Makefile diff --git a/Makefile.rpi b/Makefile.rpi new file mode 100644 index 0000000..3941c51 --- /dev/null +++ b/Makefile.rpi @@ -0,0 +1,5 @@ +OPTS = -DOPUS -DALAC -DRESAMPLE -DVISEXPORT -DDSD -DIR -DGPIO -DRPI -DUSE_SSL -DLINKALL -I./include -I./include/opus -I./include/alac -I/usr/local/include -s -march=armv6 -mfloat-abi=hard -mfpu=vfp + +LDFLAGS=-L./lib -L/usr/local/lib -s -lgomp + +include Makefile diff --git a/Makefile.sun b/Makefile.sun new file mode 100644 index 0000000..372fd7f --- /dev/null +++ b/Makefile.sun @@ -0,0 +1,24 @@ +# Solaris SPARC portaudio v18 api +CC = gcc +CPP = cpp +CFLAGS ?= -fPIC -Wall -O3 -DRESAMPLE -DGPIO -I`pwd`/include -s +LDFLAGS ?= -lpthread -lsocket -lnsl -ldl -lrt -lm -L`pwd`/lib -lportaudio -R/opt/squeezelite/lib -s +EXECUTABLE ?= squeezelite-sun + +SOURCES = main.c slimproto.c utils.c buffer.c stream.c decode.c flac.c pcm.c mad.c vorbis.c output_alsa.c output.c output_pa.c output_pack.c output_stdout.c output_vis.c daemonize.c faad.c mpg.c resample.c process.c gpio.c ffmpeg.c +DEPS = squeezelite.h slimproto.h dsd2pcm/dsd2pcm.h + +OBJECTS = $(SOURCES:.c=.o) + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(OBJECTS) $(LDFLAGS) -o $@ + +$(OBJECTS): $(DEPS) + +.c.o: + $(CC) $(CFLAGS) $< -c -o $@ + +clean: + rm -f $(OBJECTS) $(EXECUTABLE) diff --git a/Makefile.x86_64 b/Makefile.x86_64 new file mode 100644 index 0000000..0289a11 --- /dev/null +++ b/Makefile.x86_64 @@ -0,0 +1,8 @@ +# OSX 10.7+ 64-bit only +OPTS = -DPORTAUDIO -DALAC -DOPUS -DRESAMPLE -DGPIO -DLINKALL -DVISEXPORT -DDSD -DUSE_SSL -I./include64 -I./include64/opus -I./include64/alac -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -arch x86_64 -mmacosx-version-min=10.7 + +LDFLAGS = -Wl,-syslibroot,/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -arch x86_64 -mmacosx-version-min=10.7 -L./lib64 + +LDADD = -lportaudio -lpthread -ldl -lm -framework CoreVideo -framework VideoDecodeAcceleration -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon + +include Makefile diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5b0d29 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +Squeezelite v1.9.x, Copyright 2012-2015 Adrian Smith, 2015-2019 Ralph Irving.
+
+See the squeezelite manpage for usage details.
+https://ralph-irving.github.io/squeezelite.html
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+Contains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which
+is subject to its own license.
+
+Contains the Daphile Project full dsd patch Copyright 2013-2017 Daphile,
+which is subject to its own license.
+
+Option to allow server side upsampling for PCM streams (-W) from
+squeezelite-R2 (c) Marco Curti 2015, marcoc1712@gmail.com.
+
+RaspberryPi minimal GPIO Interface
+http://abyz.me.uk/rpi/pigpio/examples.html#Misc_minimal_gpio.
+
+This software uses libraries from the FFmpeg project under
+the LGPLv2.1 and its source can be downloaded from
+https://sourceforge.net/projects/lmsclients/files/source/
diff --git a/alac.c b/alac.c new file mode 100644 index 0000000..b8b524b --- /dev/null +++ b/alac.c @@ -0,0 +1,559 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * (c) Philippe, philippe_44@outlook.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "squeezelite.h" + +#if ALAC +#include "alac_wrapper.h" + +#if BYTES_PER_FRAME == 4 +#define ALIGN8(n) (n << 8) +#define ALIGN16(n) (n) +#define ALIGN24(n) (n >> 8) +#define ALIGN32(n) (n >> 16) +#else +#define ALIGN8(n) (n << 24) +#define ALIGN16(n) (n << 16) +#define ALIGN24(n) (n << 8) +#define ALIGN32(n) (n) +#endif + +#define BLOCK_SIZE (4096 * BYTES_PER_FRAME) +#define MIN_READ BLOCK_SIZE +#define MIN_SPACE (MIN_READ * 4) + +struct chunk_table { + u32_t sample, offset; +}; + +struct alac { + void *decoder; + u8_t *writebuf; + // following used for mp4 only + u32_t consume; + u32_t pos; + u32_t sample; + u32_t nextchunk; + void *stsc; + u32_t skip; + u64_t samples; + u64_t sttssamples; + bool empty; + struct chunk_table *chunkinfo; + u32_t *block_size, default_block_size, block_index; + unsigned sample_rate; + unsigned char channels, sample_size; + unsigned trak, play; +}; + +static struct alac *l; + +extern log_level loglevel; + +extern struct buffer *streambuf; +extern struct buffer *outputbuf; +extern struct streamstate stream; +extern struct outputstate output; +extern struct decodestate decode; +extern struct processstate process; + +#define LOCK_S mutex_lock(streambuf->mutex) +#define UNLOCK_S mutex_unlock(streambuf->mutex) +#define LOCK_O mutex_lock(outputbuf->mutex) +#define UNLOCK_O mutex_unlock(outputbuf->mutex) +#if PROCESS +#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) +#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) +#define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) +#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex) +#define IF_DIRECT(x) if (decode.direct) { x } +#define IF_PROCESS(x) if (!decode.direct) { x } +#else +#define LOCK_O_direct mutex_lock(outputbuf->mutex) +#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex) +#define LOCK_O_not_direct +#define UNLOCK_O_not_direct +#define IF_DIRECT(x) { x } +#define IF_PROCESS(x) +#endif + +// read mp4 header to extract config data +static int read_mp4_header(void) { + size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); + char type[5]; + u32_t len; + + while (bytes >= 8) { + // count trak to find the first playable one + u32_t consume; + + len = unpackN((u32_t *)streambuf->readp); + memcpy(type, streambuf->readp + 4, 4); + type[4] = '\0'; + + if (!strcmp(type, "moov")) { + l->trak = 0; + l->play = 0; + } + if (!strcmp(type, "trak")) { + l->trak++; + } + + // extract audio config from within alac + if (!strcmp(type, "alac") && bytes > len) { + u8_t *ptr = streambuf->readp + 36; + l->decoder = alac_create_decoder(len - 36, ptr, &l->sample_size, &l->sample_rate, &l->channels); + l->play = l->trak; + } + + // extract the total number of samples from stts + if (!strcmp(type, "stsz") && bytes > len) { + u32_t i; + u8_t *ptr = streambuf->readp + 12; + l->default_block_size = unpackN((u32_t *) ptr); ptr += 4; + if (!l->default_block_size) { + u32_t entries = unpackN((u32_t *)ptr); ptr += 4; + l->block_size = malloc((entries + 1)* 4); + for (i = 0; i < entries; i++) { + l->block_size[i] = unpackN((u32_t *)ptr); ptr += 4; + } + l->block_size[entries] = 0; + LOG_DEBUG("total blocksize contained in stsz %u", entries); + } else { + LOG_DEBUG("fixed blocksize in stsz %u", l->default_block_size); + } + } + + // extract the total number of samples from stts + if (!strcmp(type, "stts") && bytes > len) { + u32_t i; + u8_t *ptr = streambuf->readp + 12; + u32_t entries = unpackN((u32_t *)ptr); + ptr += 4; + for (i = 0; i < entries; ++i) { + u32_t count = unpackN((u32_t *)ptr); + u32_t size = unpackN((u32_t *)(ptr + 4)); + l->sttssamples += count * size; + ptr += 8; + } + LOG_DEBUG("total number of samples contained in stts: " FMT_u64, l->sttssamples); + } + + // stash sample to chunk info, assume it comes before stco + if (!strcmp(type, "stsc") && bytes > len && !l->chunkinfo) { + l->stsc = malloc(len - 12); + if (l->stsc == NULL) { + LOG_WARN("malloc fail"); + return -1; + } + memcpy(l->stsc, streambuf->readp + 12, len - 12); + } + + // build offsets table from stco and stored stsc + if (!strcmp(type, "stco") && bytes > len && l->play == l->trak) { + u32_t i; + // extract chunk offsets + u8_t *ptr = streambuf->readp + 12; + u32_t entries = unpackN((u32_t *)ptr); + ptr += 4; + l->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1)); + if (l->chunkinfo == NULL) { + LOG_WARN("malloc fail"); + return -1; + } + for (i = 0; i < entries; ++i) { + l->chunkinfo[i].offset = unpackN((u32_t *)ptr); + l->chunkinfo[i].sample = 0; + ptr += 4; + } + l->chunkinfo[i].sample = 0; + l->chunkinfo[i].offset = 0; + // fill in first sample id for each chunk from stored stsc + if (l->stsc) { + u32_t stsc_entries = unpackN((u32_t *)l->stsc); + u32_t sample = 0; + u32_t last = 0, last_samples = 0; + u8_t *ptr = (u8_t *)l->stsc + 4; + while (stsc_entries--) { + u32_t first = unpackN((u32_t *)ptr); + u32_t samples = unpackN((u32_t *)(ptr + 4)); + if (last) { + for (i = last - 1; i < first - 1; ++i) { + l->chunkinfo[i].sample = sample; + sample += last_samples; + } + } + if (stsc_entries == 0) { + for (i = first - 1; i < entries; ++i) { + l->chunkinfo[i].sample = sample; + sample += samples; + } + } + last = first; + last_samples = samples; + ptr += 12; + } + free(l->stsc); + l->stsc = NULL; + } + } + + // found media data, advance to start of first chunk and return + if (!strcmp(type, "mdat")) { + _buf_inc_readp(streambuf, 8); + l->pos += 8; + bytes -= 8; + if (l->play) { + LOG_DEBUG("type: mdat len: %u pos: %u", len, l->pos); + if (l->chunkinfo && l->chunkinfo[0].offset > l->pos) { + u32_t skip = l->chunkinfo[0].offset - l->pos; + LOG_DEBUG("skipping: %u", skip); + if (skip <= bytes) { + _buf_inc_readp(streambuf, skip); + l->pos += skip; + } else { + l->consume = skip; + } + } + l->sample = l->nextchunk = 1; + l->block_index = 0; + return 1; + } else { + LOG_DEBUG("type: mdat len: %u, no playable track found", len); + return -1; + } + } + + // parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless + if (!strcmp(type, "----") && bytes > len) { + u8_t *ptr = streambuf->readp + 8; + u32_t remain = len - 8, size; + if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) { + ptr += size; remain -= size; + } + if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) { + ptr += size; remain -= size; + } + if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) { + // data is stored as hex strings: 0 start end samples + u32_t b, c; u64_t d; + if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) { + LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d); + if (l->sttssamples && l->sttssamples < b + c + d) { + LOG_DEBUG("reducing samples as stts count is less"); + d = l->sttssamples - (b + c); + } + l->skip = b; + l->samples = d; + } + } + } + + // default to consuming entire box + consume = len; + + // read into these boxes so reduce consume + if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") || + !strcmp(type, "udta") || !strcmp(type, "ilst")) { + consume = 8; + } + // special cases which mix mix data in the enclosing box which we want to read into + if (!strcmp(type, "stsd")) consume = 16; + if (!strcmp(type, "mp4a")) consume = 36; + if (!strcmp(type, "meta")) consume = 12; + + // consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse + if (bytes >= consume) { + LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume); + _buf_inc_readp(streambuf, consume); + l->pos += consume; + bytes -= consume; + } else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") || + !strcmp(type, "stsz") || !strcmp(type, "stco") || !strcmp(type, "----")) ) { + LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes); + _buf_inc_readp(streambuf, bytes); + l->pos += bytes; + l->consume = consume - bytes; + break; + } else if (len > streambuf->size) { + // can't process an atom larger than streambuf! + LOG_ERROR("atom %s too large for buffer %u %u", type, len, streambuf->size); + return -1; + } else { + // make sure there is 'len' contiguous space + _buf_unwrap(streambuf, len); + break; + } + } + + return 0; +} + +static decode_state alac_decode(void) { + size_t bytes; + bool endstream; + u8_t *iptr; + u32_t frames, block_size; + + LOCK_S; + + // data not reached yet + if (l->consume) { + u32_t consume = min(l->consume, _buf_used(streambuf)); + LOG_DEBUG("consume: %u of %u", consume, l->consume); + _buf_inc_readp(streambuf, consume); + l->pos += consume; + l->consume -= consume; + UNLOCK_S; + return DECODE_RUNNING; + } + + if (decode.new_stream) { + int found = 0; + + // mp4 - read header + found = read_mp4_header(); + + if (found == 1) { + bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); + + LOG_INFO("setting track_start"); + LOCK_O; + + output.next_sample_rate = decode_newstream(l->sample_rate, output.supported_rates); + IF_DSD( output.next_fmt = PCM; ) + output.track_start = outputbuf->writep; + if (output.fade_mode) _checkfade(true); + decode.new_stream = false; + + UNLOCK_O; + } else if (found == -1) { + LOG_WARN("[%p]: error reading stream header"); + UNLOCK_S; + return DECODE_ERROR; + } else { + // not finished header parsing come back next time + UNLOCK_S; + return DECODE_RUNNING; + } + } + + bytes = _buf_used(streambuf); + block_size = l->default_block_size ? l->default_block_size : l->block_size[l->block_index]; + + // stream terminated + if (stream.state <= DISCONNECT && (bytes == 0 || block_size == 0)) { + UNLOCK_S; + LOG_DEBUG("end of stream"); + return DECODE_COMPLETE; + } + + // is there enough data for decoding + if (bytes < block_size) { + UNLOCK_S; + return DECODE_RUNNING; + } else if (block_size != l->default_block_size) l->block_index++; + + bytes = min(bytes, _buf_cont_read(streambuf)); + + // need to create a buffer with contiguous data + if (bytes < block_size) { + u8_t *buffer = malloc(block_size); + memcpy(buffer, streambuf->readp, bytes); + memcpy(buffer + bytes, streambuf->buf, block_size - bytes); + iptr = buffer; + } else iptr = streambuf->readp; + + if (!alac_to_pcm(l->decoder, iptr, l->writebuf, 2, &frames)) { + LOG_ERROR("decode error"); + UNLOCK_S; + return DECODE_ERROR; + } + + // and free it + if (bytes < block_size) free(iptr); + + LOG_SDEBUG("block of %u bytes (%u frames)", block_size, frames); + + endstream = false; + // mp4 end of chunk - skip to next offset + if (l->chunkinfo && l->chunkinfo[l->nextchunk].offset && l->sample++ == l->chunkinfo[l->nextchunk].sample) { + if (l->chunkinfo[l->nextchunk].offset > l->pos) { + u32_t skip = l->chunkinfo[l->nextchunk].offset - l->pos; + if (_buf_used(streambuf) >= skip) { + _buf_inc_readp(streambuf, skip); + l->pos += skip; + } else { + l->consume = skip; + } + l->nextchunk++; + } else { + LOG_ERROR("error: need to skip backwards!"); + endstream = true; + } + // mp4 when not at end of chunk + } else if (frames) { + _buf_inc_readp(streambuf, block_size); + l->pos += block_size; + } else { + endstream = true; + } + + UNLOCK_S; + + if (endstream) { + LOG_WARN("unable to decode further"); + return DECODE_ERROR; + } + + // now point at the beginning of decoded samples + iptr = l->writebuf; + + if (l->skip) { + u32_t skip; + if (l->empty) { + l->empty = false; + l->skip -= frames; + LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames); + } + skip = min(frames, l->skip); + LOG_DEBUG("gapless: skipping %u frames at start", skip); + frames -= skip; + l->skip -= skip; + iptr += skip * l->channels * l->sample_size; + } + + if (l->samples) { + if (l->samples < frames) { + LOG_DEBUG("gapless: trimming %u frames from end", frames - l->samples); + frames = (u32_t) l->samples; + } + l->samples -= frames; + } + + LOCK_O_direct; + + while (frames > 0) { + size_t f, count; + ISAMPLE_T *optr; + + IF_DIRECT( + f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME); + optr = (ISAMPLE_T *)outputbuf->writep; + ); + IF_PROCESS( + f = min(frames, process.max_in_frames - process.in_frames); + optr = (ISAMPLE_T *)((u8_t *) process.inbuf + process.in_frames * BYTES_PER_FRAME); + ); + + f = min(f, frames); + count = f; + + if (l->sample_size == 8) { + while (count--) { + *optr++ = ALIGN8(*iptr++); + *optr++ = ALIGN8(*iptr++); + } + } else if (l->sample_size == 16) { + u16_t *_iptr = (u16_t*) iptr; + while (count--) { + *optr++ = ALIGN16(*_iptr++); + *optr++ = ALIGN16(*_iptr++); + } + } else if (l->sample_size == 24) { + while (count--) { + *optr++ = ALIGN24(*(u32_t*) iptr); + *optr++ = ALIGN24(*(u32_t*) (iptr + 3)); + iptr += 6; + } + } else if (l->sample_size == 32) { + u32_t *_iptr = (u32_t*) iptr; + while (count--) { + *optr++ = ALIGN32(*_iptr++); + *optr++ = ALIGN32(*_iptr++); + } + } else { + LOG_ERROR("unsupported bits per sample: %u", l->sample_size); + } + + frames -= f; + + IF_DIRECT( + _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); + ); + IF_PROCESS( + process.in_frames = f; + // called only if there is enough space in process buffer + if (frames) LOG_ERROR("unhandled case"); + ); + } + + UNLOCK_O_direct; + + return DECODE_RUNNING; +} + +static void alac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { + if (l->decoder) alac_delete_decoder(l->decoder); + else l->writebuf = malloc(BLOCK_SIZE * 2); + + if (l->chunkinfo) free(l->chunkinfo); + if (l->block_size) free(l->block_size); + if (l->stsc) free(l->stsc); + l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL; + l->skip = 0; + l->samples = l->sttssamples = 0; + l->empty = false; + l->pos = l->consume = l->sample = l->nextchunk = 0; +} + +static void alac_close(void) { + if (l->decoder) alac_delete_decoder(l->decoder); + if (l->chunkinfo) free(l->chunkinfo); + if (l->block_size) free(l->block_size); + if (l->stsc) free(l->stsc); + l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL; + free(l->writebuf); +} + +struct codec *register_alac(void) { + static struct codec ret = { + 'l', // id + "alc", // types + MIN_READ, // min read + MIN_SPACE, // min space assuming a ratio of 2 + alac_open, // open + alac_close, // close + alac_decode, // decode + }; + + l = malloc(sizeof(struct alac)); + if (!l) { + return NULL; + } + + l->decoder = l->chunkinfo = l->stsc = l->block_size = NULL; + + LOG_INFO("using alac to decode alc"); + return &ret; +} + +#endif /* ALAC */ diff --git a/alac_wrapper.cpp b/alac_wrapper.cpp new file mode 100644 index 0000000..427b9b4 --- /dev/null +++ b/alac_wrapper.cpp @@ -0,0 +1,65 @@ +/* + * alac_wrapper.c - ALAC decoder wrapper + * + * (c) Philippe, philippe_44@outlook.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include + +#include "ALACDecoder.h" +#include "ALACBitUtilities.h" +#include "alac_wrapper.h" + +typedef struct alac_codec_s { + ALACDecoder *Decoder; + unsigned block_size, frames_per_packet; +} alac_codec_t; + +/*----------------------------------------------------------------------------*/ +extern "C" struct alac_codec_s *alac_create_decoder(int magic_cookie_size, unsigned char *magic_cookie, + unsigned char *sample_size, unsigned *sample_rate, + unsigned char *channels) { + struct alac_codec_s *codec = (struct alac_codec_s*) malloc(sizeof(struct alac_codec_s)); + + codec->Decoder = new ALACDecoder; + codec->Decoder->Init(magic_cookie, magic_cookie_size); + + *channels = codec->Decoder->mConfig.numChannels; + *sample_rate = codec->Decoder->mConfig.sampleRate; + *sample_size = codec->Decoder->mConfig.bitDepth; + + codec->frames_per_packet = codec->Decoder->mConfig.frameLength; + codec->block_size = codec->frames_per_packet * (*channels) * (*sample_size) / 8; + + return codec; +} + +/*----------------------------------------------------------------------------*/ +extern "C" void alac_delete_decoder(struct alac_codec_s *codec) { + delete (ALACDecoder*) codec->Decoder; + free(codec); +} + +/*----------------------------------------------------------------------------*/ +extern "C" bool alac_to_pcm(struct alac_codec_s *codec, unsigned char* input, + unsigned char *output, char channels, unsigned *out_frames) { + BitBuffer input_buffer; + + BitBufferInit(&input_buffer, input, codec->block_size); + return codec->Decoder->Decode(&input_buffer, output, codec->frames_per_packet, channels, out_frames) == ALAC_noErr; +} + diff --git a/alac_wrapper.h b/alac_wrapper.h new file mode 100644 index 0000000..5f01483 --- /dev/null +++ b/alac_wrapper.h @@ -0,0 +1,40 @@ +/***************************************************************************** + * alac_wrapper.h: ALAC coder wrapper + * + * Copyright (C) 2016 Philippe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + *****************************************************************************/ +#ifndef __ALAC_WRAPPER_H_ +#define __ALAC_WRAPPER_H_ + +struct alac_codec_s; + +#ifdef __cplusplus +extern "C" { +#endif + +struct alac_codec_s *alac_create_decoder(int magic_cookie_size, unsigned char *magic_cookie, + unsigned char *sample_size, unsigned *sample_rate, + unsigned char *channels); +void alac_delete_decoder(struct alac_codec_s *codec); +bool alac_to_pcm(struct alac_codec_s *codec, unsigned char* input, + unsigned char *output, char channels, unsigned *out_frames); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/alpine/APKBUILD b/alpine/APKBUILD new file mode 100644 index 0000000..6f5267a --- /dev/null +++ b/alpine/APKBUILD @@ -0,0 +1,43 @@ +# Contributor: Carl Chave +# Maintainer: Ralph Irving +pkgname=squeezelite +pkgver=1.9.7.1273 +pkgrel=1 +pkgdesc="Lightweight headless squeezebox player for Logitech Media Server" +url="https://github.com/ralph-irving/squeezelite" +arch="all" +license="GPL-3.0-or-later3" +options="!check" # No test suite +depends="flac alsa-lib faad2 mpg123 libvorbis libmad soxr openssl opusfile libalac" +makedepends="flac-dev alsa-lib-dev faad2-dev mpg123-dev libvorbis-dev libmad-dev soxr-dev openssl-dev opusfile-dev opus-dev libalac-dev" +install="$pkgname.pre-install" +subpackages="$pkgname-doc $pkgname-openrc" +source="$pkgname-$pkgver.zip::https://github.com/ralph-irving/squeezelite/archive/master.zip load-libtremor-first.patch $pkgname.confd $pkgname.initd" +builddir="$srcdir/$pkgname-master" + +build() { + cd "$builddir" + make OPTS="-DRESAMPLE -DDSD -DGPIO -DVISEXPORT -DUSE_SSL -DNO_SSLSYM -DOPUS -DALAC -I/usr/include/opus -I/usr/include/alac" + gcc -Os -fomit-frame-pointer -fcommon -s -o find_servers tools/find_servers.c + gcc -Os -fomit-frame-pointer -fcommon -s -o alsacap tools/alsacap.c -lasound +} + +package() { + cd "$builddir" + install -Dm 755 squeezelite \ + "$pkgdir"/usr/bin/squeezelite + install -Dm 755 alsacap \ + "$pkgdir"/usr/bin/alsacap + install -Dm 755 find_servers \ + "$pkgdir"/usr/bin/find_servers + install -Dm 644 doc/squeezelite.1 \ + "$pkgdir"/usr/share/man/man1/squeezelite.1 + install -Dm 644 "$srcdir"/squeezelite.confd \ + "$pkgdir"/etc/conf.d/squeezelite + install -Dm 755 "$srcdir"/squeezelite.initd \ + "$pkgdir"/etc/init.d/squeezelite +} + +sha512sums="93d7695abb0bf670590cef73c2d9d484771e63acca39864b60cd819bf4b42fff87f9a82f7d419acce469c493d6bedfaba1207e646dde151bc76af959874f16cd load-libtremor-first.patch +ff552fcbbbf2b2291958fa1c61057f54ba0d9b19620666036dd1e19897b5d7bcc543c40699c3ee53b2eec7a38b9cf46cb9205c2048f7d5488c23085d9904f114 squeezelite.confd +fdc0358c10c56772d5e70178eac88f62729c22965c530ad989ebcc8aa4af8c871085bdeb8b4ccd96d8cf2e87388258180054d908719f81f2745d97654f6d60eb squeezelite.initd" diff --git a/alpine/libalac/APKBUILD b/alpine/libalac/APKBUILD new file mode 100644 index 0000000..0328877 --- /dev/null +++ b/alpine/libalac/APKBUILD @@ -0,0 +1,49 @@ +# Maintainer: Ralph Irving +pkgname=libalac +pkgver=1.0.0 +pkgrel=1 +pkgdesc="Apple Lossless Audio Codec (ALAC) library" +url="https://github.com/TimothyGu/alac" +arch="all" +subpackages="$pkgname-doc $pkgname-dev" +license="Apache 2.0" +makedepends="git autoconf automake make tar" +source="fix-arm-segfault.patch \ + alac-version.patch" + +prepare() { + cd "$builddir/../" + + git clone $url $pkgname-$pkgver + + cd $pkgname-$pkgver + + for patchfile in $source; do + patch -p1 -i ../$patchfile + done +} + +build() { + cd "$builddir" + autoreconf -if + ./configure \ + --build=$CBUILD \ + --host=$CHOST \ + --prefix=/usr + + make -j1 +} + +check() { + cd "$builddir" + make check +} + +package() { + cd "$builddir" + make DESTDIR="$pkgdir" install + install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/COPYING +} + +sha512sums="e72b13714476170108844b84c0043dc06d2ff2e8c9b651a7ad1571d148fc5567aca48048646d16fb82630d6972a31b9328f04e522972b297d1cf8e804785867f fix-arm-segfault.patch +093379f79b5dc9f5b8aa45826d61738b088d78305a7d514df33851ae34d02ee9034a8ecddf2558fcb1bf4daaf64c620ea4411521908cfc748e31fd0a2d50bbf7 alac-version.patch" diff --git a/alpine/libalac/alac-version.patch b/alpine/libalac/alac-version.patch new file mode 100644 index 0000000..2af91bf --- /dev/null +++ b/alpine/libalac/alac-version.patch @@ -0,0 +1,11 @@ +--- libalac-1.0.0/configure.ac.orig 2020-09-06 10:55:00.450562777 -0400 ++++ libalac-1.0.0/configure.ac 2020-09-06 10:56:39.308930134 -0400 +@@ -14,7 +14,7 @@ + dnl See the License for the specific language governing permissions and + dnl limitations under the License. + +-AC_INIT([alac], [0.0r4+tg1], [nobody]) ++AC_INIT([alac], [1.0.0], [nobody]) + AC_CONFIG_AUX_DIR(.) + AC_CONFIG_MACRO_DIR([m4]) + AM_INIT_AUTOMAKE([tar-ustar foreign]) diff --git a/alpine/libalac/fix-arm-segfault.patch b/alpine/libalac/fix-arm-segfault.patch new file mode 100644 index 0000000..1d47782 --- /dev/null +++ b/alpine/libalac/fix-arm-segfault.patch @@ -0,0 +1,13 @@ +diff --git a/codec/EndianPortable.c b/codec/EndianPortable.c +index 5a7d5b8..b8423c9 100644 +--- a/codec/EndianPortable.c ++++ b/codec/EndianPortable.c +@@ -40,6 +40,8 @@ + #define TARGET_RT_LITTLE_ENDIAN 1 + #elif defined (TARGET_OS_WIN32) + #define TARGET_RT_LITTLE_ENDIAN 1 ++#elif defined (__arm__) || defined(__aarch64__) ++#define TARGET_RT_LITTLE_ENDIAN 1 + #endif + + uint16_t Swap16NtoB(uint16_t inUInt16) diff --git a/alpine/libtremor/APKBUILD b/alpine/libtremor/APKBUILD new file mode 100644 index 0000000..a6228a4 --- /dev/null +++ b/alpine/libtremor/APKBUILD @@ -0,0 +1,40 @@ +# Maintainer: Ralph Irving +pkgname=libtremor +pkgver=0.19681 +pkgrel=0 +pkgdesc="Fixed Point Ogg Vorbis compliant software decoder library" +url="https://svn.xiph.org/trunk/Tremor" +arch="all" +license="GPL-3.0-only" +subpackages="$pkgname-doc $pkgname-dev" +makedepends="libogg-dev subversion autoconf automake" +depends="libogg" +options="!check" + +build() { + cd "$builddir/../" + + svn checkout https://svn.xiph.org/trunk/Tremor $pkgname-$pkgver + + cd $pkgname-$pkgver + + # Patch for error: + # ./configure: line 9215: syntax error near unexpected token `,' + # ./configure: line 9215: ` XIPH_PATH_OGG(, as_fn_error $? "must have Ogg installed!" "$LINENO" 5)' + sed -i "s/\(XIPH_PATH_OGG(, AC_MSG_ERROR(must have Ogg installed!))\)/#\1/" configure.in + + ./autogen.sh \ + --build=$CBUILD \ + --host=$CHOST \ + --prefix=/usr \ + --enable-static=no + + make +} + +package() { + cd "$builddir" + + make DESTDIR="$pkgdir" install + install -Dm644 COPYING "$pkgdir"/usr/share/licenses/$pkgname/COPYING +} diff --git a/alpine/load-libtremor-first.patch b/alpine/load-libtremor-first.patch new file mode 100644 index 0000000..4850ca8 --- /dev/null +++ b/alpine/load-libtremor-first.patch @@ -0,0 +1,23 @@ +Index: vorbis.c +=================================================================== +--- squeezelite/vorbis.c (revision 1213) ++++ squeezelite/vorbis.c (working copy) +@@ -320,14 +320,14 @@ + + static bool load_vorbis() { + #if !LINKALL +- void *handle = dlopen(LIBVORBIS, RTLD_NOW); ++ void *handle = dlopen(LIBTREMOR, RTLD_NOW); + char *err; +- bool tremor = false; ++ bool tremor = true; + + if (!handle) { +- handle = dlopen(LIBTREMOR, RTLD_NOW); ++ handle = dlopen(LIBVORBIS, RTLD_NOW); + if (handle) { +- tremor = true; ++ tremor = false; + } else { + LOG_INFO("dlerror: %s", dlerror()); + return false; diff --git a/alpine/squeezelite.confd b/alpine/squeezelite.confd new file mode 100644 index 0000000..86235b4 --- /dev/null +++ b/alpine/squeezelite.confd @@ -0,0 +1,31 @@ +# Copyright 1999-2017 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +# Configuration for /etc/init.d/squeezelite + +# IP address of Logitech Media Server; leave this blank to try to +# locate the server via auto-discovery. +# If you fill this in then include "-s" before the IP address, eg: +# SL_SERVER_IP="-s 1.2.3.4" +SL_SERVERIP="" + +# User that Squeezelite should run as. The dedicated 'squeezelite' +# user is preferred to avoid running with high privilege. This user +# should be a member of the 'audio' group to allow access to the audio +# hardware. Running as the 'root' user allows the sound output thread +# to run at a very high priority -- this can help avoid gaps in +# playback, but could be a potential security problem if there are +# exploitable vulnerabilities in Squeezelite. +SL_USER=squeezelite + +# Any other switches to pass to Squeezelite. See 'squeezelite -h' for +# a description of all possible switches. + +# Example setting: +# 1. the ALSA output device +# 2. the player name +# 3. turning on visualiser support (-v) +# +# SL_OPTS="-o sysdefault -n $HOSTNAME -v" +# +SL_OPTS="" diff --git a/alpine/squeezelite.initd b/alpine/squeezelite.initd new file mode 100644 index 0000000..ba6aa26 --- /dev/null +++ b/alpine/squeezelite.initd @@ -0,0 +1,31 @@ +#!/sbin/openrc-run +# Copyright 1999-2015 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +depend() { + need net + use alsasound + after bootmisc +} + +start() { + ebegin "Starting squeezelite" + start-stop-daemon \ + --start \ + --exec /usr/bin/squeezelite \ + --pidfile /run/squeezelite.pid \ + --make-pidfile \ + --user ${SL_USER} \ + --background \ + -- ${SL_OPTS} ${SL_SERVERIP} + eend $? +} + +stop() { + ebegin "Stopping squeezelite" + start-stop-daemon \ + --stop \ + --exec /usr/bin/squeezelite \ + --pidfile /run/squeezelite.pid + eend $? +} diff --git a/alpine/squeezelite.pre-install b/alpine/squeezelite.pre-install new file mode 100644 index 0000000..19da2ab --- /dev/null +++ b/alpine/squeezelite.pre-install @@ -0,0 +1,5 @@ +#!/bin/sh + +adduser -S -D -H -h /dev/null -s /sbin/nologin -G audio -g squeezelite squeezelite 2>/dev/null + +exit 0 diff --git a/buffer.c b/buffer.c index fd83a76..b43a33e 100644 --- a/buffer.c +++ b/buffer.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -93,6 +94,56 @@ buf->base_size = size; } +void _buf_unwrap(struct buffer *buf, size_t cont) { + ssize_t len, by = cont - (buf->wrap - buf->readp); + size_t size; + u8_t *scratch; + + // do nothing if we have enough space + if (by <= 0 || cont >= buf->size) return; + + // buffer already unwrapped, just move it up + if (buf->writep >= buf->readp) { + memmove(buf->readp - by, buf->readp, buf->writep - buf->readp); + buf->readp -= by; + buf->writep -= by; + return; + } + + // how much is overlapping + size = by - (buf->readp - buf->writep); + len = buf->writep - buf->buf; + + // buffer is wrapped and enough free space to move data up directly + if (size <= 0) { + memmove(buf->readp - by, buf->readp, buf->wrap - buf->readp); + buf->readp -= by; + memcpy(buf->wrap - by, buf->buf, min(len, by)); + if (len > by) { + memmove(buf->buf, buf->buf + by, len - by); + buf->writep -= by; + } else buf->writep += buf->size - by; + return; + } + + scratch = malloc(size); + + // buffer is wrapped but not enough free room => use scratch zone + if (scratch) { + memcpy(scratch, buf->writep - size, size); + memmove(buf->readp - by, buf->readp, buf->wrap - buf->readp); + buf->readp -= by; + memcpy(buf->wrap - by, buf->buf, by); + memmove(buf->buf, buf->buf + by, len - by - size); + buf->writep -= by; + memcpy(buf->writep - size, scratch, size); + free(scratch); + } else { + _buf_unwrap(buf, cont / 2); + _buf_unwrap(buf, cont - cont / 2); + } +} + void buf_init(struct buffer *buf, size_t size) { buf->buf = malloc(size); buf->readp = buf->buf; diff --git a/daemonize.c b/daemonize.c new file mode 100644 index 0000000..0a62d82 --- /dev/null +++ b/daemonize.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2013 Ralph Irving + * + * daemonize.c is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * daemonize.c is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SlimProtoLib; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "squeezelite.h" + +#include +#include + +void fork_child_handler(int signum) +{ + switch(signum) { + case SIGALRM: exit(EXIT_FAILURE); break; + case SIGUSR1: exit(EXIT_SUCCESS); break; + case SIGCHLD: exit(EXIT_FAILURE); break; + } +} + +pid_t parent; +void init_daemonize() +{ + pid_t pid, sid; + + /* already a daemon */ + if ( getppid() == 1 ) return; + + /* Trap signals that we expect to recieve */ + signal(SIGCHLD,fork_child_handler); + signal(SIGUSR1,fork_child_handler); + signal(SIGALRM,fork_child_handler); + + /* Fork off the parent process */ + pid = fork(); + if (pid < 0) { + syslog( LOG_ERR, "unable to fork daemon, code=%d (%s)", + errno, strerror(errno) ); + exit(EXIT_FAILURE); + } + + /* If we got a good PID, then we can exit the parent process. */ + if (pid > 0) { + + /* Wait for confirmation from the child via SIGTERM or SIGCHLD, or + for two seconds to elapse (SIGALRM). pause() should not return. */ + alarm(2); + pause(); + + exit(EXIT_FAILURE); + } + + /* At this point we are executing as the child process */ + parent = getppid(); + + /* Cancel certain signals */ + signal(SIGCHLD,SIG_DFL); /* A child process dies */ + signal(SIGTSTP,SIG_IGN); /* Various TTY signals */ + signal(SIGTTOU,SIG_IGN); + signal(SIGTTIN,SIG_IGN); + signal(SIGHUP, SIG_IGN); /* Ignore hangup signal */ + signal(SIGTERM,SIG_DFL); /* Die on SIGTERM */ + + /* Change the file mode mask */ + umask(0); + + /* Create a new SID for the child process */ + sid = setsid(); + if (sid < 0) { + syslog( LOG_ERR, "unable to create a new session, code %d (%s)", + errno, strerror(errno) ); + exit(EXIT_FAILURE); + } + + /* Change the current working directory. This prevents the current + directory from being locked; hence not being able to remove it. */ + if ((chdir("/")) < 0) { + syslog( LOG_ERR, "unable to change directory to %s, code %d (%s)", + "/", errno, strerror(errno) ); + exit(EXIT_FAILURE); + } +} + +int daemon( int nochdir, int noclose ) { + + if ( ! noclose ) + { + /* Redirect standard files to /dev/null */ + freopen( "/dev/null", "r", stdin); + freopen( "/dev/null", "w", stdout); + freopen( "/dev/null", "w", stderr); + } + + /* Tell the parent process that we are A-okay */ + kill( parent, SIGUSR1 ); + + return 0; +} diff --git a/decode.c b/decode.c index add5fcb..dda7fa4 100644 --- a/decode.c +++ b/decode.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -119,42 +120,87 @@ return 0; } +static void sort_codecs(int pry, struct codec* ptr) { + static int priority[MAX_CODECS]; + int i, tpry; + struct codec* tptr; + + for (i = 0; i < MAX_CODECS; i++) { + if (!codecs[i]) { + codecs[i] = ptr; + priority[i] = pry; + return; + } + if (pry < priority[i]) { + tptr = codecs[i]; + codecs[i] = ptr; + ptr = tptr; + tpry = priority[i]; + priority[i] = pry; + pry = tpry; + } + } +} + static thread_type thread; void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs) { int i; + char* order_codecs; loglevel = level; - LOG_INFO("init decode, include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs); + LOG_INFO("init decode"); // register codecs // dsf,dff,alc,wma,wmap,wmal,aac,spt,ogg,ogf,flc,aif,pcm,mp3 i = 0; #if DSD - if (!strstr(exclude_codecs, "dsd") && (!include_codecs || strstr(include_codecs, "dsd"))) codecs[i++] = register_dsd(); -#endif -#if FFMPEG - if (!strstr(exclude_codecs, "alac") && (!include_codecs || strstr(include_codecs, "alac"))) codecs[i++] = register_ff("alc"); - if (!strstr(exclude_codecs, "wma") && (!include_codecs || strstr(include_codecs, "wma"))) codecs[i++] = register_ff("wma"); -#endif - if (!strstr(exclude_codecs, "aac") && (!include_codecs || strstr(include_codecs, "aac"))) codecs[i++] = register_faad(); - if (!strstr(exclude_codecs, "ogg") && (!include_codecs || strstr(include_codecs, "ogg"))) codecs[i++] = register_vorbis(); - if (!strstr(exclude_codecs, "flac") && (!include_codecs || strstr(include_codecs, "flac"))) codecs[i++] = register_flac(); - if (!strstr(exclude_codecs, "pcm") && (!include_codecs || strstr(include_codecs, "pcm"))) codecs[i++] = register_pcm(); + if (!strstr(exclude_codecs, "dsd") && (!include_codecs || (order_codecs = strstr(include_codecs, "dsd")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_dsd()); +#endif +#if ALAC + if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_alac()); +#elif FFMPEG + if (!strstr(exclude_codecs, "alac") && (!include_codecs || (order_codecs = strstr(include_codecs, "alac")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("alc")); + if (!strstr(exclude_codecs, "wma") && (!include_codecs || (order_codecs = strstr(include_codecs, "wma")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_ff("wma")); +#endif +#ifndef NO_FAAD + if (!strstr(exclude_codecs, "aac") && (!include_codecs || (order_codecs = strstr(include_codecs, "aac")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_faad()); +#endif + if (!strstr(exclude_codecs, "ogg") && (!include_codecs || (order_codecs = strstr(include_codecs, "ogg")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_vorbis()); +#if OPUS + if (!strstr(exclude_codecs, "ops") && (!include_codecs || (order_codecs = strstr(include_codecs, "ops")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_opus()); +#endif + if (!strstr(exclude_codecs, "flac") && (!include_codecs || (order_codecs = strstr(include_codecs, "flac")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_flac()); + if (!strstr(exclude_codecs, "pcm") && (!include_codecs || (order_codecs = strstr(include_codecs, "pcm")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_pcm()); // try mad then mpg for mp3 unless command line option passed if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mad")) && - (!include_codecs || strstr(include_codecs, "mp3") || strstr(include_codecs, "mad"))) codecs[i] = register_mad(); - if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mpg")) && !codecs[i] && - (!include_codecs || strstr(include_codecs, "mp3") || strstr(include_codecs, "mpg"))) codecs[i] = register_mpg(); + (!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mad")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mad()); + else if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mpg")) && + (!include_codecs || (order_codecs = strstr(include_codecs, "mp3")) || (order_codecs = strstr(include_codecs, "mpg")))) + sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg()); + + LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs); mutex_create(decode.mutex); #if LINUX || OSX || FREEBSD pthread_attr_t attr; pthread_attr_init(&attr); +#ifdef PTHREAD_STACK_MIN pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + DECODE_THREAD_STACK_SIZE); +#endif pthread_create(&thread, &attr, decode_thread, NULL); pthread_attr_destroy(&attr); #endif diff --git a/doc/squeezelite.1 b/doc/squeezelite.1 new file mode 100644 index 0000000..c0096c1 --- /dev/null +++ b/doc/squeezelite.1 @@ -0,0 +1,378 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" (C) Copyright 2013-4 Chris Boot +.\" +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH SQUEEZELITE 1 "2020-07-16" "Debian Project" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +squeezelite \- Lightweight headless Squeezebox emulator +.SH SYNOPSIS +.B squeezelite +.RI [ options ] +.SH DESCRIPTION +.B Squeezelite +is a small headless Logitech Squeezebox emulator. It is aimed at supporting high +quality audio including USB DAC based output at multiple sample rates. +.PP +The player is controlled using, and media is streamed from, a Logitech Media +Server instance running somewhere on the local network. +.SH OPTIONS +This program supports the following options: +.TP +.B \-? +Show a summary of the available command-line options. +.TP +.B \-s [:] +Connect to the specified Logitech Media Server, otherwise uses automatic +discovery to find server on the local network. This option should only be needed +if automatic discovery does not work, or the server is not on the local network +segment (e.g. behind a router). +.TP +.B \-o +Specify the audio output device; the default value is +.IR default . +Use the +.B \-l +option to list available output devices. +.I - +can be used to output raw samples to standard output. +.TP +.B \-l +List available audio output devices to stdout and exit. These device names can +be passed to the +.B \-o +option in order to select a particular device or configuration to use for audio +playback. +.TP +.B \-a +Specify parameters used when opening an audio output device. +.PP +.RS +For ALSA, the format +.B :

::: +is used where +.B +is the buffer time in milliseconds (values less than 500) or size in bytes (default +.IR 40 ms); +.B

+is the period count (values less than 50) or size in bytes (default +.IR 4 " periods);" +.B +is the sample format (possible values: +.IR 16 ", " 24 ", " 24_3 " or " 32 ); +.B +is whether to use mmap (possible values: +.IR 0 " or " 1 ). +.B +open ALSA output device twice. (possible values: +.IR 0 " or " 1 ). +.RE +.RS +.PP +For Linux PortAudio, the value +.B +is simply the target latency in milliseconds. +.RE +.RS +.PP +For MacOS, +.B : +.B +is target latency in milliseconds. +.B +open device in Pro Mode or Play Nice (respective values: +.IR 0 " or " 1 ). +.RE +.RS +.PP +For Windows, +.B : +.B +is target latency in milliseconds. +.B +use exclusive mode for WASAPI (possible values: +.IR 0 " or " 1 ). +.RE +.RS +.PP +When the output is sent to standard output, the value can be +.IR 16 ", " 24 " or " 32 , +which denotes the sample size in bits. Little Endian only. +.RE +.TP +.B \-b : +Specify internal stream and output buffer sizes in kilobytes. Default is 2048:3446. +.TP +.B \-c ,... +Restrict codecs to those specified, otherwise load all available codecs. Use +.B squeezelite -? +to obtain the list of codecs built into \fBsqueezelite\fR. +.TP +.B \-C +Close the output device after +.B +seconds of the player being idle; the default is to always keep the device open +as long as the payer is "on". +.TP +.B \-d = +Set logging level. Categories are: +.IR all ", " slimproto ", " stream ", " decode ", " output " or " ir . +Levels can be: +.IR info ", " debug " or " sdebug . +The option can be repeated to set different log levels for different categories. +.TP +.B \-e ,... +Explicitly exclude native support of one or more codecs. See also +.BR \-c , +above. +.TP +.B \-f +Send logging output to a log file instead of standard output or standard error. +.TP +.B \-G : +Specify the BCM GPIO# to use for Amp Power Relay and if the output +should be Active High or Low. This cannot be used with the \fB-S\fR option. +.TP +.B \-i [] +Enable LIRC remote control support. If the optional +.B +is not provided, +.I ~/.lircrc +is used instead. +.TP +.B \-m +Override the player's MAC address. The format must be colon-delimited +hexadecimal, for example: ab:cd:ef:12:34:56. This is usually automatically +detected, and should not need to be provided in most circumstances. +.TP +.B \-M +Override the player's hardware model name. The default value is +.IR SqueezeLite . +.TP +.B \-n +Set the player name. This name is used by the Logitech Media Server to refer to +the player by name. This option is mututally exclusive with +.BR \-N . +.TP +.B \-N +Allow the server to set the player's name. The player name is stored in the file +pointed to by +.B +so that it can persist between restarts. This option is mututally exclusive with +.BR \-n . +.TP +.B \-O +Specify mixer device, defaults to \fB\fR. +\. +.TP +.B \-p +Set real time priority of output thread (1-99; default +.IR 45 ). +Not applicable when using PortAudio. +.TP +.B \-P +Write the process ID (PID) number to the given +.BR . +This may be useful when running \fBsqueezelite\fR as a daemon. +.TP +.B \-r [:] +Specify sample rates supported by the output device; this is required if the +output device is switched off when \fBsqueezelite\fR is started. The format is +either a single maximum sample rate, a range of sample rates in the format +.IR - , +or a comma-separated list of available rates. Delay is an optional time to wait +when switching sample rates between tracks, in milliseconds. +.TP +.B \-S +Absolute path to script to launch on power commands from LMS. This +cannot be used with the \fB-G\fR option. +.TP +.B \-u|-R [params] +Enable upsampling of played audio. The argument is optional; see +.B RESAMPLING +(below) for more information. The options +.BR -u " and " -R +are synonymous. +.TP +.B \-D [delay] +Output device supports DSD over PCM (DoP). DSD streams will be converted to DoP +before output. If this option is not supplied, DSD streams will be converted to +PCM and resampled, so they can be played on a PCM DAC. Delay is an optional time +to wait when switching between PCM and DoP between tracks, in milliseconds. +.TP +.B \-v +Enable visualiser support. This creates a shared memory segment that contains +some of the audio being played, so that an external visualiser can read and +process this to create visualisations. +.TP +.B \-W +Read wave and aiff format from header, ignoring server parameters. +.TP +.B \-L +List available volume controls for the output device. Only applicable when +using ALSA output. +.TP +.B \-U +Unmute the given ALSA +.B +at daemon startup and set it to full volume. Use software volume adjustment for +playback. This option is mutually exclusive with the \fB\-V\fR option. Only +applicable when using ALSA output. +.TP +.B \-V +Use the given ALSA +.B +for volume adjustment during playback. This prevents the use of software volume +control within \fBsqueezelite\fR. This option is mutually exclusive with the +\fB\-U\fR option. If neither \fB\-U\fR nor \fB\-V\fR options are provided, +no ALSA controls are adjusted while running \fBsqueezelite\fR and software +volume control is used instead. Only applicable when using ALSA output. +.TP +.B \-X +Use linear volume adjustments instead of in terms of dB (only for +hardware volume control). +.TP +.B \-z +Cause \fBsqueezelite\fR to run as a daemon. That is, it detaches itself from the +terminal and runs in the background. +.TP +.B \-Z +Report rate to server in helo as the maximum sample rate we can support. +.TP +.B \-t +Display version and license information. +.SH RESAMPLING +Audio can be resampled or upsampled before being sent to the output device. This +can be enabled simply by passing the \fB\-u\fR option to \fBsqueezelite\fR, but +further configuration can be given as an argument to the option. +.PP +Resampling is performed using the SoX Resampler library; the documentation for +that library and the SoX \fIrate\fR effect many be helpful when configuring +upsampling for \fBsqueezelite\fR. +.PP +The format of the argument is +.B :::::: +.SS recipe +This part of the argument string is made up of a number of single-character +flags: \fB[v|h|m|l|q][L|I|M][s][E|X]\fR. The default value is \fBhL\fR. +.TP +.IR v ", " h ", " m ", " l " or " q +are mutually exclusive and correspond to very high, high, medium, low or quick +quality. +.TP +.IR L ", " I " or " M +correspond to linear, intermediate or minimum phase. +.TP +.IR s +changes resampling bandwidth from the default 95% (based on the 3dB point) to +99%. +.TP +.IR E +exception - avoids resampling if the output device supports the playback sample +rate natively. +.TP +.IR X +resamples to the maximum sample rate for the output device ("asynchronous" +resampling). +.TP +.B Examples +.B \-u vLs +would use very high quality setting, linear phase filter and steep cut-off. +.br +.B \-u hM +would specify high quality, with the minimum phase filter. +.br +.B \-u hMX +would specify high quality, with the minimum phase filter and async upsampling +to max device rate. +.SS flags +The second optional argument to \fB\-u\fR allows the user to specify the +following arguments (taken from the \fIsoxr.h\fR header file), in hex: +.sp +#define SOXR_ROLLOFF_SMALL 0u /* <= 0.01 dB */ +.br +#define SOXR_ROLLOFF_MEDIUM 1u /* <= 0.35 dB */ +.br +#define SOXR_ROLLOFF_NONE 2u /* For Chebyshev bandwidth. */ +.sp +#define SOXR_MAINTAIN_3DB_PT 4u /* Reserved for internal use. */ +.br +#define SOXR_HI_PREC_CLOCK 8u /* Increase 'irrational' ratio accuracy. */ +.br +#define SOXR_DOUBLE_PRECISION 16u /* Use D.P. calcs even if precision <= 20. */ +.br +#define SOXR_VR 32u /* Experimental, variable-rate resampling. */ +.TP +.B Examples +.B \-u :2 +would specify \fBSOXR_ROLLOFF_NONE\fR. +.sp +\fBNB:\fR In the example above the first option, \fB\fR, has not been +specified so would default to \fBhL\fR. Therefore, specifying \fB\-u :2\fR is +equivalent to having specified \fB\-u hL:2\fR. +.SS attenuation +Internally, data is passed to the SoX resample process as 32 bit integers and +output from the SoX resample process as 32 bit integers. Why does this matter? +There is the possibility that integer samples, once resampled may be clipped +(i.e. exceed the maximum value). By default, if you do not specify an +\fBattenuation\fR value, it will default to \-1db. A value of \fI0\fR on the +command line, i.e. \fB-u ::0\fR will disable the default \-1db attenuation being +applied. +.sp +\fBNB:\fR Clipped samples will be logged. Keep an eye on the log file. +.TP +.B Examples +.B \-u ::6 +specifies to apply \-6db (ie. halve the volume) prior to the resampling process. +.SS precision +The internal 'bit' precision used in the re-sampling calculations (ie. quality). +.sp +\fBNB:\fR HQ = 20, VHQ = 28. +.TP +.B Examples +.B \-u :::28 +specifies 28-bit precision. +.SS passband_end +A percentage value between 0 and 100, where 100 is the Nyquist frequency. The +default if not explicitly set is \fI91.3\fR. +.TP +.B Examples +.B \-u ::::98 +specifies passband ends at 98 percent of the Nyquist frequency. +.SS stopband_start +A percentage value between 0 and 100, where 100 is the Nyquist frequency. The +default if not explicitly set is \fI100\fR. +.TP +.B Examples +.B \-u :::::100 +specifies that the stopband starts at the Nyquist frequency. +.SS phase_response +A value between 0-100, where \fI0\fR is equivalent to the recipe \fIM\fR flag +for minimum phase, \fI25\fR is equivalent to the recipe \fII\fR flag for +intermediate phase and \fI50\fR is equivalent to the recipe \fIL\fR flag for +linear phase. +.TP +.B Examples +.B \-u ::::::50 +specifies linear phase. +.SH SEE ALSO +.TP +http://wiki.slimdevices.com/index.php/Squeezelite +.TP +http://wiki.slimdevices.com/index.php/Logitech_Media_Server +.TP +sox(1) +for further information about resampling. diff --git a/dop.c b/dop.c index b498202..8a300ad 100644 --- a/dop.c +++ b/dop.c @@ -30,22 +30,22 @@ #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) -// check for 32 dop marker frames to see if this is dop in flac -// dop is always encoded in 24 bit samples with marker 0x0005xxxx or 0x00FAxxxx -bool is_flac_dop(u32_t *lptr, u32_t *rptr, frames_t frames) { +// check for 32 dop marker frames to see if this is a dop stream +// dop is always encoded in 24 bit samples with markers 0x05 or 0xFA in MSB +bool is_stream_dop(u8_t *lptr, u8_t *rptr, int step, frames_t frames) { int matched = 0; u32_t next = 0; while (frames--) { - if (((*lptr & 0x00FF0000) == 0x00050000 && (*rptr & 0x00FF0000) == 0x00050000) || - ((*lptr & 0x00FF0000) == 0x00FA0000 && (*rptr & 0x00FF0000) == 0x00FA0000)) { - if (*lptr >> 24 == next) { + if ((*lptr == 0x05 && *rptr == 0x05) || + (*lptr == 0xFA && *rptr == 0xFA)) { + if (*lptr == next) { matched++; - next = ( 0x05 + 0xFA ) - next; } else { - next = *lptr >> 24; + next = *lptr; matched = 1; } + next = ( 0x05 + 0xFA ) - next; } else { return false; } @@ -53,7 +53,7 @@ return true; } - ++lptr; ++rptr; + lptr+=step; rptr+=step; } return false; } @@ -65,38 +65,22 @@ if (!invert) { while (frames--) { u32_t scaled_marker = marker << 24; - *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; + *ptr = (*ptr & 0x00FFFF00) | scaled_marker; ++ptr; - *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; + *ptr = (*ptr & 0x00FFFF00) | scaled_marker; ++ptr; marker = ( 0x05 + 0xFA ) - marker; } } else { while (frames--) { u32_t scaled_marker = marker << 24; - *ptr = ((~(*ptr)) & 0x00FFFFFF) | scaled_marker; + *ptr = ((~(*ptr)) & 0x00FFFF00) | scaled_marker; ++ptr; - *ptr = ((~(*ptr)) & 0x00FFFFFF) | scaled_marker; + *ptr = ((~(*ptr)) & 0x00FFFF00) | scaled_marker; ++ptr; marker = ( 0x05 + 0xFA ) - marker; } } } -// fill silence buffer with 10101100 which represents dop silence -// leave marker zero it will be updated at output, leave lsb zero -void dop_silence_frames(u32_t *ptr, frames_t frames) { - while (frames--) { - *ptr++ = 0x00ACAC00; - *ptr++ = 0x00ACAC00; - } -} - -void dop_init(bool enable, unsigned delay) { - LOCK_O; - output.has_dop = enable; - output.dop_delay = delay; - UNLOCK_O; -} - #endif // DSD diff --git a/dsd.c b/dsd.c index 32d4795..b641fe8 100644 --- a/dsd.c +++ b/dsd.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -58,11 +59,11 @@ #define BLOCK 4096 // expected size of dsd block #define BLOCK_FRAMES BLOCK * BYTES_PER_FRAME -#define WRAP_BUF_SIZE 16 +#define WRAP_BUF_SIZE 32 // max 4 bytes per frame and 8 channels typedef enum { UNKNOWN=0, DSF, DSDIFF } dsd_type; -static bool dop = false; // local copy of output.has_dop to avoid holding output lock +static dsd_format outfmt = PCM; // local copy of output.dsdfmt to avoid holding output lock struct dsd { dsd_type type; @@ -211,8 +212,24 @@ unsigned bytes = _buf_used(streambuf); unsigned block_left = d->block_size; - - unsigned bytes_per_frame = dop ? 2 : 1; + unsigned padding = 0; + + unsigned bytes_per_frame; + switch (outfmt) { + case DSD_U32_LE: + case DSD_U32_BE: + bytes_per_frame = 4; + break; + case DSD_U16_LE: + case DSD_U16_BE: + case DOP: + case DOP_S24_LE: + case DOP_S24_3LE: + bytes_per_frame = 2; + break; + default: + bytes_per_frame = 1; + } if (bytes < d->block_size * d->channels) { LOG_INFO("stream too short"); // this can occur when scanning the track @@ -236,6 +253,22 @@ iptrr -= streambuf->size; } + // Remove zero padding from last block in case of inaccurate sample count + if ((_buf_used(streambuf) == d->block_size * d->channels) + && (d->sample_bytes > _buf_used(streambuf))) { + int i; + u8_t *ipl, *ipr; + for (i = d->block_size - 1; i > 0; i--) { + ipl = iptrl + i; + if (ipl >= streambuf->wrap) ipl -= streambuf->size; + ipr = iptrr + i; + if (ipr >= streambuf->wrap) ipr -= streambuf->size; + if (*ipl || *ipr) break; + padding++; + } + block_left -= padding; + } + bytes = min(block_left, min(streambuf->wrap - iptrl, streambuf->wrap - iptrr)); IF_DIRECT( @@ -249,12 +282,14 @@ frames = min(bytes, d->sample_bytes) / bytes_per_frame; if (frames == 0) { - if (dop && d->sample_bytes == 1 && bytes >= 2) { - // 1 byte left add a byte of silence and play - *(iptrl + 1) = *(iptrr + 1) = 0x69; + if (d->sample_bytes && bytes >= (2 * d->sample_bytes)) { + // byte(s) left fill frame with silence byte(s) and play + int i; + for (i = d->sample_bytes; i < bytes_per_frame; i++) + *(iptrl + i) = *(iptrr + i) = 0x69; frames = 1; } else { - // should not get here due to wrapping m/2 for dop should never result in 0 as header len is always even + // should not get here due to wrapping m/2 for dsd should never result in 0 as header len is always even LOG_INFO("frames got to zero"); return DECODE_COMPLETE; } @@ -266,7 +301,125 @@ count = frames; - if (dop) { + switch (outfmt) { + + case DSD_U32_LE: + case DSD_U32_BE: + + if (d->channels == 1) { + if (d->lsb_first) { + while (count--) { + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16 + | dsd2pcm_bitreverse[*(iptrl+2)] << 8 | dsd2pcm_bitreverse[*(iptrl+3)]; + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16 + | dsd2pcm_bitreverse[*(iptrl+2)] << 8 | dsd2pcm_bitreverse[*(iptrl+3)]; + iptrl += 4; + } + } else { + while (count--) { + *(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16 | *(iptrl+2) << 8 | *(iptrl+3); + *(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16 | *(iptrl+2) << 8 | *(iptrl+3); + iptrl += 4; + } + } + } else { + if (d->lsb_first) { + while (count--) { + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16 + | dsd2pcm_bitreverse[*(iptrl+2)] << 8 | dsd2pcm_bitreverse[*(iptrl+3)]; + *(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 24 | dsd2pcm_bitreverse[*(iptrr+1)] << 16 + | dsd2pcm_bitreverse[*(iptrr+2)] << 8 | dsd2pcm_bitreverse[*(iptrr+3)]; + iptrl += 4; + iptrr += 4; + } + } else { + while (count--) { + *(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16 | *(iptrl+2) << 8 | *(iptrl+3); + *(optr++) = *(iptrr) << 24 | *(iptrr+1) << 16 | *(iptrr+2) << 8 | *(iptrr+3); + iptrl += 4; + iptrr += 4; + } + } + } + + break; + + case DSD_U16_LE: + case DSD_U16_BE: + + if (d->channels == 1) { + if (d->lsb_first) { + while (count--) { + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16; + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16; + iptrl += 2; + } + } else { + while (count--) { + *(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16; + *(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16; + iptrl += 2; + } + } + } else { + if (d->lsb_first) { + while (count--) { + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16; + *(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 24 | dsd2pcm_bitreverse[*(iptrr+1)] << 16; + iptrl += 2; + iptrr += 2; + } + } else { + while (count--) { + *(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16; + *(optr++) = *(iptrr) << 24 | *(iptrr+1) << 16; + iptrl += 2; + iptrr += 2; + } + } + } + + break; + + case DSD_U8: + + if (d->channels == 1) { + if (d->lsb_first) { + while (count--) { + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24; + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24; + iptrl += 1; + } + } else { + while (count--) { + *(optr++) = *(iptrl) << 24; + *(optr++) = *(iptrl) << 24; + iptrl += 1; + } + } + } else { + if (d->lsb_first) { + while (count--) { + *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24; + *(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 24; + iptrl += 1; + iptrr += 1; + } + } else { + while (count--) { + *(optr++) = *(iptrl) << 24; + *(optr++) = *(iptrr) << 24; + iptrl += 1; + iptrr += 1; + } + } + } + + break; + + case DOP: + case DOP_S24_LE: + case DOP_S24_3LE: if (d->channels == 1) { if (d->lsb_first) { @@ -300,7 +453,9 @@ } } - } else { + break; + + case PCM: if (d->channels == 1) { float *iptrf = d->transfer[0]; @@ -329,6 +484,8 @@ } } + break; + } _buf_inc_readp(streambuf, bytes_read); @@ -353,6 +510,11 @@ LOG_SDEBUG("write %u frames", frames); } + if (padding) { + _buf_inc_readp(streambuf, padding); + LOG_INFO("Zero padding removed: %u bytes", padding); + } + // skip the other channel blocks // the right channel has already been read and is guarenteed to be in streambuf so can be skipped immediately if (d->channels > 1) { @@ -385,9 +547,19 @@ out = process.max_in_frames; ); - if (dop) { + switch (outfmt) { + case DSD_U32_LE: + case DSD_U32_BE: + bytes_per_frame = d->channels * 4; + break; + case DSD_U16_LE: + case DSD_U16_BE: + case DOP: + case DOP_S24_LE: + case DOP_S24_3LE: bytes_per_frame = d->channels * 2; - } else { + break; + default: bytes_per_frame = d->channels; out = min(out, BLOCK); } @@ -404,7 +576,7 @@ optr = (u32_t *)process.inbuf; ); - // handle wrap around end of streambuf and partial dop frame at end of stream + // handle wrap around end of streambuf and partial dsd frame at end of stream if (!frames && bytes < bytes_per_frame) { memset(tmp, 0x69, WRAP_BUF_SIZE); // 0x69 = dsd silence memcpy(tmp, streambuf->readp, bytes); @@ -420,7 +592,69 @@ count = frames; - if (dop) { + switch (outfmt) { + + case DSD_U32_LE: + case DSD_U32_BE: + + if (d->channels == 1) { + while (count--) { + *(optr++) = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3); + *(optr++) = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3); + iptr += bytes_per_frame; + } + } else { + while (count--) { + *(optr++) = *(iptr ) << 24 | *(iptr + d->channels) << 16 + | *(iptr + 2 * d->channels) << 8 | *(iptr + 3 * d->channels); + *(optr++) = *(iptr+1) << 24 | *(iptr + d->channels + 1) << 16 + | *(iptr + 2 * d->channels + 1) << 8 | *(iptr + 3 * d->channels + 1); + iptr += bytes_per_frame; + } + } + + break; + + case DSD_U16_LE: + case DSD_U16_BE: + + if (d->channels == 1) { + while (count--) { + *(optr++) = *(iptr) << 24 | *(iptr+1) << 16; + *(optr++) = *(iptr) << 24 | *(iptr+1) << 16; + iptr += bytes_per_frame; + } + } else { + while (count--) { + *(optr++) = *(iptr ) << 24 | *(iptr + d->channels) << 16; + *(optr++) = *(iptr+1) << 24 | *(iptr + d->channels + 1) << 16; + iptr += bytes_per_frame; + } + } + + break; + + case DSD_U8: + + if (d->channels == 1) { + while (count--) { + *(optr++) = *(iptr) << 24; + *(optr++) = *(iptr) << 24; + iptr += bytes_per_frame; + } + } else { + while (count--) { + *(optr++) = *(iptr ) << 24; + *(optr++) = *(iptr+1) << 24; + iptr += bytes_per_frame; + } + } + + break; + + case DOP: + case DOP_S24_LE: + case DOP_S24_3LE: if (d->channels == 1) { while (count--) { @@ -436,7 +670,9 @@ } } - } else { + break; + + case PCM: if (d->channels == 1) { float *iptrf = d->transfer[0]; @@ -465,6 +701,8 @@ } } + break; + } _buf_inc_readp(streambuf, bytes_read); @@ -491,6 +729,9 @@ static decode_state dsd_decode(void) { decode_state ret; + char *fmtstr; + + fmtstr = "None"; LOCK_S; @@ -527,25 +768,61 @@ LOG_INFO("setting track_start"); output.track_start = outputbuf->writep; - dop = output.has_dop; - - if (dop && d->sample_rate / 16 > output.supported_rates[0]) { - LOG_INFO("DOP sample rate too high for device - converting to PCM"); - dop = false; - } - - if (dop) { - LOG_INFO("DOP output"); - output.next_dop = true; + outfmt = output.dsdfmt; + + switch (outfmt) { + case DSD_U32_LE: + fmtstr = "DSD_U32_LE"; + output.next_sample_rate = d->sample_rate / 32; + break; + case DSD_U32_BE: + fmtstr = "DSD_U32_BE"; + output.next_sample_rate = d->sample_rate / 32; + break; + case DSD_U16_LE: + fmtstr = "DSD_U16_LE"; output.next_sample_rate = d->sample_rate / 16; - output.fade = FADE_INACTIVE; - } else { + break; + case DSD_U16_BE: + fmtstr = "DSD_U16_BE"; + output.next_sample_rate = d->sample_rate / 16; + break; + case DSD_U8: + fmtstr = "DSD_U8"; + output.next_sample_rate = d->sample_rate / 8; + break; + case DOP: + fmtstr = "DOP"; + output.next_sample_rate = d->sample_rate / 16; + break; + case DOP_S24_LE: + fmtstr = "DOP_S24_LE"; + output.next_sample_rate = d->sample_rate / 16; + break; + case DOP_S24_3LE: + fmtstr = "DOP_S24_3LE"; + output.next_sample_rate = d->sample_rate / 16; + break; + case PCM: + // PCM case after DSD rate check and possible fallback to PCM conversion + break; + } + + if (outfmt != PCM && output.next_sample_rate > output.supported_rates[0]) { + LOG_INFO("DSD sample rate too high for device - converting to PCM"); + outfmt = PCM; + } + + if (outfmt == PCM) { LOG_INFO("DSD to PCM output"); - output.next_dop = false; output.next_sample_rate = decode_newstream(d->sample_rate / 8, output.supported_rates); if (output.fade_mode) _checkfade(true); - } - + } else { + LOG_INFO("DSD%u stream, format: %s, rate: %uHz\n", d->sample_rate / 44100, fmtstr, output.next_sample_rate); + output.fade = FADE_INACTIVE; + } + + output.next_fmt = outfmt; decode.new_stream = false; UNLOCK_O; @@ -568,6 +845,13 @@ UNLOCK_S; return ret; +} + +void dsd_init(dsd_format format, unsigned delay) { + LOCK_O; + output.dsdfmt = format; + output.dsd_delay = delay; + UNLOCK_O; } static void dsd_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { @@ -625,4 +909,22 @@ return &ret; } +// invert polarity for frames in the output buffer +void dsd_invert(u32_t *ptr, frames_t frames) { + while (frames--) { + *ptr = ~(*ptr); + ++ptr; + *ptr = ~(*ptr); + ++ptr; + } +} + +// fill silence buffer with 10101100 which represents dsd silence +void dsd_silence_frames(u32_t *ptr, frames_t frames) { + while (frames--) { + *ptr++ = 0x69696969; + *ptr++ = 0x69696969; + } +} + #endif // DSD diff --git a/faad.c b/faad.c index 96d33ca..8a190ff 100644 --- a/faad.c +++ b/faad.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +22,12 @@ #include "squeezelite.h" #include + +#if BYTES_PER_FRAME == 4 +#define ALIGN(n) (n) +#else +#define ALIGN(n) (n << 8) +#endif #define WRAPBUF_LEN 2048 @@ -302,7 +309,13 @@ a->pos += bytes; a->consume = consume - bytes; break; + } else if (len > streambuf->size) { + // can't process an atom larger than streambuf! + LOG_ERROR("atom %s too large for buffer %u %u", type, len, streambuf->size); + return -1; } else { + // make sure there is 'len' contiguous space + _buf_unwrap(streambuf, len); break; } } @@ -313,8 +326,8 @@ static decode_state faad_decode(void) { size_t bytes_total; size_t bytes_wrap; - NeAACDecFrameInfo info; - s32_t *iptr; + static NeAACDecFrameInfo info; + ISAMPLE_T *iptr; bool endstream; frames_t frames; @@ -376,7 +389,7 @@ LOCK_O; LOG_INFO("setting track_start"); output.next_sample_rate = decode_newstream(samplerate, output.supported_rates); - IF_DSD( output.next_dop = false; ) + IF_DSD( output.next_fmt = PCM; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; @@ -399,7 +412,7 @@ if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) { // make a local copy of frames which may have wrapped round the end of streambuf - u8_t buf[WRAPBUF_LEN]; + static u8_t buf[WRAPBUF_LEN]; memcpy(buf, streambuf->readp, bytes_wrap); memcpy(buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap); @@ -490,29 +503,34 @@ while (frames > 0) { frames_t f; frames_t count; - s32_t *optr; + ISAMPLE_T *optr; IF_DIRECT( f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME; - optr = (s32_t *)outputbuf->writep; + optr = (ISAMPLE_T *)outputbuf->writep; ); IF_PROCESS( f = process.max_in_frames; - optr = (s32_t *)process.inbuf; + optr = (ISAMPLE_T *)process.inbuf; ); f = min(f, frames); count = f; if (info.channels == 2) { +#if BYTES_PER_FRAME == 4 + memcpy(optr, iptr, count * BYTES_PER_FRAME); + iptr += count * 2; +#else while (count--) { - *optr++ = *iptr++ << 8; - *optr++ = *iptr++ << 8; - } + *optr++ = ALIGN(*iptr++); + *optr++ = ALIGN(*iptr++); + } +#endif } else if (info.channels == 1) { while (count--) { - *optr++ = *iptr << 8; - *optr++ = *iptr++ << 8; + *optr++ = ALIGN(*iptr); + *optr++ = ALIGN(*iptr++); } } else { LOG_WARN("unsupported number of channels"); @@ -562,7 +580,12 @@ conf = NEAAC(a, GetCurrentConfiguration, a->hAac); +#if BYTES_PER_FRAME == 4 + conf->outputFormat = FAAD_FMT_16BIT; +#else conf->outputFormat = FAAD_FMT_24BIT; +#endif + conf->defSampleRate = 44100; conf->downMatrix = 1; if (!NEAAC(a, SetConfiguration, a->hAac, conf)) { diff --git a/ffmpeg.c b/ffmpeg.c index 279f31d..c7378c4 100644 --- a/ffmpeg.c +++ b/ffmpeg.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,6 +43,7 @@ AVInputFormat *input_format; AVFormatContext *formatC; AVCodecContext *codecC; + AVCodecParameters *codecP; AVFrame *frame; AVPacket *avpkt; unsigned mmsh_bytes_left; @@ -52,9 +54,17 @@ unsigned (* avcodec_version)(void); AVCodec * (* avcodec_find_decoder)(int); int attribute_align_arg (* avcodec_open2)(AVCodecContext *, const AVCodec *, AVDictionary **); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) + AVFrame * (* av_frame_alloc)(void); + void (* av_frame_free)(AVFrame **); +#else AVFrame * (* avcodec_alloc_frame)(void); void (* avcodec_free_frame)(AVFrame **); +#endif int attribute_align_arg (* avcodec_decode_audio4)(AVCodecContext *, AVFrame *, int *, const AVPacket *); + AVCodecContext * (* avcodec_alloc_context3)(const AVCodec *); + void (* avcodec_free_context)(AVCodecContext **); + int (* avcodec_parameters_to_context)(AVCodecContext *, const AVCodecParameters *); // ffmpeg symbols to be dynamically loaded from libavformat unsigned (* avformat_version)(void); AVFormatContext * (* avformat_alloc_context)(void); @@ -64,7 +74,11 @@ AVIOContext * (* avio_alloc_context)(unsigned char *, int, int, void *, int (*read_packet)(void *, uint8_t *, int), int (*write_packet)(void *, uint8_t *, int), int64_t (*seek)(void *, int64_t, int)); void (* av_init_packet)(AVPacket *); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,24,102) + void (* av_packet_unref)(AVPacket *); +#else void (* av_free_packet)(AVPacket *); +#endif int (* av_read_frame)(AVFormatContext *, AVPacket *); AVInputFormat * (* av_find_input_format)(const char *); void (* av_register_all)(void); @@ -119,7 +133,7 @@ // our own version of useful error function not included in earlier ffmpeg versions -static char *av__err2str(errnum) { +static char *av__err2str(int errnum) { static char buf[64]; AV(ff, strerror, errnum, buf, 64); return buf; @@ -157,7 +171,7 @@ } static int _read_data(void *opaque, u8_t *buffer, int buf_size) { - size_t bytes; + unsigned int bytes; LOCK_S; @@ -264,7 +278,7 @@ ff->mmsh_bytes_left = ff->mmsh_bytes_pad = ff->mmsh_packet_len = 0; if (!ff->readbuf) { - ff->readbuf = AV(ff, malloc, READ_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); + ff->readbuf = AV(ff, malloc, READ_SIZE + AV_INPUT_BUFFER_PADDING_SIZE); } avio = AVIO(ff, alloc_context, ff->readbuf, READ_SIZE, 0, NULL, _read_data, NULL, NULL); @@ -294,16 +308,16 @@ } if (ff->wma && ff->wma_playstream < ff->formatC->nb_streams) { - if (ff->formatC->streams[ff->wma_playstream]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + if (ff->formatC->streams[ff->wma_playstream]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { LOG_INFO("using wma stream sent from server: %i", ff->wma_playstream); audio_stream = ff->wma_playstream; } } if (audio_stream == -1) { - int i; + unsigned int i; for (i = 0; i < ff->formatC->nb_streams; ++i) { - if (ff->formatC->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + if (ff->formatC->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream = i; LOG_INFO("found stream: %i", i); break; @@ -318,13 +332,30 @@ av_stream = ff->formatC->streams[audio_stream]; - ff->codecC = av_stream->codec; - - codec = AVCODEC(ff, find_decoder, ff->codecC->codec_id); + ff->codecC = AVCODEC (ff, alloc_context3, NULL); + if ( ff->codecC == NULL ) { + LOG_ERROR("can't allocate avctx"); + return DECODE_ERROR; + } + + ff->codecP = av_stream->codecpar; + + if ( (AVCODEC(ff, parameters_to_context, ff->codecC, ff->codecP) ) < 0) { + AVCODEC(ff, free_context, &ff->codecC); + + LOG_ERROR("can't initialize avctx"); + return DECODE_ERROR; + } + + codec = AVCODEC(ff, find_decoder, ff->codecP->codec_id); AVCODEC(ff, open2, ff->codecC, codec, NULL); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) + ff->frame = AV(ff, frame_alloc); +#else ff->frame = AVCODEC(ff, alloc_frame); +#endif ff->avpkt = AV(ff, malloc, sizeof(AVPacket)); if (ff->avpkt == NULL) { @@ -338,8 +369,8 @@ LOCK_O; LOG_INFO("setting track_start"); - output.next_sample_rate = decode_newstream(ff->codecC->sample_rate, output.supported_rates); - IF_DSD( output.next_dop = false; ) + output.next_sample_rate = decode_newstream(ff->codecP->sample_rate, output.supported_rates); + IF_DSD( output.next_fmt = PCM; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; @@ -375,7 +406,7 @@ len = AVCODEC(ff, decode_audio4, ff->codecC, ff->frame, &got_frame, &pkt_c); if (len < 0) { LOG_ERROR("avcodec_decode_audio4 error: %i %s", len, av__err2str(len)); - return DECODE_RUNNING; + break; // exit loop, free the packet, and continue decoding } pkt_c.data += len; @@ -505,14 +536,30 @@ } } +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,24,102) + AV(ff, packet_unref, ff->avpkt); +#else AV(ff, free_packet, ff->avpkt); +#endif return DECODE_RUNNING; } static void _free_ff_data(void) { + if (ff->codecC) { + AVCODEC(ff, free_context, &ff->codecC); + ff->codecC = NULL; + } + if (ff->formatC) { - if (ff->formatC->pb) AV(ff, freep, &ff->formatC->pb); + if (ff->formatC->pb) { + // per ffmpeg docs, the buffer originally pointed to by ff->readbuf may be dynamically freed and reallocated behind the scenes, so this is the one that must be freed + // otherwise, a double free can occur (seen by valgrind), resulting in e.g. SIGILL + AV(ff, freep, &ff->formatC->pb->buffer); + ff->readbuf = NULL; + AV(ff, freep, &ff->formatC->pb); + } + AVFORMAT(ff, free_context, ff->formatC); ff->formatC = NULL; } @@ -520,9 +567,17 @@ if (ff->frame) { // ffmpeg version dependant free function #if !LINKALL + #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) + ff->av_frame_free ? AV(ff, frame_free, &ff->frame) : AV(ff, freep, &ff->frame); + #else ff->avcodec_free_frame ? AVCODEC(ff, free_frame, &ff->frame) : AV(ff, freep, &ff->frame); + #endif #elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54,28,0) + #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) + AV(ff, frame_free, &ff->frame); + #else AVCODEC(ff, free_frame, &ff->frame); + #endif #else AV(ff, freep, &ff->frame); #endif @@ -530,7 +585,11 @@ } if (ff->avpkt) { +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,24,102) + AV(ff, packet_unref, ff->avpkt); +#else AV(ff, free_packet, ff->avpkt); +#endif AV(ff, freep, &ff->avpkt); ff->avpkt = NULL; } @@ -568,11 +627,6 @@ static void ff_close(void) { _free_ff_data(); - - if (ff->readbuf) { - AV(ff, freep, &ff->readbuf); - ff->readbuf = NULL; - } } static bool load_ff() { @@ -607,11 +661,23 @@ ff->avcodec_version = dlsym(handle_codec, "avcodec_version"); ff->avcodec_find_decoder = dlsym(handle_codec, "avcodec_find_decoder"); ff->avcodec_open2 = dlsym(handle_codec, "avcodec_open2"); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) + ff->av_frame_alloc = dlsym(handle_codec, "av_frame_alloc"); + ff->av_frame_free = dlsym(handle_codec, "av_frame_free"); +#else ff->avcodec_alloc_frame = dlsym(handle_codec, "avcodec_alloc_frame"); ff->avcodec_free_frame = dlsym(handle_codec, "avcodec_free_frame"); +#endif ff->avcodec_decode_audio4 = dlsym(handle_codec, "avcodec_decode_audio4"); + ff->avcodec_alloc_context3 = dlsym(handle_format, "avcodec_alloc_context3"); + ff->avcodec_free_context = dlsym(handle_format, "avcodec_free_context"); + ff->avcodec_parameters_to_context = dlsym(handle_format, "avcodec_parameters_to_context"); ff->av_init_packet = dlsym(handle_codec, "av_init_packet"); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57,24,102) + ff->av_packet_unref = dlsym(handle_codec, "av_packet_unref"); +#else ff->av_free_packet = dlsym(handle_codec, "av_free_packet"); +#endif if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); @@ -704,7 +770,7 @@ static struct codec ret = { 'w', // id - "wma,wmap,wmal", // types + "wma,wmap", // types READ_SIZE, // min read WRITE_SIZE, // min space ff_open_wma, // open @@ -712,7 +778,7 @@ ff_decode, // decode }; - LOG_INFO("using ffmpeg to decode wma,wmap,wmal"); + LOG_INFO("using ffmpeg to decode wma,wmap"); return &ret; } diff --git a/flac.c b/flac.c index a5c7b3b..8475c04 100644 --- a/flac.c +++ b/flac.c @@ -22,8 +22,21 @@ #include +#if BYTES_PER_FRAME == 4 +#define ALIGN8(n) (n << 8) +#define ALIGN16(n) (n) +#define ALIGN24(n) (n >> 8) +#define ALIGN32(n) (n >> 16) +#else +#define ALIGN8(n) (n << 24) +#define ALIGN16(n) (n << 16) +#define ALIGN24(n) (n << 8) +#define ALIGN32(n) (n) +#endif + struct flac { FLAC__StreamDecoder *decoder; + u8_t container; #if !LINKALL // FLAC symbols to be dynamically loaded const char **FLAC__StreamDecoderErrorStatusString; @@ -43,6 +56,18 @@ FLAC__StreamDecoderErrorCallback error_callback, void *client_data ); + FLAC__StreamDecoderInitStatus (* FLAC__stream_decoder_init_ogg_stream)( + FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderReadCallback read_callback, + FLAC__StreamDecoderSeekCallback seek_callback, + FLAC__StreamDecoderTellCallback tell_callback, + FLAC__StreamDecoderLengthCallback length_callback, + FLAC__StreamDecoderEofCallback eof_callback, + FLAC__StreamDecoderWriteCallback write_callback, + FLAC__StreamDecoderMetadataCallback metadata_callback, + FLAC__StreamDecoderErrorCallback error_callback, + void *client_data + ); FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder); FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder); #endif @@ -118,14 +143,22 @@ decode.new_stream = false; #if DSD - if (output.has_dop && bits_per_sample == 24 && is_flac_dop((u32_t *)lptr, (u32_t *)rptr, frames)) { +#if SL_LITTLE_ENDIAN +#define MARKER_OFFSET 2 +#else +#define MARKER_OFFSET 1 +#endif + if (bits_per_sample == 24 && is_stream_dop(((u8_t *)lptr) + MARKER_OFFSET, ((u8_t *)rptr) + MARKER_OFFSET, 4, frames)) { LOG_INFO("file contains DOP"); - output.next_dop = true; + if (output.dsdfmt == DOP_S24_LE || output.dsdfmt == DOP_S24_3LE) + output.next_fmt = output.dsdfmt; + else + output.next_fmt = DOP; output.next_sample_rate = frame->header.sample_rate; output.fade = FADE_INACTIVE; } else { output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates); - output.next_dop = false; + output.next_fmt = PCM; if (output.fade_mode) _checkfade(true); } #else @@ -141,14 +174,14 @@ while (frames > 0) { frames_t f; frames_t count; - s32_t *optr; + ISAMPLE_T *optr; IF_DIRECT( - optr = (s32_t *)outputbuf->writep; + optr = (ISAMPLE_T *)outputbuf->writep; f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; ); IF_PROCESS( - optr = (s32_t *)process.inbuf; + optr = (ISAMPLE_T *)process.inbuf; f = process.max_in_frames; ); @@ -158,23 +191,23 @@ if (bits_per_sample == 8) { while (count--) { - *optr++ = *lptr++ << 24; - *optr++ = *rptr++ << 24; + *optr++ = ALIGN8(*lptr++); + *optr++ = ALIGN8(*rptr++); } } else if (bits_per_sample == 16) { while (count--) { - *optr++ = *lptr++ << 16; - *optr++ = *rptr++ << 16; + *optr++ = ALIGN16(*lptr++); + *optr++ = ALIGN16(*rptr++); } } else if (bits_per_sample == 24) { while (count--) { - *optr++ = *lptr++ << 8; - *optr++ = *rptr++ << 8; + *optr++ = ALIGN24(*lptr++); + *optr++ = ALIGN24(*rptr++); } } else if (bits_per_sample == 32) { while (count--) { - *optr++ = *lptr++; - *optr++ = *rptr++; + *optr++ = ALIGN32(*lptr++); + *optr++ = ALIGN32(*rptr++); } } else { LOG_ERROR("unsupported bits per sample: %u", bits_per_sample); @@ -200,18 +233,30 @@ LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderErrorStatusString)[status]); } +static void flac_close(void) { + FLAC(f, stream_decoder_delete, f->decoder); + f->decoder = NULL; +} + static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) { + if ( f->decoder && f->container != sample_size ) { + flac_close(); + } + + f->container = sample_size; + if (f->decoder) { FLAC(f, stream_decoder_reset, f->decoder); } else { f->decoder = FLAC(f, stream_decoder_new); } - FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL); -} - -static void flac_close(void) { - FLAC(f, stream_decoder_delete, f->decoder); - f->decoder = NULL; + + if ( f->container == 'o' ) { + LOG_INFO("ogg/flac container - using init_ogg_stream"); + FLAC(f, stream_decoder_init_ogg_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL); + } else { + FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL); + } } static decode_state flac_decode(void) { @@ -247,6 +292,7 @@ f->FLAC__stream_decoder_reset = dlsym(handle, "FLAC__stream_decoder_reset"); f->FLAC__stream_decoder_delete = dlsym(handle, "FLAC__stream_decoder_delete"); f->FLAC__stream_decoder_init_stream = dlsym(handle, "FLAC__stream_decoder_init_stream"); + f->FLAC__stream_decoder_init_ogg_stream = dlsym(handle, "FLAC__stream_decoder_init_ogg_stream"); f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single"); f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state"); @@ -264,9 +310,9 @@ struct codec *register_flac(void) { static struct codec ret = { 'f', // id - "flc", // types - 8192, // min read - 102400, // min space + "ogf,flc", // types + 16384, // min read + 204800, // min space flac_open, // open flac_close, // close flac_decode, // decode @@ -283,6 +329,6 @@ return NULL; } - LOG_INFO("using flac to decode flc"); + LOG_INFO("using flac to decode ogf,flc"); return &ret; } diff --git a/gpio.c b/gpio.c new file mode 100644 index 0000000..e9943e5 --- /dev/null +++ b/gpio.c @@ -0,0 +1,105 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * gpio.c (c) Paul Hermann, 2015-2017 under the same license terms + * -Control of Raspberry pi GPIO for amplifier power + * -Launch script on power status change from LMS + */ + +#if GPIO + +#include "squeezelite.h" +#include +#include +#include + +static int gpio_state = -1; +static int initialized = -1; +static int power_state = -1; + +void relay( int state) { +#ifdef RPI + gpio_state = state; + + // Set up gpio using BCM Pin #'s + if (initialized == -1){ + if ( gpioInitialise() == 0 ){ + initialized = 1; + } + } + if ( initialized == 1){ + gpioSetMode (gpio_pin, PI_OUTPUT); + } + + if(gpio_state == 1) + gpioWrite(gpio_pin, PI_HIGH^gpio_active_low); + else if(gpio_state == 0) + gpioWrite(gpio_pin, PI_LOW^gpio_active_low); + // Done! +#endif +} + +char *cmdline; +int argloc; + +void relay_script( int state) { + gpio_state = state; + int err; + + // Call script with init parameter + if (initialized == -1){ + int strsize = strlen(power_script); + cmdline = (char*) malloc(strsize+3); + argloc = strsize + 1; + + strcpy(cmdline, power_script); + strcat(cmdline, " 2"); + if ((err = system(cmdline)) != 0){ + fprintf (stderr, "%s exit status = %d\n", cmdline, err); + } + else{ + initialized = 1; + } + } + + // Call Script to turn on or off on = 1, off = 0 + // Checks current status to avoid calling script excessivly on track changes where alsa re-inits. + + if( (gpio_state == 1) && (power_state != 1)){ + cmdline[argloc] = '1'; + if ((err = system(cmdline)) != 0){ + fprintf (stderr, "%s exit status = %d\n", cmdline, err); + } + else { + power_state = 1; + } + } + else if( (gpio_state == 0) && (power_state != 0)){ + cmdline[argloc] = '0'; + if ((err = system(cmdline)) != 0){ + fprintf (stderr, "%s exit status = %d\n", cmdline, err); + } + else { + power_state = 0; + } + } +// Done! +} + +#endif // GPIO diff --git a/ir.c b/ir.c index 2355ee4..2a09d47 100644 --- a/ir.c +++ b/ir.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,11 +21,19 @@ // ir thread - linux only +#if IR + +#include + +/* Avoid name conflicts from syslog.h inclusion in lirc client header */ +#ifdef LOG_DEBUG +#undef LOG_DEBUG +#endif +#ifdef LOG_INFO +#undef LOG_INFO +#endif + #include "squeezelite.h" - -#if IR - -#include #define LIRC_CLIENT_ID "squeezelite" @@ -60,7 +69,7 @@ #define LIRC(h, fn, ...) (h)->lirc_##fn(__VA_ARGS__) #endif -// cmds based on entires in Slim_Device_Remote.ir +// cmds based on entries in Slim_Device_Remote.ir // these may appear as config entries in .lircrc files static struct { char *cmd; @@ -76,6 +85,12 @@ { "muting", 0x7689c43b }, { "power_on", 0x76898f70 }, { "power_off",0x76898778 }, + { "preset_1", 0x76898a75 }, + { "preset_2", 0x76894ab5 }, + { "preset_3", 0x7689ca35 }, + { "preset_4", 0x76892ad5 }, + { "preset_5", 0x7689aa55 }, + { "preset_6", 0x76896a95 }, { NULL, 0 }, }; @@ -87,14 +102,41 @@ } keymap[] = { { "KEY_VOLUMEDOWN", 0x768900ff, true }, { "KEY_VOLUMEUP", 0x7689807f, true }, - { "KEY_PREVIOUS", 0x7689c03f, false }, - { "KEY_REWIND", 0x7689c03f, false }, - { "KEY_NEXT", 0x7689a05f, false }, - { "KEY_FORWARD", 0x7689a05f, false }, + { "KEY_PREVIOUS", 0x7689c03f, true }, + { "KEY_REWIND", 0x7689c03f, true }, + { "KEY_NEXT", 0x7689a05f, true }, + { "KEY_FORWARD", 0x7689a05f, true }, { "KEY_PAUSE", 0x768920df, true }, { "KEY_PLAY", 0x768910ef, false }, { "KEY_POWER", 0x768940bf, false }, { "KEY_MUTE", 0x7689c43b, false }, + { "KEY_0", 0x76899867, true }, + { "KEY_1", 0x7689f00f, true }, + { "KEY_2", 0x768908f7, true }, + { "KEY_3", 0x76898877, true }, + { "KEY_4", 0x768948b7, true }, + { "KEY_5", 0x7689c837, true }, + { "KEY_6", 0x768928d7, true }, + { "KEY_7", 0x7689a857, true }, + { "KEY_8", 0x76896897, true }, + { "KEY_9", 0x7689e817, true }, + { "KEY_FAVORITES", 0x768918e7, false }, + { "KEY_FAVORITES", 0x7689e21d, false }, + { "KEY_SEARCH", 0x768958a7, false }, + { "KEY_SEARCH", 0x7689629d, false }, + { "KEY_SHUFFLE", 0x7689d827, false }, + { "KEY_SLEEP", 0x7689b847, false }, + { "KEY_INSERT", 0x7689609f, false }, // Add + { "KEY_UP", 0x7689e01f, true }, + { "KEY_LEFT", 0x7689906f, true }, + { "KEY_RIGHT", 0x7689d02f, true }, + { "KEY_DOWN", 0x7689b04f, true }, + { "KEY_HOME", 0x768922dd, false }, + { "KEY_MEDIA_REPEAT", 0x768938c7, false }, +// { "KEY_TITLE", 0x76897887, false }, // Now Playing +// { "KEY_TITLE", 0x7689a25d, false }, // Now Playing +// { "KEY_TEXT", 0x7689f807, false }, // Size +// { "KEY_BRIGHTNESS_CYCLE", 0x768904fb, false }, { NULL, 0 , false }, }; @@ -112,7 +154,8 @@ int i; for (i = 0; keymap[i].lirc; i++) { if (!strcmp(c, keymap[i].lirc)) { - if (keymap[i].repeat || !strcmp(r, "00")) { + // inputlirc issues "0", while LIRC uses "00" + if (keymap[i].repeat || !strcmp(r, "0") || !strcmp(r,"00")) { return keymap[i].code; } LOG_DEBUG("repeat suppressed"); @@ -214,9 +257,12 @@ fd = LIRC(i, init, LIRC_CLIENT_ID, 0); if (fd > 0) { - if (LIRC(i, readconfig,lircrc, &config, NULL) != 0) { + if (LIRC(i, readconfig, lircrc, &config, NULL) != 0) { LOG_WARN("error reading config: %s", lircrc); } + else { + LOG_DEBUG("loaded lircrc config: %s", lircrc); + } mutex_create(ir.mutex); diff --git a/mad.c b/mad.c index 30e2498..adfa6ea 100644 --- a/mad.c +++ b/mad.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -87,15 +88,18 @@ #endif // based on libmad minimad.c scale -static inline u32_t scale(mad_fixed_t sample) { +static inline ISAMPLE_T scale(mad_fixed_t sample) { sample += (1L << (MAD_F_FRACBITS - 24)); if (sample >= MAD_F_ONE) sample = MAD_F_ONE - 1; else if (sample < -MAD_F_ONE) sample = -MAD_F_ONE; - - return (s32_t)(sample >> (MAD_F_FRACBITS + 1 - 24)) << 8; +#if BYTES_PER_FRAME == 4 + return (ISAMPLE_T)((sample >> (MAD_F_FRACBITS + 1 - 24)) >> 8); +#else + return (ISAMPLE_T)((sample >> (MAD_F_FRACBITS + 1 - 24)) << 8); +#endif } // check for id3.2 tag at start of file - http://id3.org/id3v2.4.0-structure, return length @@ -219,7 +223,8 @@ decode_state ret; if (!eos && m->stream.error == MAD_ERROR_BUFLEN) { ret = DECODE_RUNNING; - } else if (eos && (m->stream.error == MAD_ERROR_BUFLEN || m->stream.error == MAD_ERROR_LOSTSYNC)) { + } else if (eos && (m->stream.error == MAD_ERROR_BUFLEN || m->stream.error == MAD_ERROR_LOSTSYNC + || m->stream.error == MAD_ERROR_BADBITRATE)) { ret = DECODE_COMPLETE; } else if (!MAD_RECOVERABLE(m->stream.error)) { LOG_INFO("mad_frame_decode error: %s - stopping decoder", MAD(m, stream_errorstr, &m->stream)); @@ -241,7 +246,7 @@ LOCK_O; LOG_INFO("setting track_start"); output.next_sample_rate = decode_newstream(m->synth.pcm.samplerate, output.supported_rates); - IF_DSD( output.next_dop = false; ) + IF_DSD( output.next_fmt = PCM; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; @@ -298,15 +303,15 @@ while (frames > 0) { size_t f, count; - s32_t *optr; + ISAMPLE_T *optr; IF_DIRECT( f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME); - optr = (s32_t *)outputbuf->writep; + optr = (ISAMPLE_T *)outputbuf->writep; ); IF_PROCESS( f = min(frames, process.max_in_frames - process.in_frames); - optr = (s32_t *)((u8_t *)process.inbuf + process.in_frames * BYTES_PER_FRAME); + optr = (ISAMPLE_T *)((u8_t *)process.inbuf + process.in_frames * BYTES_PER_FRAME); ); count = f; @@ -342,6 +347,12 @@ m->samples = 0; m->readbuf_len = 0; m->last_error = MAD_ERROR_NONE; + + // explicitly clear before calling mad_*_init (again) to free the malloc'd memory + MAD(m, stream_finish, &m->stream); + MAD(m, frame_finish, &m->frame); + mad_synth_finish(&m->synth); + MAD(m, stream_init, &m->stream); MAD(m, frame_init, &m->frame); MAD(m, synth_init, &m->synth); @@ -397,7 +408,7 @@ mad_decode, // decode }; - m = malloc(sizeof(struct mad)); + m = calloc(1, sizeof(struct mad)); if (!m) { return NULL; } diff --git a/main.c b/main.c index 5f3d48c..4677ea7 100644 --- a/main.c +++ b/main.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2020, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,19 +17,34 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * + * Additions (c) Paul Hermann, 2015-2017 under the same license terms + * -Control of Raspberry pi GPIO for amplifier power + * -Launch script on power status change from LMS */ #include "squeezelite.h" #include -#define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith." - -#define CODECS_BASE "flac,pcm,mp3,ogg,aac" -#if FFMPEG +#define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith, 2015-2020 Ralph Irving." + +#define CODECS_BASE "flac,pcm,mp3,ogg" +#if NO_FAAD +#define CODECS_AAC "" +#else +#define CODECS_AAC ",aac" +#endif +#if ALAC +#define CODECS_FF ",alac" +#elif FFMPEG #define CODECS_FF ",wma,alac" #else #define CODECS_FF "" +#endif +#if OPUS +#define CODECS_OPUS ",ops" +#else +#define CODECS_OPUS "" #endif #if DSD #define CODECS_DSD ",dsd" @@ -37,7 +53,7 @@ #endif #define CODECS_MP3 " (mad,mpg for specific mp3 codec)" -#define CODECS CODECS_BASE CODECS_FF CODECS_DSD CODECS_MP3 +#define CODECS CODECS_BASE CODECS_AAC CODECS_FF CODECS_OPUS CODECS_DSD CODECS_MP3 static void usage(const char *argv0) { printf(TITLE " See -t for license terms\n" @@ -49,8 +65,12 @@ " -a :

::\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n" #endif #if PORTAUDIO -#if OSX +#if PA18API + " -a :\tSpecify output target 4 byte frames per buffer, number of buffers\n" +#elif OSX && !defined(OSXPPC) " -a :\t\tSpecify Portaudio params to open output device, l = target latency in ms, r = allow OSX to resample (0|1)\n" +#elif WIN + " -a :\t\tSpecify Portaudio params to open output device, l = target latency in ms, e = use exclusive mode for WASAPI (0|1)\n" #else " -a \t\tSpecify Portaudio params to open output device, l = target latency in ms\n" #endif @@ -58,12 +78,16 @@ " -a \t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n" " -b :\tSpecify internal Stream and Output buffer sizes in Kbytes\n" " -c ,\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n" + " \t\t\tCodecs reported to LMS in order listed, allowing codec priority refinement.\n" " -C \t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n" #if !IR " -d =\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n" #else " -d =\tSet logging level, logs: all|slimproto|stream|decode|output|ir, level: info|debug|sdebug\n" #endif +#if defined(GPIO) && defined(RPI) + " -G :\tSpecify the BCM GPIO# to use for Amp Power Relay and if the output should be Active High or Low\n" +#endif " -e ,\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n" " -f \t\tWrite debug to logfile\n" #if IR @@ -73,13 +97,17 @@ " -M \tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n" " -n \t\tSet the player name\n" " -N \t\tStore player name in filename to allow server defined name changes to be shared between servers (not supported with -n)\n" + " -W\t\t\tRead wave and aiff format from header, ignore server parameters\n" #if ALSA " -p \t\tSet real time priority of output thread (1-99)\n" #endif -#if LINUX || FREEBSD +#if LINUX || FREEBSD || SUN " -P \t\tStore the process id (PID) in filename\n" #endif " -r [:]\tSample rates supported, allows output to be off when squeezelite is started; rates = |-|,,; delay = optional delay switching rates in ms\n" +#if GPIO + " -S \tAbsolute path to script to launch on power commands from LMS\n" +#endif #if RESAMPLE " -R -u [params]\tResample, params = ::::::,\n" " \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n" @@ -91,24 +119,36 @@ " \t\t\t phase_response = 0-100 (0 = minimum / 50 = linear / 100 = maximum)\n" #endif #if DSD - " -D [delay]\t\tOutput device supports DSD over PCM (DoP), delay = optional delay switching between PCM and DoP in ms\n" +#if ALSA + " -D [delay][:format]\tOutput device supports DSD, delay = optional delay switching between PCM and DSD in ms\n" + " \t\t\t format = dop (default if not specified), u8, u16le, u16be, u32le or u32be.\n" +#else + " -D [delay]\t\tOutput device supports DSD over PCM (DoP), delay = optional delay switching between PCM and DoP in ms\n" +#endif #endif #if VISEXPORT - " -v \t\t\tVisualiser support\n" + " -v \t\t\tVisualizer support\n" #endif # if ALSA + " -O \tSpecify mixer device, defaults to 'output device'\n" " -L \t\t\tList volume controls for output device\n" " -U \t\tUnmute ALSA control and set to full volume (not supported with -V)\n" " -V \t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n" -#endif -#if LINUX || FREEBSD + " -X \t\t\tUse linear volume adjustments instead of in terms of dB (only for hardware volume control)\n" +#endif +#if LINUX || FREEBSD || SUN " -z \t\t\tDaemonize\n" +#endif +#if RESAMPLE + " -Z \t\tReport rate to server in helo as the maximum sample rate we can support\n" #endif " -t \t\t\tLicense terms\n" " -? \t\t\tDisplay this help text\n" "\n" "Build options:" -#if LINUX +#if SUN + " SOLARIS" +#elif LINUX " LINUX" #endif #if WIN @@ -117,6 +157,9 @@ #if OSX " OSX" #endif +#if OSXPPC + "PPC" +#endif #if FREEBSD " FREEBSD" #endif @@ -125,6 +168,12 @@ #endif #if PORTAUDIO " PORTAUDIO" +#if PA18API + "18" +#endif +#endif +#if PULSEAUDIO + " PULSEAUDIO" #endif #if EVENTFD " EVENTFD" @@ -142,8 +191,16 @@ " RESAMPLE" #endif #endif -#if FFMPEG +#if ALAC + " ALAC" +#elif FFMPEG " FFMPEG" +#endif +#if OPUS + " OPUS" +#endif +#if NO_FAAD + " NO_FAAD" #endif #if VISEXPORT " VISEXPORT" @@ -151,8 +208,20 @@ #if IR " IR" #endif +#if GPIO + " GPIO" +#endif +#if RPI + " RPI" +#endif #if DSD " DSD" +#endif +#if USE_SSL + " SSL" +#endif +#if NO_SSLSYM + " NO_SSLSYM" #endif #if LINKALL " LINKALL" @@ -172,11 +241,37 @@ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n\n" "You should have received a copy of the GNU General Public License\n" - "along with this program. If not, see .\n\n" + "along with this program. If not, see .\n" + + "\nThe Squeezelite source code is available on github.\n" + "\n" + + "\nThe source and patches for bundled 3rd party libraries can be found on\n" + "SourceForge. \n" #if DSD - "Contains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n" - "is subject to its own license.\n\n" -#endif + "\nContains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n" + "is subject to its own license.\n" + "\nContains the Daphile Project full dsd patch Copyright 2013-2017 Daphile,\n" + "which is subject to its own license.\n" +#endif + "\nOption to allow server side upsampling for PCM streams (-W) from\n" + "squeezelite-R2 (c) Marco Curti 2015, marcoc1712@gmail.com.\n" +#if RPI + "\nContains minimal GPIO Interface .\n" +#endif +#if FFMPEG + "\nThis software uses libraries from the FFmpeg project under\n" + "the LGPLv2.1 and its source can be downloaded from\n" + "\n" +#endif +#if OPUS + "\nOpus decoder support (c) Philippe 2018-2019, philippe_44@outlook.com\n" +#endif +#if ALAC + "\nContains Apple Lossless (ALAC) decoder. Apache License Version 2.0\n" + "Apple ALAC decoder support (c) Philippe 2018-2019, philippe_44@outlook.com\n" +#endif + "\n" ); } @@ -195,6 +290,8 @@ char *name = NULL; char *namefile = NULL; char *modelname = NULL; + extern bool pcm_check_header; + extern bool user_rates; char *logfile = NULL; u8_t mac[6]; unsigned stream_buf_size = STREAMBUF_SIZE; @@ -204,19 +301,21 @@ char *resample = NULL; char *output_params = NULL; unsigned idle = 0; -#if LINUX || FREEBSD +#if LINUX || FREEBSD || SUN bool daemonize = false; char *pidfile = NULL; FILE *pidfp = NULL; #endif #if ALSA unsigned rt_priority = OUTPUT_RT_PRIORITY; + char *mixer_device = output_device; char *output_mixer = NULL; bool output_mixer_unmute = false; + bool linear_volume = false; #endif #if DSD - bool dop = false; - unsigned dop_delay = 0; + unsigned dsd_delay = 0; + dsd_format dsd_outfmt = PCM; #endif #if VISEXPORT bool visexport = false; @@ -233,6 +332,8 @@ log_level log_ir = lWARN; #endif + int maxSampleRate = 0; + char *optarg = NULL; int optind = 1; int i; @@ -251,14 +352,21 @@ char *opt = argv[optind] + 1; if (strstr("oabcCdefmMnNpPrs" #if ALSA - "UV" + "UVO" +#endif +/* + * only allow '-Z ' override of maxSampleRate + * reported by client if built with the capability to resample! + */ +#if RESAMPLE + "Z" #endif , opt) && optind < argc - 1) { optarg = argv[optind + 1]; optind += 2; - } else if (strstr("ltz?" -#if ALSA - "L" + } else if (strstr("ltz?W" +#if ALSA + "LX" #endif #if RESAMPLE "uR" @@ -271,6 +379,12 @@ #endif #if IR "i" +#endif +#if defined(GPIO) && defined(RPI) + "G" +#endif +#if GPIO + "S" #endif , opt)) { @@ -285,6 +399,9 @@ switch (opt[0]) { case 'o': output_device = optarg; +#if ALSA + mixer_device = optarg; +#endif break; case 'a': output_params = optarg; @@ -395,6 +512,9 @@ if (dstr) { rate_delay = atoi(dstr); } + if (rates[0]) { + user_rates = true; + } } break; case 's': @@ -405,6 +525,9 @@ break; case 'N': namefile = optarg; + break; + case 'W': + pcm_check_header = true; break; #if ALSA case 'p': @@ -416,7 +539,7 @@ } break; #endif -#if LINUX || FREEBSD +#if LINUX || FREEBSD || SUN case 'P': pidfile = optarg; break; @@ -425,12 +548,6 @@ list_devices(); exit(0); break; -#if ALSA - case 'L': - list_mixers(output_device); - exit(0); - break; -#endif #if RESAMPLE case 'u': case 'R': @@ -440,12 +557,27 @@ resample = ""; } break; + case 'Z': + maxSampleRate = atoi(optarg); + break; #endif #if DSD case 'D': - dop = true; + dsd_outfmt = DOP; if (optind < argc && argv[optind] && argv[optind][0] != '-') { - dop_delay = atoi(argv[optind++]); + char *dstr = next_param(argv[optind++], ':'); + char *fstr = next_param(NULL, ':'); + dsd_delay = dstr ? atoi(dstr) : 0; + if (fstr) { + if (!strcmp(fstr, "dop")) dsd_outfmt = DOP; + if (!strcmp(fstr, "u8")) dsd_outfmt = DSD_U8; + if (!strcmp(fstr, "u16le")) dsd_outfmt = DSD_U16_LE; + if (!strcmp(fstr, "u32le")) dsd_outfmt = DSD_U32_LE; + if (!strcmp(fstr, "u16be")) dsd_outfmt = DSD_U16_BE; + if (!strcmp(fstr, "u32be")) dsd_outfmt = DSD_U32_BE; + if (!strcmp(fstr, "dop24")) dsd_outfmt = DOP_S24_LE; + if (!strcmp(fstr, "dop24_3")) dsd_outfmt = DOP_S24_3LE; + } } break; #endif @@ -455,6 +587,16 @@ break; #endif #if ALSA + case 'O': + mixer_device = optarg; + break; + case 'L': + list_mixers(mixer_device); + exit(0); + break; + case 'X': + linear_volume = true; + break; case 'U': output_mixer_unmute = true; case 'V': @@ -474,9 +616,71 @@ } break; #endif -#if LINUX || FREEBSD +#if defined(GPIO) && defined(RPI) + case 'G': + if (power_script != NULL){ + fprintf(stderr, "-G and -S options cannot be used together \n\n" ); + usage(argv[0]); + exit(1); + } + if (optind < argc && argv[optind] && argv[optind][0] != '-') { + char *gp = next_param(argv[optind++], ':'); + char *go = next_param (NULL, ':'); + gpio_pin = atoi(gp); + if (go != NULL){ + if ((strcmp(go, "H")==0)|(strcmp(go, "h")==0)){ + gpio_active_low=false; + }else if((strcmp(go, "L")==0)|(strcmp(go, "l")==0)){ + gpio_active_low=true; + }else{ + fprintf(stderr,"Must set output to be active High or Low i.e. -G18:H or -G18:L\n"); + usage(argv[0]); + exit(1); + } + }else{ + fprintf(stderr,"-G Option Error\n"); + usage(argv[0]); + exit(1); + } + gpio_active = true; + relay(0); + + } else { + fprintf(stderr, "Error in GPIO Pin assignment.\n"); + usage(argv[0]); + exit(1); + } + break; +#endif +#if GPIO + case 'S': + if (gpio_active){ + fprintf(stderr, "-G and -S options cannot be used together \n\n" ); + usage(argv[0]); + exit(1); + } + if (optind < argc && argv[optind] && argv[optind][0] != '-') { + power_script = argv[optind++]; + if( access( power_script, R_OK|X_OK ) == -1 ) { + // file doesn't exist + fprintf(stderr, "Script %s, not found\n\n", argv[optind-1]); + usage(argv[0]); + exit(1); + } + } else { + fprintf(stderr, "No Script Name Given.\n\n"); + usage(argv[0]); + exit(1); + } + relay_script(0); + break; +#endif +#if LINUX || FREEBSD || SUN case 'z': daemonize = true; +#if SUN + init_daemonize(); +#endif /* SUN */ break; #endif case 't': @@ -505,6 +709,10 @@ #endif #if defined(SIGHUP) signal(SIGHUP, sighandler); +#endif + +#if USE_SSL && !LINKALL && !NO_SSLSYM + ssl_loaded = load_ssl_symbols(); #endif // set the output buffer size if not specified on the command line, take account of resampling @@ -531,7 +739,7 @@ } } -#if LINUX || FREEBSD +#if LINUX || FREEBSD || SUN if (pidfile) { if (!(pidfp = fopen(pidfile, "w")) ) { fprintf(stderr, "Error opening pidfile %s: %s\n", pidfile, strerror(errno)); @@ -547,7 +755,7 @@ } if (pidfp) { - fprintf(pidfp, "%d\n", getpid()); + fprintf(pidfp, "%d\n", (int) getpid()); fclose(pidfp); } #endif @@ -562,16 +770,19 @@ output_init_stdout(log_output, output_buf_size, output_params, rates, rate_delay); } else { #if ALSA - output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, output_mixer, - output_mixer_unmute); + output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, mixer_device, output_mixer, + output_mixer_unmute, linear_volume); #endif #if PORTAUDIO output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); #endif +#if PULSEAUDIO + output_init_pulse(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); +#endif } #if DSD - dop_init(dop, dop_delay); + dsd_init(dsd_outfmt, dsd_delay); #endif #if VISEXPORT @@ -599,7 +810,7 @@ exit(1); } - slimproto(log_slimproto, server, mac, name, namefile, modelname); + slimproto(log_slimproto, server, mac, name, namefile, modelname, maxSampleRate); decode_close(); stream_close(); @@ -613,6 +824,9 @@ #if PORTAUDIO output_close_pa(); #endif +#if PULSEAUDIO + output_close_pulse(); +#endif } #if IR @@ -623,12 +837,16 @@ winsock_close(); #endif -#if LINUX || FREEBSD +#if LINUX || FREEBSD || SUN if (pidfile) { unlink(pidfile); free(pidfile); } #endif +#if USE_SSL && !LINKALL && !NO_SSLSYM + free_ssl_symbols(); +#endif + exit(0); } diff --git a/minimal_gpio.c b/minimal_gpio.c new file mode 100644 index 0000000..a887947 --- /dev/null +++ b/minimal_gpio.c @@ -0,0 +1,283 @@ +/* + minimal_gpio.c + 2019-07-03 + Public Domain + + Original Site: http://abyz.me.uk/rpi/pigpio/examples.html + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +static volatile uint32_t piPeriphBase = 0x20000000; + +static volatile int pi_is_2711 = 0; + +#define SYST_BASE (piPeriphBase + 0x003000) +#define DMA_BASE (piPeriphBase + 0x007000) +#define CLK_BASE (piPeriphBase + 0x101000) +#define GPIO_BASE (piPeriphBase + 0x200000) +#define UART0_BASE (piPeriphBase + 0x201000) +#define PCM_BASE (piPeriphBase + 0x203000) +#define SPI0_BASE (piPeriphBase + 0x204000) +#define I2C0_BASE (piPeriphBase + 0x205000) +#define PWM_BASE (piPeriphBase + 0x20C000) +#define BSCS_BASE (piPeriphBase + 0x214000) +#define UART1_BASE (piPeriphBase + 0x215000) +#define I2C1_BASE (piPeriphBase + 0x804000) +#define I2C2_BASE (piPeriphBase + 0x805000) +#define DMA15_BASE (piPeriphBase + 0xE05000) + +#define DMA_LEN 0x1000 /* allow access to all channels */ +#define CLK_LEN 0xA8 +#define GPIO_LEN 0xF4 +#define SYST_LEN 0x1C +#define PCM_LEN 0x24 +#define PWM_LEN 0x28 +#define I2C_LEN 0x1C +#define BSCS_LEN 0x40 + +#define GPSET0 7 +#define GPSET1 8 + +#define GPCLR0 10 +#define GPCLR1 11 + +#define GPLEV0 13 +#define GPLEV1 14 + +#define GPPUD 37 +#define GPPUDCLK0 38 +#define GPPUDCLK1 39 + +/* BCM2711 has different pulls */ + +#define GPPUPPDN0 57 +#define GPPUPPDN1 58 +#define GPPUPPDN2 59 +#define GPPUPPDN3 60 + +#define SYST_CS 0 +#define SYST_CLO 1 +#define SYST_CHI 2 + +static volatile uint32_t *gpioReg = MAP_FAILED; +static volatile uint32_t *systReg = MAP_FAILED; +static volatile uint32_t *bscsReg = MAP_FAILED; + +#define PI_BANK (gpio>>5) +#define PI_BIT (1<<(gpio&0x1F)) + +/* gpio modes. */ + +#define PI_INPUT 0 +#define PI_OUTPUT 1 +#define PI_ALT0 4 +#define PI_ALT1 5 +#define PI_ALT2 6 +#define PI_ALT3 7 +#define PI_ALT4 3 +#define PI_ALT5 2 + +void gpioSetMode(unsigned gpio, unsigned mode) +{ + int reg, shift; + + reg = gpio/10; + shift = (gpio%10) * 3; + + gpioReg[reg] = (gpioReg[reg] & ~(7<> shift) & 7; +} + +/* Values for pull-ups/downs off, pull-down and pull-up. */ + +#define PI_PUD_OFF 0 +#define PI_PUD_DOWN 1 +#define PI_PUD_UP 2 + +void gpioSetPullUpDown(unsigned gpio, unsigned pud) +{ + int shift = (gpio & 0xf) << 1; + uint32_t bits; + uint32_t pull = 0; + + if (pi_is_2711) + { + switch (pud) + { + case PI_PUD_OFF: pull = 0; break; + case PI_PUD_UP: pull = 1; break; + case PI_PUD_DOWN: pull = 2; break; + } + + bits = *(gpioReg + GPPUPPDN0 + (gpio>>4)); + bits &= ~(3 << shift); + bits |= (pull << shift); + *(gpioReg + GPPUPPDN0 + (gpio>>4)) = bits; + } + else + { + *(gpioReg + GPPUD) = pud; + + usleep(20); + + *(gpioReg + GPPUDCLK0 + PI_BANK) = PI_BIT; + + usleep(20); + + *(gpioReg + GPPUD) = 0; + + *(gpioReg + GPPUDCLK0 + PI_BANK) = 0; + } +} + +int gpioRead(unsigned gpio) +{ + if ((*(gpioReg + GPLEV0 + PI_BANK) & PI_BIT) != 0) return 1; + else return 0; +} + +void gpioWrite(unsigned gpio, unsigned level) +{ + if (level == 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT; + else *(gpioReg + GPSET0 + PI_BANK) = PI_BIT; +} + +void gpioTrigger(unsigned gpio, unsigned pulseLen, unsigned level) +{ + if (level == 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT; + else *(gpioReg + GPSET0 + PI_BANK) = PI_BIT; + + usleep(pulseLen); + + if (level != 0) *(gpioReg + GPCLR0 + PI_BANK) = PI_BIT; + else *(gpioReg + GPSET0 + PI_BANK) = PI_BIT; +} + +/* Bit (1<= 8) + { + piPeriphBase = buf[4]<<24 | buf[5]<<16 | buf[6]<<8 | buf[7]; + if (!piPeriphBase) + piPeriphBase = buf[8]<<24 | buf[9]<<16 | buf[10]<<8 | buf[11]; + + if (piPeriphBase == 0xFE000000) pi_is_2711 = 1; + } + fclose(filp); + } + + return rev; +} + +/* Returns the number of microseconds after system boot. Wraps around + after 1 hour 11 minutes 35 seconds. +*/ + +uint32_t gpioTick(void) { return systReg[SYST_CLO]; } + + +/* Map in registers. */ + +static uint32_t * initMapMem(int fd, uint32_t addr, uint32_t len) +{ + return (uint32_t *) mmap(0, len, + PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_SHARED|MAP_LOCKED, + fd, addr); +} + +int gpioInitialise(void) +{ + int fd; + + gpioHardwareRevision(); /* sets rev and peripherals base address */ + + fd = open("/dev/mem", O_RDWR | O_SYNC) ; + + if (fd < 0) + { + fprintf(stderr, + "This program needs root privileges. Try using sudo\n"); + return -1; + } + + gpioReg = initMapMem(fd, GPIO_BASE, GPIO_LEN); + systReg = initMapMem(fd, SYST_BASE, SYST_LEN); + bscsReg = initMapMem(fd, BSCS_BASE, BSCS_LEN); + + close(fd); + + if ((gpioReg == MAP_FAILED) || + (systReg == MAP_FAILED) || + (bscsReg == MAP_FAILED)) + { + fprintf(stderr, + "Bad, mmap failed\n"); + return -1; + } + return 0; +} diff --git a/mpg.c b/mpg.c index f3074f2..be62f34 100644 --- a/mpg.c +++ b/mpg.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -124,7 +125,7 @@ LOG_INFO("setting track_start"); LOCK_O_not_direct; output.next_sample_rate = decode_newstream(rate, output.supported_rates); - IF_DSD( output.next_dop = false; ) + IF_DSD( output.next_fmt = PCM; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; diff --git a/opus.c b/opus.c new file mode 100644 index 0000000..7d742c6 --- /dev/null +++ b/opus.c @@ -0,0 +1,328 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com + * Philippe 2018-2019, philippe_44@outlook.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "squeezelite.h" +#if OPUS + +/* +* with some low-end CPU, the decode call takes a fair bit of time and if the outputbuf is locked during that +* period, the output_thread (or equivalent) will be locked although there is plenty of samples available. +* Normally, with PRIO_INHERIT, that thread should increase decoder priority and get the lock quickly but it +* seems that when the streambuf has plenty of data, the decode thread grabs the CPU to much, even it the output +* thread has a higher priority. Using an interim buffer where opus decoder writes the output is not great from +* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long +*/ +#if EMBEDDED +#define FRAME_BUF 2048 +#endif + +#if BYTES_PER_FRAME == 4 +#define ALIGN(n) (n) +#else +#define ALIGN(n) (n << 16) +#endif + +#include + +struct opus { + struct OggOpusFile *of; +#if FRAME_BUF + u8_t *write_buf; +#endif +#if !LINKALL + // opus symbols to be dynamically loaded + void (*op_free)(OggOpusFile *_of); + int (*op_read)(OggOpusFile *_of, opus_int16 *_pcm, int _buf_size, int *_li); + const OpusHead* (*op_head)(OggOpusFile *_of, int _li); + OggOpusFile* (*op_open_callbacks) (void *_source, OpusFileCallbacks *_cb, unsigned char *_initial_data, size_t _initial_bytes, int *_error); +#endif +}; + +static struct opus *u; + +extern log_level loglevel; + +extern struct buffer *streambuf; +extern struct buffer *outputbuf; +extern struct streamstate stream; +extern struct outputstate output; +extern struct decodestate decode; +extern struct processstate process; + +#define LOCK_S mutex_lock(streambuf->mutex) +#define UNLOCK_S mutex_unlock(streambuf->mutex) +#define LOCK_O mutex_lock(outputbuf->mutex) +#define UNLOCK_O mutex_unlock(outputbuf->mutex) +#if PROCESS +#define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) +#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) +#define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) +#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex) +#define IF_DIRECT(x) if (decode.direct) { x } +#define IF_PROCESS(x) if (!decode.direct) { x } +#else +#define LOCK_O_direct mutex_lock(outputbuf->mutex) +#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex) +#define LOCK_O_not_direct +#define UNLOCK_O_not_direct +#define IF_DIRECT(x) { x } +#define IF_PROCESS(x) +#endif + +#if LINKALL +#define OP(h, fn, ...) (op_ ## fn)(__VA_ARGS__) +#else +#define OP(h, fn, ...) (h)->op_ ## fn(__VA_ARGS__) +#endif + +// called with mutex locked within vorbis_decode to avoid locking O before S +static int _read_cb(void *datasource, char *ptr, int size) { + size_t bytes; + + LOCK_S; + + bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); + bytes = min(bytes, size); + + memcpy(ptr, streambuf->readp, bytes); + _buf_inc_readp(streambuf, bytes); + + UNLOCK_S; + + return bytes; +} + +static decode_state opus_decompress(void) { + frames_t frames; + int n; + static int channels; + u8_t *write_buf; + + LOCK_S; + + if (stream.state <= DISCONNECT && !_buf_used(streambuf)) { + UNLOCK_S; + return DECODE_COMPLETE; + } + + UNLOCK_S; + + if (decode.new_stream) { + struct OpusFileCallbacks cbs; + const struct OpusHead *info; + int err; + + cbs.read = (op_read_func) _read_cb; + cbs.seek = NULL; cbs.tell = NULL; cbs.close = NULL; + + if ((u->of = OP(u, open_callbacks, streambuf, &cbs, NULL, 0, &err)) == NULL) { + LOG_WARN("open_callbacks error: %d", err); + return DECODE_COMPLETE; + } + + info = OP(u, head, u->of, -1); + + LOCK_O; + output.next_sample_rate = decode_newstream(48000, output.supported_rates); + IF_DSD( output.next_fmt = PCM; ) + output.track_start = outputbuf->writep; + if (output.fade_mode) _checkfade(true); + decode.new_stream = false; + UNLOCK_O; + + channels = info->channel_count; + + LOG_INFO("setting track_start"); + } + +#if FRAME_BUF + IF_DIRECT( + frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; + frames = min(frames, FRAME_BUF); + write_buf = u->write_buf; + ); +#else + LOCK_O_direct; + IF_DIRECT( + frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; + write_buf = outputbuf->writep; + ); +#endif + IF_PROCESS( + frames = process.max_in_frames; + write_buf = process.inbuf; + ); + + // write the decoded frames into outputbuf then unpack them (they are 16 bits) + n = OP(u, read, u->of, (opus_int16*) write_buf, frames * channels, NULL); + +#if FRAME_BUF + LOCK_O_direct; +#endif + + if (n > 0) { + frames_t count; + s16_t *iptr; + ISAMPLE_T *optr; + + frames = n; + count = frames * channels; + + // work backward to unpack samples (if needed) + iptr = (s16_t *) write_buf + count; + optr = (ISAMPLE_T *) write_buf + frames * 2; + + if (channels == 2) { +#if BYTES_PER_FRAME == 4 +#if FRAME_BUF + // copy needed only when DIRECT and FRAME_BUF + IF_DIRECT( + memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME); + ) +#endif +#else + while (count--) { + *--optr = ALIGN(*--iptr); + } +#endif + } else if (channels == 1) { + while (count--) { + *--optr = ALIGN(*--iptr); + *--optr = ALIGN(*iptr); + } + } + + IF_DIRECT( + _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); + ); + IF_PROCESS( + process.in_frames = frames; + ); + + LOG_SDEBUG("wrote %u frames", frames); + + } else if (n == 0) { + + if (stream.state <= DISCONNECT) { + LOG_INFO("partial decode"); + UNLOCK_O_direct; + UNLOCK_S; + return DECODE_COMPLETE; + } else { + LOG_INFO("no frame decoded"); + } + + } else if (n == OP_HOLE) { + + // recoverable hole in stream, seen when skipping + LOG_DEBUG("hole in stream"); + + } else { + + LOG_INFO("op_read error: %d", n); + UNLOCK_O_direct; + return DECODE_COMPLETE; + } + + UNLOCK_O_direct; + + return DECODE_RUNNING; +} + + +static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { + if (!u->of) { +#if FRAME_BUF + if (!u->write_buf) u->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME); +#endif + } else { + OP(u, free, u->of); + u->of = NULL; + } +} + +static void opus_close(void) { + if (u->of) { + OP(u, free, u->of); + u->of = NULL; + } +#if FRAME_BUF + free(u->write_buf); + u->write_buf = NULL; +#endif +} + +static bool load_opus(void) { +#if !LINKALL + void *handle = dlopen(LIBOPUS, RTLD_NOW); + char *err; + + if (!handle) { + LOG_INFO("dlerror: %s", dlerror()); + return false; + } + + u->op_free = dlsym(handle, "op_free"); + u->op_read = dlsym(handle, "op_read"); + u->op_head = dlsym(handle, "op_head"); + u->op_open_callbacks = dlsym(handle, "op_open_callbacks"); + + if ((err = dlerror()) != NULL) { + LOG_INFO("dlerror: %s", err); + return false; + } + + LOG_INFO("loaded "LIBOPUS); +#endif + + return true; +} + +struct codec *register_opus(void) { + static struct codec ret = { + 'u', // id + "ops", // types + 4*1024, // min read + 32*1024, // min space + opus_open, // open + opus_close, // close + opus_decompress, // decode + }; + + u = malloc(sizeof(struct opus)); + if (!u) { + return NULL; + } + + u->of = NULL; +#if FRAME_BUF + u->write_buf = NULL; +#endif + if (!load_opus()) { + return NULL; + } + + LOG_INFO("using opus to decode ops"); + return &ret; +} +#endif /* OPUS */ + diff --git a/output.c b/output.c index 6176c8b..571a172 100644 --- a/output.c +++ b/output.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,8 +33,10 @@ u8_t *silencebuf; #if DSD -u8_t *silencebuf_dop; +u8_t *silencebuf_dsd; #endif + +bool user_rates = false; #define LOCK mutex_lock(outputbuf->mutex) #define UNLOCK mutex_unlock(outputbuf->mutex) @@ -56,7 +59,7 @@ silence = false; // start when threshold met - if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 100 && frames > output.start_frames) { + if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) { output.state = OUTPUT_RUNNING; LOG_INFO("start buffer frames: %u", frames); wake_controller(); @@ -124,10 +127,13 @@ unsigned delay = 0; if (output.current_sample_rate != output.next_sample_rate) { delay = output.rate_delay; +#if PULSEAUDIO + set_sample_rate(output.next_sample_rate); +#endif } IF_DSD( - if (output.dop != output.next_dop) { - delay = output.dop_delay; + if (output.outfmt != output.next_fmt) { + delay = output.dsd_delay; } ) frames -= size; @@ -149,9 +155,9 @@ output.track_start_time = gettime_ms(); output.current_sample_rate = output.next_sample_rate; IF_DSD( - output.dop = output.next_dop; + output.outfmt = output.next_fmt; ) - if (!output.fade == FADE_ACTIVE || !output.fade_mode == FADE_CROSSFADE) { + if (output.fade == FADE_INACTIVE || output.fade_mode != FADE_CROSSFADE) { output.current_replay_gain = output.next_replay_gain; } output.track_start = NULL; @@ -163,7 +169,7 @@ } IF_DSD( - if (output.dop) { + if (output.outfmt != PCM) { gainL = gainR = FIXED_ONE; } ) @@ -282,7 +288,8 @@ bytes = output.next_sample_rate * BYTES_PER_FRAME * output.fade_secs; if (output.fade_mode == FADE_INOUT) { - bytes /= 2; + /* align on a frame boundary */ + bytes = ((bytes / 2) / BYTES_PER_FRAME) * BYTES_PER_FRAME; } if (start && (output.fade_mode == FADE_IN || (output.fade_mode == FADE_INOUT && _buf_used(outputbuf) == 0))) { @@ -359,12 +366,12 @@ memset(silencebuf, 0, MAX_SILENCE_FRAMES * BYTES_PER_FRAME); IF_DSD( - silencebuf_dop = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME); - if (!silencebuf_dop) { - LOG_ERROR("unable to malloc silence dop buffer"); + silencebuf_dsd = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME); + if (!silencebuf_dsd) { + LOG_ERROR("unable to malloc silence dsd buffer"); exit(0); } - dop_silence_frames((u32_t *)silencebuf_dop, MAX_SILENCE_FRAMES); + dsd_silence_frames((u32_t *)silencebuf_dsd, MAX_SILENCE_FRAMES); ) LOG_DEBUG("idle timeout: %u", idle); @@ -376,12 +383,20 @@ output.error_opening = false; output.idle_to = (u32_t) idle; - if (!rates[0]) { - if (!test_open(output.device, output.supported_rates)) { - LOG_ERROR("unable to open output device"); + /* Skip test_open for stdout, set default sample rates */ + if ( output.device[0] == '-' ) { + for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { + output.supported_rates[i] = rates[i]; + } + } + else { + if (!test_open(output.device, output.supported_rates, user_rates)) { + LOG_ERROR("unable to open output device: %s", output.device); exit(0); } - } else { + } + + if (user_rates) { for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) { output.supported_rates[i] = rates[i]; } @@ -415,7 +430,7 @@ buf_destroy(outputbuf); free(silencebuf); IF_DSD( - free(silencebuf_dop); + free(silencebuf_dsd); ) } diff --git a/output_alsa.c b/output_alsa.c index aa01560..93ba1e7 100644 --- a/output_alsa.c +++ b/output_alsa.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,6 +17,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * + * Additions (c) Paul Hermann, 2015-2017 under the same license terms + * -Control of Raspberry pi GPIO for amplifier power + * -Launch script on power status change from LMS */ // Output using Alsa @@ -43,7 +47,13 @@ // ouput device static struct { char device[MAX_DEVICE_LEN + 1]; + char *ctl; + char *mixer_ctl; snd_pcm_format_t format; +#if DSD + dsd_format outfmt; + snd_pcm_format_t pcmfmt; +#endif snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t period_size; unsigned rate; @@ -51,14 +61,18 @@ bool reopen; u8_t *write_buf; const char *volume_mixer_name; - int volume_mixer_index; + bool mixer_linear; + snd_mixer_elem_t* mixer_elem; + snd_mixer_t *mixer_handle; + long mixer_min; + long mixer_max; } alsa; static snd_pcm_t *pcmp = NULL; extern u8_t *silencebuf; #if DSD -extern u8_t *silencebuf_dop; +extern u8_t *silencebuf_dsd; #endif static log_level loglevel; @@ -70,6 +84,24 @@ #define LOCK mutex_lock(outputbuf->mutex) #define UNLOCK mutex_unlock(outputbuf->mutex) + +static char *ctl4device(const char *device) { + char *ctl = NULL; + + if (!strncmp(device, "hw:", 3)) + ctl = strdup(device); + else if (!strncmp(device, "plughw:", 7)) + ctl = strdup(device + 4); + + if (ctl) { + char *comma; + if ((comma = strrchr(ctl, ','))) + *comma = '\0'; + } else + ctl = strdup(device); + + return ctl; +} void list_devices(void) { void **hints, **n; @@ -101,6 +133,7 @@ snd_mixer_t *handle; snd_mixer_selem_id_t *sid; snd_mixer_elem_t *elem; + char *ctl = ctl4device(output_device); snd_mixer_selem_id_alloca(&sid); LOG_INFO("listing mixers for: %s", output_device); @@ -109,11 +142,13 @@ LOG_ERROR("open error: %s", snd_strerror(err)); return; } - if ((err = snd_mixer_attach(handle, output_device)) < 0) { + if ((err = snd_mixer_attach(handle, ctl)) < 0) { LOG_ERROR("attach error: %s", snd_strerror(err)); snd_mixer_close(handle); + free(ctl); return; } + free(ctl); if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { LOG_ERROR("register error: %s", snd_strerror(err)); snd_mixer_close(handle); @@ -143,100 +178,52 @@ #define MINVOL_DB 72 // LMS volume map for SqueezePlay sends values in range ~ -72..0 dB -static void set_mixer(const char *device, const char *mixer, int mixer_index, bool setmax, float ldB, float rdB) { +static void set_mixer(bool setmax, float ldB, float rdB) { int err; long nleft, nright; - long min, max; - snd_mixer_t *handle; - snd_mixer_selem_id_t *sid; - snd_mixer_elem_t* elem; - - if ((err = snd_mixer_open(&handle, 0)) < 0) { - LOG_ERROR("open error: %s", snd_strerror(err)); - return; - } - if ((err = snd_mixer_attach(handle, device)) < 0) { - LOG_ERROR("attach error: %s", snd_strerror(err)); - snd_mixer_close(handle); - return; - } - if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { - LOG_ERROR("register error: %s", snd_strerror(err)); - snd_mixer_close(handle); - return; - } - if ((err = snd_mixer_load(handle)) < 0) { - LOG_ERROR("load error: %s", snd_strerror(err)); - snd_mixer_close(handle); - return; - } - - snd_mixer_selem_id_alloca(&sid); - - snd_mixer_selem_id_set_index(sid, mixer_index); - snd_mixer_selem_id_set_name(sid, mixer); - - if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) { - LOG_ERROR("error find selem %s", mixer); - snd_mixer_close(handle); - return; - } - - if (snd_mixer_selem_has_playback_switch(elem)) { - snd_mixer_selem_set_playback_switch_all(elem, 1); // unmute - } - - err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max); - - if (err < 0 || max - min < 1000) { - // unable to get db range or range is less than 10dB - ignore and set using raw values - if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) { - LOG_ERROR("unable to get volume raw range"); - } else { - long lraw, rraw; - if (setmax) { - lraw = rraw = max; - } else { - lraw = ((ldB > -MINVOL_DB ? MINVOL_DB + floor(ldB) : 0) / MINVOL_DB * (max-min)) + min; - rraw = ((rdB > -MINVOL_DB ? MINVOL_DB + floor(rdB) : 0) / MINVOL_DB * (max-min)) + min; - } - LOG_DEBUG("setting vol raw [%ld..%ld]", min, max); - if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) { - LOG_ERROR("error setting left volume: %s", snd_strerror(err)); - } - if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) { - LOG_ERROR("error setting right volume: %s", snd_strerror(err)); - } - } + + if (alsa.mixer_linear) { + long lraw, rraw; + if (setmax) { + lraw = rraw = alsa.mixer_max; + } else { + lraw = ((ldB > -MINVOL_DB ? MINVOL_DB + floor(ldB) : 0) / MINVOL_DB * (alsa.mixer_max-alsa.mixer_min)) + alsa.mixer_min; + rraw = ((rdB > -MINVOL_DB ? MINVOL_DB + floor(rdB) : 0) / MINVOL_DB * (alsa.mixer_max-alsa.mixer_min)) + alsa.mixer_min; + } + LOG_DEBUG("setting vol raw [%ld..%ld]", alsa.mixer_min, alsa.mixer_max); + if ((err = snd_mixer_selem_set_playback_volume(alsa.mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) { + LOG_ERROR("error setting left volume: %s", snd_strerror(err)); + } + if ((err = snd_mixer_selem_set_playback_volume(alsa.mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) { + LOG_ERROR("error setting right volume: %s", snd_strerror(err)); + } } else { // set db directly - LOG_DEBUG("setting vol dB [%ld..%ld]", min, max); + LOG_DEBUG("setting vol dB [%ld..%ld]", alsa.mixer_min, alsa.mixer_max); if (setmax) { // set to 0dB if available as this should be max volume for music recored at max pcm values - if (max >= 0 && min <= 0) { + if (alsa.mixer_max >= 0 && alsa.mixer_min <= 0) { ldB = rdB = 0; } else { - ldB = rdB = max; - } - } - if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB, 1)) < 0) { + ldB = rdB = alsa.mixer_max; + } + } + if ((err = snd_mixer_selem_set_playback_dB(alsa.mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB, 1)) < 0) { LOG_ERROR("error setting left volume: %s", snd_strerror(err)); } - if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB, 1)) < 0) { + if ((err = snd_mixer_selem_set_playback_dB(alsa.mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB, 1)) < 0) { LOG_ERROR("error setting right volume: %s", snd_strerror(err)); } } - if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) { + if ((err = snd_mixer_selem_get_playback_volume(alsa.mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) { LOG_ERROR("error getting left vol: %s", snd_strerror(err)); } - if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) { + if ((err = snd_mixer_selem_get_playback_volume(alsa.mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) { LOG_ERROR("error getting right vol: %s", snd_strerror(err)); } - LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", mixer, ldB, nleft, rdB, nright); - - snd_mixer_close(handle); + LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", alsa.volume_mixer_name, ldB, nleft, rdB, nright); } void set_volume(unsigned left, unsigned right) { @@ -260,7 +247,7 @@ ldB = 20 * log10( left / 65536.0F ); rdB = 20 * log10( right / 65536.0F ); - set_mixer(output.device, alsa.volume_mixer_name, alsa.volume_mixer_index, false, ldB, rdB); + set_mixer(false, ldB, rdB); } static void *alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { @@ -282,7 +269,7 @@ } } -bool test_open(const char *device, unsigned rates[]) { +bool test_open(const char *device, unsigned rates[], bool userdef_rates) { int err; snd_pcm_t *pcm; snd_pcm_hw_params_t *hw_params; @@ -302,12 +289,14 @@ } // find supported sample rates to enable client side resampling of non supported rates - unsigned i, ind; - unsigned ref[] TEST_RATES; - - for (i = 0, ind = 0; ref[i]; ++i) { - if (snd_pcm_hw_params_test_rate(pcm, hw_params, ref[i], 0) == 0) { - rates[ind++] = ref[i]; + if (!userdef_rates) { + unsigned i, ind; + unsigned ref[] TEST_RATES; + + for (i = 0, ind = 0; ref[i]; ++i) { + if (snd_pcm_hw_params_test_rate(pcm, hw_params, ref[i], 0) == 0) { + rates[ind++] = ref[i]; + } } } @@ -334,7 +323,11 @@ return true; } +#if DSD +static int alsa_open(const char *device, unsigned sample_rate, unsigned alsa_buffer, unsigned alsa_period, dsd_format outfmt) { +#else static int alsa_open(const char *device, unsigned sample_rate, unsigned alsa_buffer, unsigned alsa_period) { +#endif int err; snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); @@ -344,6 +337,9 @@ // reset params alsa.rate = 0; +#if DSD + alsa.outfmt = PCM; +#endif alsa.period_size = 0; strcpy(alsa.device, device); @@ -400,6 +396,26 @@ } // set the sample format +#if DSD + switch (outfmt) { + case DSD_U8: + alsa.format = SND_PCM_FORMAT_DSD_U8; break; + case DSD_U16_LE: + alsa.format = SND_PCM_FORMAT_DSD_U16_LE; break; + case DSD_U16_BE: + alsa.format = SND_PCM_FORMAT_DSD_U16_BE; break; + case DSD_U32_LE: + alsa.format = SND_PCM_FORMAT_DSD_U32_LE; break; + case DSD_U32_BE: + alsa.format = SND_PCM_FORMAT_DSD_U32_BE; break; + case DOP_S24_LE: + alsa.format = SND_PCM_FORMAT_S24_LE; break; + case DOP_S24_3LE: + alsa.format = SND_PCM_FORMAT_S24_3LE; break; + default: + alsa.format = alsa.pcmfmt; + } +#endif snd_pcm_format_t *fmt = alsa.format ? &alsa.format : (snd_pcm_format_t *)fmts; do { if (snd_pcm_hw_params_set_format(pcmp, hw_params, *fmt) >= 0) { @@ -428,6 +444,18 @@ output.format = S24_3LE; break; case SND_PCM_FORMAT_S16_LE: output.format = S16_LE; break; +#if DSD + case SND_PCM_FORMAT_DSD_U32_LE: + output.format = U32_LE; break; + case SND_PCM_FORMAT_DSD_U32_BE: + output.format = U32_BE; break; + case SND_PCM_FORMAT_DSD_U16_LE: + output.format = U16_LE; break; + case SND_PCM_FORMAT_DSD_U16_BE: + output.format = U16_BE; break; + case SND_PCM_FORMAT_DSD_U8: + output.format = U8; break; +#endif default: break; } @@ -512,7 +540,10 @@ // this indicates we have opened the device ok alsa.rate = sample_rate; - +#if DSD + alsa.outfmt = outfmt; +#endif + return 0; } @@ -548,11 +579,14 @@ inputptr = (s32_t *) (silence ? silencebuf : outputbuf->readp); IF_DSD( - if (output.dop) { + if (output.outfmt != PCM) { if (silence) { - inputptr = (s32_t *) silencebuf_dop; - } - update_dop((u32_t *) inputptr, out_frames, output.invert && !silence); + inputptr = (s32_t *) silencebuf_dsd; + } + if (output.outfmt == DOP || output.outfmt == DOP_S24_LE || output.outfmt == DOP_S24_3LE) + update_dop((u32_t *) inputptr, out_frames, output.invert && !silence); + else if (output.invert && !silence) + dsd_invert((u32_t *) inputptr, out_frames); } ) @@ -633,18 +667,40 @@ } probe_device = false; } +#if DSD + if (!pcmp || alsa.rate != output.current_sample_rate || alsa.outfmt != output.outfmt ) { +#else if (!pcmp || alsa.rate != output.current_sample_rate) { +#endif +#if GPIO + // Wake up amp + if (gpio_active) { + ampstate = 1; + relay(1); + } + if (power_script != NULL) { + ampstate = 1; + relay_script(1); + } +#endif LOG_INFO("open output device: %s", output.device); LOCK; // FIXME - some alsa hardware requires opening twice for a new sample rate to work // this is a workaround which should be removed if (alsa.reopen) { +#if DSD + alsa_open(output.device, output.current_sample_rate, output.buffer, output.period, output.outfmt); +#else alsa_open(output.device, output.current_sample_rate, output.buffer, output.period); - } - +#endif + } +#if DSD + if (!!alsa_open(output.device, output.current_sample_rate, output.buffer, output.period, output.outfmt)) { +#else if (!!alsa_open(output.device, output.current_sample_rate, output.buffer, output.period)) { +#endif output.error_opening = true; UNLOCK; sleep(5); @@ -661,6 +717,7 @@ LOG_INFO("XRUN"); if ((err = snd_pcm_recover(pcmp, -EPIPE, 1)) < 0) { LOG_INFO("XRUN recover failed: %s", snd_strerror(err)); + usleep(10000); } start = true; continue; @@ -711,7 +768,11 @@ start = false; } } else { - if ((err = snd_pcm_wait(pcmp, 1000)) < 0) { + usleep(10000); + if ((err = snd_pcm_wait(pcmp, 1000)) <= 0) { + if ( err == 0 ) { + LOG_INFO("pcm wait timeout"); + } if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { LOG_INFO("pcm wait error: %s", snd_strerror(err)); } @@ -746,6 +807,17 @@ pcmp = NULL; output_off = true; vis_stop(); +#if GPIO + // Put Amp to Sleep + if (gpio_active){ + ampstate = 0; + relay(0); + } + if (power_script != NULL ){ + ampstate = 0; + relay_script(0); + } +#endif continue; } @@ -786,10 +858,61 @@ return 0; } +int mixer_init_alsa(const char *device, const char *mixer, int mixer_index) { + int err; + snd_mixer_selem_id_t *sid; + + if ((err = snd_mixer_open(&alsa.mixer_handle, 0)) < 0) { + LOG_ERROR("open error: %s", snd_strerror(err)); + return -1; + } + if ((err = snd_mixer_attach(alsa.mixer_handle, device)) < 0) { + LOG_ERROR("attach error: %s", snd_strerror(err)); + snd_mixer_close(alsa.mixer_handle); + return -1; + } + if ((err = snd_mixer_selem_register(alsa.mixer_handle, NULL, NULL)) < 0) { + LOG_ERROR("register error: %s", snd_strerror(err)); + snd_mixer_close(alsa.mixer_handle); + return -1; + } + if ((err = snd_mixer_load(alsa.mixer_handle)) < 0) { + LOG_ERROR("load error: %s", snd_strerror(err)); + snd_mixer_close(alsa.mixer_handle); + return -1; + } + + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, mixer_index); + snd_mixer_selem_id_set_name(sid, mixer); + + if ((alsa.mixer_elem = snd_mixer_find_selem(alsa.mixer_handle, sid)) == NULL) { + LOG_ERROR("error find selem %s", alsa.mixer_handle); + snd_mixer_close(alsa.mixer_handle); + return -1; + } + + if (snd_mixer_selem_has_playback_switch(alsa.mixer_elem)) { + snd_mixer_selem_set_playback_switch_all(alsa.mixer_elem, 1); // unmute + } + + err = snd_mixer_selem_get_playback_dB_range(alsa.mixer_elem, &alsa.mixer_min, &alsa.mixer_max); + + if (err < 0 || alsa.mixer_max - alsa.mixer_min < 1000 || alsa.mixer_linear) { + alsa.mixer_linear = 1; + // unable to get db range or range is less than 10dB - ignore and set using raw values + if ((err = snd_mixer_selem_get_playback_volume_range(alsa.mixer_elem, &alsa.mixer_min, &alsa.mixer_max)) < 0) + { + LOG_ERROR("Unable to get volume raw range"); + return -1; + } + } + return 0; +} + static pthread_t thread; -void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], - unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute) { +void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *mixer_device, char *volume_mixer, bool mixer_unmute, bool mixer_linear) { unsigned alsa_buffer = ALSA_BUFFER_TIME; unsigned alsa_period = ALSA_PERIOD_COUNT; @@ -820,13 +943,17 @@ alsa.mmap = alsa_mmap; alsa.write_buf = NULL; +#if DSD + alsa.pcmfmt = 0; +#else alsa.format = 0; +#endif alsa.reopen = alsa_reopen; - - if (!mixer_unmute) { - alsa.volume_mixer_name = volume_mixer_name; - alsa.volume_mixer_index = volume_mixer_index ? atoi(volume_mixer_index) : 0; - } + alsa.mixer_handle = NULL; + alsa.ctl = ctl4device(device); + alsa.mixer_ctl = mixer_device ? ctl4device(mixer_device) : alsa.ctl; + alsa.volume_mixer_name = volume_mixer_name; + alsa.mixer_linear = mixer_linear; output.format = 0; output.buffer = alsa_buffer; @@ -836,10 +963,17 @@ output.rate_delay = rate_delay; if (alsa_sample_fmt) { +#if DSD + if (!strcmp(alsa_sample_fmt, "32")) alsa.pcmfmt = SND_PCM_FORMAT_S32_LE; + if (!strcmp(alsa_sample_fmt, "24")) alsa.pcmfmt = SND_PCM_FORMAT_S24_LE; + if (!strcmp(alsa_sample_fmt, "24_3")) alsa.pcmfmt = SND_PCM_FORMAT_S24_3LE; + if (!strcmp(alsa_sample_fmt, "16")) alsa.pcmfmt = SND_PCM_FORMAT_S16_LE; +#else if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; if (!strcmp(alsa_sample_fmt, "24")) alsa.format = SND_PCM_FORMAT_S24_LE; if (!strcmp(alsa_sample_fmt, "24_3")) alsa.format = SND_PCM_FORMAT_S24_3LE; if (!strcmp(alsa_sample_fmt, "16")) alsa.format = SND_PCM_FORMAT_S16_LE; +#endif } LOG_INFO("requested alsa_buffer: %u alsa_period: %u format: %s mmap: %u", output.buffer, output.period, @@ -848,9 +982,19 @@ snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); output_init_common(level, device, output_buf_size, rates, idle); - - if (mixer_unmute && volume_mixer_name) { - set_mixer(output.device, volume_mixer_name, volume_mixer_index ? atoi(volume_mixer_index) : 0, true, 0, 0); + + if (volume_mixer_name) { + if (mixer_init_alsa(alsa.mixer_ctl, alsa.volume_mixer_name, volume_mixer_index ? + atoi(volume_mixer_index) : 0) < 0) + { + LOG_ERROR("Initialization of mixer failed, reverting to software volume"); + alsa.mixer_handle = NULL; + alsa.volume_mixer_name = NULL; + } + } + if (mixer_unmute && alsa.volume_mixer_name) { + set_mixer(true, 0, 0); + alsa.volume_mixer_name = NULL; } #if LINUX @@ -862,8 +1006,11 @@ LOG_INFO("memory locked"); } +#ifdef __GLIBC__ mallopt(M_TRIM_THRESHOLD, -1); mallopt(M_MMAP_MAX, 0); + LOG_INFO("glibc detected using mallopt"); +#endif touch_memory(silencebuf, MAX_SILENCE_FRAMES * BYTES_PER_FRAME); touch_memory(outputbuf->buf, outputbuf->size); @@ -896,6 +1043,9 @@ pthread_join(thread, NULL); if (alsa.write_buf) free(alsa.write_buf); + if (alsa.ctl) free(alsa.ctl); + if (alsa.mixer_ctl) free(alsa.mixer_ctl); + if (alsa.mixer_handle != NULL) snd_mixer_close(alsa.mixer_handle); output_close_common(); } diff --git a/output_pa.c b/output_pa.c index 025904b..0002d51 100644 --- a/output_pa.c +++ b/output_pa.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,9 +26,38 @@ #if PORTAUDIO #include -#if OSX + +#if WIN +#ifndef PA18API +#include +#endif +#define snprintf _snprintf +#endif + +#if OSX && !defined(OSXPPC) #include #endif + +#if PA18API +typedef int PaDeviceIndex; +typedef double PaTime; + +typedef struct PaStreamParameters +{ + PaDeviceIndex device; + int channelCount; + PaSampleFormat sampleFormat; + PaTime suggestedLatency; + +} PaStreamParameters; + +static int paContinue=0; /* Signal that the stream should continue invoking the callback and processing audio. */ +static int paComplete=1; /* Signal that the stream should stop invoking the callback and finish once all output */ + /* samples have played. */ + +static unsigned paFramesPerBuffer = 4096; +static unsigned paNumberOfBuffers = 4; +#endif /* PA18API */ // ouput device static struct { @@ -47,7 +77,7 @@ extern u8_t *silencebuf; #if DSD -extern u8_t *silencebuf_dop; +extern u8_t *silencebuf_dsd; #endif void list_devices(void) { @@ -60,10 +90,15 @@ } printf("Output devices:\n"); +#ifndef PA18API for (i = 0; i < Pa_GetDeviceCount(); ++i) { - if (Pa_GetDeviceInfo(i)->maxOutputChannels) { + if (Pa_GetDeviceInfo(i)->maxOutputChannels > 1) { printf(" %i - %s [%s]\n", i, Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); } +#else + for (i = 0; i < Pa_CountDevices(); ++i) { + printf(" %i - %s\n", i, Pa_GetDeviceInfo(i)->name); +#endif } printf("\n"); @@ -85,17 +120,26 @@ int i; if (!strncmp(device, "default", 7)) { +#ifndef PA18API return Pa_GetDefaultOutputDevice(); +#else + return Pa_GetDefaultOutputDeviceID(); +#endif } if (len >= 1 && len <= 2 && device[0] >= '0' && device[0] <= '9') { return atoi(device); } +#ifndef PA18API #define DEVICE_ID_MAXLEN 256 for (i = 0; i < Pa_GetDeviceCount(); ++i) { char tmp[DEVICE_ID_MAXLEN]; snprintf(tmp, DEVICE_ID_MAXLEN, "%s [%s]", Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); if (!strncmp(tmp, device, len)) { +#else + for (i = 0; i < Pa_CountDevices(); ++i) { + if (!strncmp(Pa_GetDeviceInfo(i)->name, device, len)) { +#endif return i; } } @@ -103,15 +147,25 @@ return -1; } +#ifndef PA18API static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); -bool test_open(const char *device, unsigned rates[]) { +#else +static int pa_callback(void *pa_input, void *pa_output, unsigned long pa_frames_wanted, + PaTimestamp outTime, void *userData); +#endif +bool test_open(const char *device, unsigned rates[], bool userdef_rates) { PaStreamParameters outputParameters; PaError err; unsigned ref[] TEST_RATES; int device_id, i, ind; - +#if WIN + PaWasapiStreamInfo wasapiInfo; + const PaDeviceInfo * paDeviceInfo; + const PaHostApiInfo *paHostApiInfo; + +#endif if ((device_id = pa_device_id(device)) == -1) { LOG_INFO("device %s not found", device); return false; @@ -120,22 +174,72 @@ outputParameters.device = device_id; outputParameters.channelCount = 2; outputParameters.sampleFormat = paInt32; +#ifndef PA18API outputParameters.suggestedLatency = output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; +#if WIN + paDeviceInfo = Pa_GetDeviceInfo( outputParameters.device ); + paHostApiInfo = Pa_GetHostApiInfo ( paDeviceInfo->hostApi ); + + if ( paHostApiInfo != NULL ) + { + if ( paHostApiInfo->type == paWASAPI ) + { + /* Use exclusive mode for WasApi device, default is shared */ + if (output.pa_hostapi_option == 1) + { + wasapiInfo.size = sizeof(PaWasapiStreamInfo); + wasapiInfo.hostApiType = paWASAPI; + wasapiInfo.version = 1; + wasapiInfo.flags = paWinWasapiExclusive; + outputParameters.hostApiSpecificStreamInfo = &wasapiInfo; + LOG_INFO("opening WASAPI device in exclusive mode"); + } + } + } +#endif /* WIN */ +#endif // check supported sample rates // Note use Pa_OpenStream as it appears more reliable than Pa_IsFormatSupported on some windows apis for (i = 0, ind = 0; ref[i]; ++i) { - err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)ref[i], paFramesPerBufferUnspecified, paNoFlag, - pa_callback, NULL); - if (err == paNoError) { - Pa_CloseStream(pa.stream); - rates[ind++] = ref[i]; - } - } - - if (!rates[0]) { +#ifndef PA18API + err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)ref[i], + paFramesPerBufferUnspecified, paNoFlag, pa_callback, NULL); +#else + err = Pa_OpenStream(&pa.stream, paNoDevice, 0, 0, NULL, outputParameters.device, + outputParameters.channelCount, outputParameters.sampleFormat, NULL, (double)ref[i], + paFramesPerBuffer, paNumberOfBuffers, paNoFlag, pa_callback, NULL); +#endif + switch (err) { + case paInvalidSampleRate: + continue; +#if WIN +#ifndef PA18API + /* Ignore these errors for device probe */ + case paUnanticipatedHostError: + continue; + + case paInvalidDevice: + continue; +#endif +#endif + case paNoError: + Pa_CloseStream(pa.stream); + if (!userdef_rates) { + rates[ind++] = ref[i]; + } + continue; + + default: + /* Any other error is a failure */ + LOG_WARN("error opening portaudio stream: %s", Pa_GetErrorText(err)); + return false; + } + } + + if (!rates[0] && !userdef_rates) { LOG_WARN("no available rate found"); return false; } @@ -213,7 +317,12 @@ PaStreamParameters outputParameters; PaError err = paNoError; int device_id; - +#if WIN + PaWasapiStreamInfo wasapiInfo; + const PaDeviceInfo * paDeviceInfo; + const PaHostApiInfo *paHostApiInfo; + +#endif if (pa.stream) { if ((err = Pa_CloseStream(pa.stream)) != paNoError) { LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); @@ -234,17 +343,18 @@ outputParameters.device = device_id; outputParameters.channelCount = 2; outputParameters.sampleFormat = paInt32; +#ifndef PA18API outputParameters.suggestedLatency = output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; -#if OSX - // enable pro mode which aims to avoid resampling if possible - // see http://code.google.com/p/squeezelite/issues/detail?id=11 & http://code.google.com/p/squeezelite/issues/detail?id=37 - // command line controls osx_playnice which is -1 if not specified, 0 or 1 - choose playnice if -1 or 1 +#endif +#if OSX && !defined(OSXPPC) + /* enable pro mode which aims to avoid resampling if possible */ + /* command line controls pa_hostapi_option which is -1 if not specified, 0 or 1 - choose playnice if -1 or 1 */ PaMacCoreStreamInfo macInfo; unsigned long streamInfoFlags; - if (output.osx_playnice) { + if (output.pa_hostapi_option) { LOG_INFO("opening device in PlayNice mode"); streamInfoFlags = paMacCorePlayNice; } else { @@ -254,32 +364,71 @@ PaMacCore_SetupStreamInfo(&macInfo, streamInfoFlags); outputParameters.hostApiSpecificStreamInfo = &macInfo; #endif +#if WIN + paDeviceInfo = Pa_GetDeviceInfo( outputParameters.device ); + paHostApiInfo = Pa_GetHostApiInfo ( paDeviceInfo->hostApi ); + + if ( paHostApiInfo != NULL ) + { + if ( paHostApiInfo->type == paWASAPI ) + { + /* Use exclusive mode for WasApi device, default is shared */ + if (output.pa_hostapi_option == 1) + { + wasapiInfo.size = sizeof(PaWasapiStreamInfo); + wasapiInfo.hostApiType = paWASAPI; + wasapiInfo.version = 1; + wasapiInfo.flags = paWinWasapiExclusive; + outputParameters.hostApiSpecificStreamInfo = &wasapiInfo; + LOG_INFO("opening WASAPI device in exclusive mode"); + } + } + } +#endif } if (!err && +#ifndef PA18API (err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)output.current_sample_rate, paFramesPerBufferUnspecified, paPrimeOutputBuffersUsingStreamCallback | paDitherOff, pa_callback, NULL)) != paNoError) { + LOG_WARN("error opening device %i - %s [%s] : %s", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, + Pa_GetHostApiInfo(Pa_GetDeviceInfo(outputParameters.device)->hostApi)->name, Pa_GetErrorText(err)); +#else + (err = Pa_OpenStream(&pa.stream, paNoDevice, 0, 0, NULL, outputParameters.device, outputParameters.channelCount, + outputParameters.sampleFormat, NULL, (double)output.current_sample_rate, paFramesPerBuffer, + paNumberOfBuffers, paDitherOff, pa_callback, NULL)) != paNoError) { LOG_WARN("error opening device %i - %s : %s", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, Pa_GetErrorText(err)); +#endif } if (!err) { - LOG_INFO("opened device %i - %s at %u latency %u ms", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, +#ifndef PA18API + LOG_INFO("opened device %i - %s [%s] at %u latency %u ms", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, + Pa_GetHostApiInfo(Pa_GetDeviceInfo(outputParameters.device)->hostApi)->name, (unsigned int)Pa_GetStreamInfo(pa.stream)->sampleRate, (unsigned int)(Pa_GetStreamInfo(pa.stream)->outputLatency * 1000)); - +#else + LOG_INFO("opened device %i - %s at %u fpb %u nbf %u", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, + (unsigned int)output.current_sample_rate, paFramesPerBuffer, paNumberOfBuffers); + +#endif pa.rate = output.current_sample_rate; +#ifndef PA18API if ((err = Pa_SetStreamFinishedCallback(pa.stream, pa_stream_finished)) != paNoError) { LOG_WARN("error setting finish callback: %s", Pa_GetErrorText(err)); } UNLOCK; // StartStream can call pa_callback in a sychronised thread on freebsd, remove lock while it is called +#endif if ((err = Pa_StartStream(pa.stream)) != paNoError) { LOG_WARN("error starting stream: %s", Pa_GetErrorText(err)); } +#ifndef PA18API LOCK; +#endif } if (err && !monitor_thread_running) { @@ -313,9 +462,10 @@ } IF_DSD( - if (output.dop) { + if (output.outfmt == DOP) { update_dop((u32_t *) outputbuf->readp, out_frames, output.invert); - } + } else if (output.outfmt != PCM && output.invert) + dsd_invert((u32_t *) outputbuf->readp, out_frames); ) memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); @@ -325,8 +475,8 @@ u8_t *buf = silencebuf; IF_DSD( - if (output.dop) { - buf = silencebuf_dop; + if (output.outfmt != PCM) { + buf = silencebuf_dsd; update_dop((u32_t *) buf, out_frames, false); // don't invert silence } ) @@ -339,25 +489,30 @@ return (int)out_frames; } +#ifndef PA18API static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags statusFlags, void *userData) { +#else +static int pa_callback(void *pa_input, void *pa_output, unsigned long pa_frames_wanted,PaTimestamp outTime, void *userData) { +#endif int ret; - double stream_time; frames_t frames; optr = (u8_t *)pa_output; LOCK; - stream_time = Pa_GetStreamTime(pa.stream); - - if (time_info->outputBufferDacTime > stream_time) { +#ifndef PA18API + if (time_info->outputBufferDacTime > time_info->currentTime) { // workaround for wdm-ks which can return outputBufferDacTime with a different epoch - output.device_frames = (unsigned)((time_info->outputBufferDacTime - stream_time) * output.current_sample_rate); + output.device_frames = (unsigned)((time_info->outputBufferDacTime - time_info->currentTime) * output.current_sample_rate); } else { output.device_frames = 0; } +#else + output.device_frames = 0; +#endif output.updated = gettime_ms(); output.frames_played_dmp = output.frames_played; @@ -382,20 +537,36 @@ UNLOCK; +#ifdef PA18API + if ( ret == paComplete ) + pa_stream_finished (userData); +#endif return ret; } void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { PaError err; +#ifndef PA18API unsigned latency = 0; - int osx_playnice = -1; - + int pa_hostapi_option = -1; + +#else + unsigned pa_frames = 0; + unsigned pa_nbufs = 0; +#endif /* PA18API */ +#ifndef PA18API char *l = next_param(params, ':'); char *p = next_param(NULL, ':'); if (l) latency = (unsigned)atoi(l); - if (p) osx_playnice = atoi(p); + if (p) pa_hostapi_option = atoi(p); +#else + char *t = next_param(params, ':'); + char *c = next_param(NULL, ':'); + if (t) pa_frames = atoi(t); + if (c) pa_nbufs = atoi(c); +#endif loglevel = level; @@ -403,15 +574,24 @@ memset(&output, 0, sizeof(output)); +#ifndef PA18API output.latency = latency; - output.osx_playnice = osx_playnice; + output.pa_hostapi_option = pa_hostapi_option; +#else + if ( pa_frames != 0 ) + paFramesPerBuffer = pa_frames; + if ( pa_nbufs != 0 ) + paNumberOfBuffers = pa_nbufs; +#endif /* PA18API */ output.format = 0; output.start_frames = 0; output.write_cb = &_write_frames; output.rate_delay = rate_delay; pa.stream = NULL; +#ifndef PA18API LOG_INFO("requested latency: %u", output.latency); +#endif if ((err = Pa_Initialize()) != paNoError) { LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); diff --git a/output_pack.c b/output_pack.c index 16e7ab7..bbffc57 100644 --- a/output_pack.c +++ b/output_pack.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,6 +45,100 @@ void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format) { switch(format) { +#if DSD + case U32_LE: + { +#if SL_LITTLE_ENDIAN + memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME); +#else + u32_t *optr = (u32_t *)(void *)outputptr; + while (cnt--) { + s32_t lsample = *(inputptr++); + s32_t rsample = *(inputptr++); + *(optr++) = + (lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 | + (lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24; + *(optr++) = + (rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 | + (rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24; + } +#endif + } + break; + case U32_BE: + { +#if SL_LITTLE_ENDIAN + u32_t *optr = (u32_t *)(void *)outputptr; + while (cnt--) { + s32_t lsample = *(inputptr++); + s32_t rsample = *(inputptr++); + *(optr++) = + (lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 | + (lsample & 0x0000ff00) << 8 | (lsample & 0x000000ff) << 24; + *(optr++) = + (rsample & 0xff000000) >> 24 | (rsample & 0x00ff0000) >> 8 | + (rsample & 0x0000ff00) << 8 | (rsample & 0x000000ff) << 24; + } +#else + memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME); +#endif + } + break; + case U16_LE: + { + u32_t *optr = (u32_t *)(void *)outputptr; +#if SL_LITTLE_ENDIAN + while (cnt--) { + *(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000); + inputptr += 2; + } +#else + while (cnt--) { + s32_t lsample = *(inputptr++); + s32_t rsample = *(inputptr++); + *(optr++) = + (lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 | + (rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24; + } +#endif + } + break; + case U16_BE: + { + u32_t *optr = (u32_t *)(void *)outputptr; +#if SL_LITTLE_ENDIAN + while (cnt--) { + s32_t lsample = *(inputptr++); + s32_t rsample = *(inputptr++); + *(optr++) = + (lsample & 0xff000000) >> 24 | (lsample & 0x00ff0000) >> 8 | + (rsample & 0xff000000) >> 8 | (rsample & 0x00ff0000) << 8; + } +#else + while (cnt--) { + *(optr++) = (*(inputptr) & 0xffff0000) | (*(inputptr + 1) >> 16 & 0x0000ffff); + inputptr += 2; + } +#endif + } + break; + case U8: + { + u16_t *optr = (u16_t *)(void *)outputptr; +#if SL_LITTLE_ENDIAN + while (cnt--) { + *(optr++) = (u16_t)((*(inputptr) >> 24 & 0x000000ff) | (*(inputptr + 1) >> 16 & 0x0000ff00)); + inputptr += 2; + } +#else + while (cnt--) { + *(optr++) = (u16_t)((*(inputptr) >> 16 & 0x0000ff00) | (*(inputptr + 1) >> 24 & 0x000000ff)); + inputptr += 2; + } +#endif + } + break; +#endif case S16_LE: { u32_t *optr = (u32_t *)(void *)outputptr; diff --git a/output_pulse.c b/output_pulse.c new file mode 100644 index 0000000..2bba1b1 --- /dev/null +++ b/output_pulse.c @@ -0,0 +1,533 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2020, ralph_irving@hotmail.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additions (c) Paul Hermann, 2015-2017 under the same license terms + * -Control of Raspberry pi GPIO for amplifier power + * -Launch script on power status change from LMS + */ + +// Output using PulseAudio + +#include "squeezelite.h" + +#if PULSEAUDIO + +#include +#include + +// To report timing information back to the LMS the latency information needs to be +// retrieved from the PulseAudio server. However this eats some CPU cycles and a few +// bytes of RAM. Here you can decide if you want to retrieve precise timing information +// or save a few CPU cycles. If you are running squeezelite on resource constrained +// device (e.g. Raspberry Pi) and you want to keep the CPU temperature down then you +// can set the PULSEAUDIO_TIMING to zero. In this case the sound card latency and +// PulseAudio buffering won't be accounted for which might give you a slightly skewed +// playback timing information. Otherwise keep this to default value of 2 and you +// will get precise timing information. +#ifndef PULSEAUDIO_TIMING +# define PULSEAUDIO_TIMING 2 +#endif + +typedef enum { + readiness_unknown, + readiness_ready, + readiness_terminated, +} pulse_readiness; + +typedef struct { + pa_mainloop *loop; + pa_context *ctx; + pulse_readiness readiness; +} pulse_connection; + +struct pulse { + bool running; + pa_stream *stream; + pulse_readiness stream_readiness; + pulse_connection conn; + pa_sample_spec sample_spec; + char *sink_name; +}; + +static struct pulse pulse; + +static log_level loglevel; + +extern struct outputstate output; +extern struct buffer *outputbuf; + +#define OUTPUT_STATE_TIMER_INTERVAL_USEC 100000 + +#define LOCK mutex_lock(outputbuf->mutex) +#define UNLOCK mutex_unlock(outputbuf->mutex) + +extern u8_t *silencebuf; + +static void pulse_state_cb(pa_context *c, void *userdata) { + pa_context_state_t state; + pulse_connection *conn = userdata; + + state = pa_context_get_state(c); + switch (state) { + // There are just here for reference + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + default: + break; + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + conn->readiness = readiness_terminated; + break; + case PA_CONTEXT_READY: + conn->readiness = readiness_ready; + break; + } +} + +static inline bool pulse_connection_is_ready(pulse_connection *conn) { + return conn->readiness == readiness_ready; +} + +static inline bool pulse_connection_check_ready(pulse_connection *conn) { + if (!pulse_connection_is_ready(conn)) { + LOG_ERROR("connection to PulseAudio server has been terminated"); + return false; + } + return true; +} + +static inline void pulse_connection_iterate(pulse_connection *conn) { + pa_mainloop_iterate(conn->loop, 1, NULL); +} + +static inline pa_context * pulse_connection_get_context(pulse_connection *conn) { + return conn->ctx; +} + +static bool pulse_connection_init(pulse_connection *conn) { + bool ret; + + conn->loop = pa_mainloop_new(); + pa_mainloop_api *api = pa_mainloop_get_api(conn->loop); + pa_proplist *proplist = pa_proplist_new(); + pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, VERSION); + conn->ctx = pa_context_new_with_proplist(api, MODEL_NAME_STRING, proplist); + pa_proplist_free(proplist); + + conn->readiness = readiness_unknown; + + bool connected = false; + + if (pa_context_connect(conn->ctx, (const char *)NULL, PA_CONTEXT_NOFLAGS, (const pa_spawn_api *)NULL) < 0) { + LOG_ERROR("failed to connect to PulseAudio server: %s", pa_strerror(pa_context_errno(conn->ctx))); + ret = false; + } else { + connected = true; + pa_context_set_state_callback(conn->ctx, pulse_state_cb, conn); + while (conn->readiness == readiness_unknown) { + pa_mainloop_iterate(conn->loop, 1, NULL); + } + + ret = pulse_connection_is_ready(conn); + } + + if (!ret) { + if (connected) pa_context_disconnect(conn->ctx); + pa_context_unref(conn->ctx); + pa_mainloop_free(conn->loop); + } + + return ret; +} + +static void pulse_connection_destroy(pulse_connection *conn) { + pa_context_disconnect(conn->ctx); + pa_context_unref(conn->ctx); + pa_mainloop_free(conn->loop); +} + +static bool pulse_operation_wait(pulse_connection *conn, pa_operation *op) { + if (op == NULL) { + LOG_ERROR("PulseAudio operation failed: %s", pa_strerror(pa_context_errno(conn->ctx))); + return false; + } + + pa_operation_state_t op_state; + while (pulse_connection_check_ready(conn) && (op_state = pa_operation_get_state(op)) == PA_OPERATION_RUNNING) { + pulse_connection_iterate(conn); + } + + pa_operation_unref(op); + + if (!pulse_connection_is_ready(conn)) + return false; + + return op_state == PA_OPERATION_DONE; +} + +static void pulse_stream_state_cb(pa_stream *stream, void *userdata) { + struct pulse *p = userdata; + switch (pa_stream_get_state(stream)) { + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + p->stream_readiness = readiness_unknown; + break; + case PA_STREAM_READY: + p->stream_readiness = readiness_ready; + break; + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + p->stream_readiness = readiness_terminated; + break; + } +} + +static void pulse_stream_success_noop_cb(pa_stream *s, int success, void *userdata) { +} + +static bool pulse_stream_create(struct pulse *p) { + p->sample_spec.rate = output.current_sample_rate; + p->sample_spec.format = PA_SAMPLE_S32LE; // SqueezeLite internally always uses this format, let PulseAudio deal with eventual resampling. + p->sample_spec.channels = 2; + + pa_proplist *proplist = pa_proplist_new(); + pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "music"); + pa_proplist_sets(proplist, PA_PROP_MEDIA_SOFTWARE, "Logitech Media Server"); + + p->stream = pa_stream_new_with_proplist(pulse_connection_get_context(&p->conn), "Logitech Media Server stream", &p->sample_spec, (const pa_channel_map *)NULL, proplist); + pa_proplist_free(proplist); + if (p->stream == NULL) + return false; + + p->stream_readiness = readiness_unknown; + pa_stream_set_state_callback(p->stream, pulse_stream_state_cb, p); + + if (pa_stream_connect_playback(p->stream, p->sink_name, (const pa_buffer_attr *)NULL, +#if PULSEAUDIO_TIMING == 2 + PA_STREAM_VARIABLE_RATE | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING, +#else + PA_STREAM_VARIABLE_RATE, +#endif + (const pa_cvolume *)NULL, (pa_stream *)NULL) < 0) { + pa_stream_unref(p->stream); + p->stream = NULL; + return false; + } + + bool ok; + while ((ok = pulse_connection_check_ready(&p->conn) && p->running) && p->stream_readiness == readiness_unknown) { + pulse_connection_iterate(&p->conn); + } + + ok = ok && p->stream_readiness == readiness_ready; + + if (ok) { + pa_buffer_attr attr = { 0, }; + attr.maxlength = (uint32_t)(-1); + attr.tlength = (uint32_t)(-1); + attr.prebuf = (uint32_t)(-1); + attr.minreq = (uint32_t)(-1); + pa_operation *op = pa_stream_set_buffer_attr(p->stream, &attr, pulse_stream_success_noop_cb, NULL); + ok = pulse_operation_wait(&p->conn, op); + } + + if (!ok) { + pa_stream_disconnect(p->stream); + pa_stream_unref(p->stream); + p->stream = NULL; + } + + return ok; +} + +static void pulse_stream_destroy(struct pulse *p) { + if (p->stream) { + pa_stream_disconnect(p->stream); + pa_stream_unref(p->stream); + p->stream = NULL; + } +} + +static void pulse_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { + if (eol == 0) { + printf(" %-50s %s\n", l->name, l->description); + } else if (eol < 0) { + LOG_WARN("error while listing PulseAudio sinks"); + } +} + +void list_devices(void) { + pulse_connection conn; + if (!pulse_connection_init(&conn)) + return; + + int state = 0; + pa_operation *op; + + while (pulse_connection_check_ready(&conn)) { + if (state == 0) { + printf("Output devices:\n"); + op = pa_context_get_sink_info_list(pulse_connection_get_context(&conn), pulse_sinklist_cb, NULL); + ++state; + } else if (pa_operation_get_state(op) == PA_OPERATION_DONE) { + pa_operation_unref(op); + break; + } + + pulse_connection_iterate(&conn); + } + + pulse_connection_destroy(&conn); +} + +static void pulse_set_volume(struct pulse *p, unsigned left, unsigned right) { + uint32_t sink_input_idx = pa_stream_get_index(p->stream); + pa_cvolume volume; + pa_cvolume_init(&volume); + volume.channels = 2; + volume.values[0] = pa_sw_volume_from_dB(20 * log10(left / 65536.0)); + volume.values[1] = left == right ? volume.values[0] : pa_sw_volume_from_dB(20 * log10(right / 65536.0)); + pa_operation *op = pa_context_set_sink_input_volume(pulse_connection_get_context(&p->conn), sink_input_idx, &volume, NULL, NULL); + if (op != NULL) { + // This is send and forget operation, dereference it right away. + if (loglevel >= lDEBUG) { + char s[20]; + LOG_DEBUG("sink input volume set to %s", pa_cvolume_snprint(s, sizeof(s), &volume)); + } + pa_operation_unref(op); + } +} + +void set_volume(unsigned left, unsigned right) { + bool adjust_sink_input = false; + + LOCK; + adjust_sink_input = (left != output.gainL) || (right != output.gainR); + output.gainL = left; + output.gainR = right; + UNLOCK; + + if (adjust_sink_input && pulse.stream != NULL) { + pulse_set_volume(&pulse, left, right); + } +} + +void set_sample_rate(uint32_t sample_rate) { + pa_operation *op = pa_stream_update_sample_rate(pulse.stream, sample_rate, NULL, NULL); + if (op != NULL) { + if (loglevel >= lDEBUG) { + LOG_DEBUG("stream sample rate set to %d Hz", sample_rate); + } + pa_operation_unref(op); + } + else { + LOG_WARN("failed to set stream sample rate to %d Hz", sample_rate); + } +} + +struct test_open_data { + unsigned *rates; + bool userdef_rates; + pa_sample_spec *sample_spec; + bool is_default_device; + char *default_sink_name; + bool got_device; +}; + +static void pulse_sinkinfo_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { + if (eol) return; + + struct test_open_data *d = userdata; + d->got_device = true; + + if (d->is_default_device) + d->default_sink_name = strdup(l->name); + + if (!d->userdef_rates) { + d->rates[0] = l->sample_spec.rate; + } + + *d->sample_spec = l->sample_spec; +} + +bool test_open(const char *device, unsigned rates[], bool userdef_rates) { + struct test_open_data d = {0, }; + d.rates = rates; + d.userdef_rates = userdef_rates; + d.sample_spec = &pulse.sample_spec; + d.is_default_device = strcmp(device, "default") == 0; + const char *sink_name = d.is_default_device ? NULL : device; + pa_operation *op = pa_context_get_sink_info_by_name(pulse_connection_get_context(&pulse.conn), sink_name, pulse_sinkinfo_cb, &d); + if (!pulse_operation_wait(&pulse.conn, op)) + return false; + if (!d.got_device) + return false; + + pulse.sink_name = d.is_default_device ? d.default_sink_name : (char *)device; + + return true; +} + +static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, + s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { + pa_stream_write(pulse.stream, silence ? silencebuf : outputbuf->readp, out_frames * BYTES_PER_FRAME, (pa_free_cb_t)NULL, 0, PA_SEEK_RELATIVE); + return (int)out_frames; +} + +void output_state_timer_cb(pa_mainloop_api *api, pa_time_event *e, const struct timeval *tv_, void *userdata) { + struct pulse *p = userdata; + pa_context_rttime_restart(pulse_connection_get_context(&p->conn), e, pa_rtclock_now() + OUTPUT_STATE_TIMER_INTERVAL_USEC); +} + +#if PULSEAUDIO_TIMING == 0 +# define DECLARE_LATENCY(n) (void)0 +# define pulse_retrieve_latency(n) true +# define pulse_get_latency(n) 0 +#elif PULSEAUDIO_TIMING == 2 +# define DECLARE_LATENCY(n) pa_usec_t n + static inline bool pulse_retrieve_latency(pa_usec_t *usec) { + return pa_stream_get_latency(pulse.stream, usec, NULL) == 0; + } +#endif + +#if PULSEAUDIO_TIMING > 0 +static inline unsigned pulse_get_latency(pa_usec_t usec) { + return (unsigned)((usec * output.current_sample_rate) / PA_USEC_PER_SEC); +} +#endif + +static void * output_thread(void *arg) { + bool output_off = (output.state == OUTPUT_OFF); + pa_time_event *output_state_timer = NULL; + + while (pulse.running) { + if (output_off) { + if (pulse.stream != NULL) { + LOG_DEBUG("destroying PulseAudio playback stream"); + pulse_stream_destroy(&pulse); + } + + if (output_state_timer == NULL) { + output_state_timer = pa_context_rttime_new(pulse_connection_get_context(&pulse.conn), + pa_rtclock_now() + OUTPUT_STATE_TIMER_INTERVAL_USEC, output_state_timer_cb, &pulse); + } + } else { + if (output_state_timer != NULL) { + pa_mainloop_api *api = pa_mainloop_get_api(pulse.conn.loop); + api->time_free(output_state_timer); + output_state_timer = NULL; + } + + if (pulse.stream == NULL) { + if (pulse_stream_create(&pulse)) { + LOG_DEBUG("PulseAudio playback stream on sink %s open", pulse.sink_name); + + unsigned left, right; + LOCK; + left = output.gainL; + right = output.gainR; + UNLOCK; + pulse_set_volume(&pulse, left, right); + } else { + if (!pulse.running) + break; + output.error_opening = true; + } + } + + if (pulse.stream != NULL) { + size_t writable = pa_stream_writable_size(pulse.stream); + if (writable > 0) { + + DECLARE_LATENCY(latency); + bool latency_ok = pulse_retrieve_latency(&latency); + + frames_t frame_count = writable / pa_sample_size(pa_stream_get_sample_spec(pulse.stream)); + + LOCK; + + if (latency_ok) { + output.device_frames = pulse_get_latency(latency); + output.updated = gettime_ms(); + output.frames_played_dmp = output.frames_played; + } + + _output_frames(frame_count); + + UNLOCK; + } + } + } + + pulse_connection_iterate(&pulse.conn); + + output_off = (output.state == OUTPUT_OFF); + } + + pulse_stream_destroy(&pulse); + + return NULL; +} + +static pthread_t thread; + +void output_init_pulse(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { + loglevel = level; + + LOG_INFO("init output"); + + output.format = 0; + output.start_frames = 0; + output.write_cb = &_write_frames; + output.rate_delay = rate_delay; + + if (!pulse_connection_init(&pulse.conn)) { + // In case of an error, the message is logged by the pulse_connection_init itself. + exit(1); + } + + output_init_common(level, device, output_buf_size, rates, idle); + + // start output thread + pulse.running = true; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); + pthread_create(&thread, &attr, output_thread, NULL); + pthread_attr_destroy(&attr); +} + +void output_close_pulse(void) { + LOG_INFO("close output"); + + pulse.running = false; + pa_mainloop_wakeup(pulse.conn.loop); + pthread_join(thread, NULL); + + if (output.device != pulse.sink_name) + free(pulse.sink_name); + + pulse_connection_destroy(&pulse.conn); + + output_close_common(); +} + +#endif diff --git a/output_stdout.c b/output_stdout.c index 37544ab..93c2359 100644 --- a/output_stdout.c +++ b/output_stdout.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,7 +37,7 @@ extern u8_t *silencebuf; #if DSD -extern u8_t *silencebuf_dop; +extern u8_t *silencebuf_dsd; #endif // buffer to hold output data so we can block on writing outside of output lock, allocated on init @@ -63,11 +64,14 @@ } IF_DSD( - if (output.dop) { + if (output.outfmt != PCM) { if (silence) { - obuf = silencebuf_dop; + obuf = silencebuf_dsd; } - update_dop((u32_t *)obuf, out_frames, output.invert && !silence); + if (output.outfmt == DOP) + update_dop((u32_t *)obuf, out_frames, output.invert && !silence); + else if (output.invert && !silence) + dsd_invert((u32_t *)obuf, out_frames); } ) @@ -155,7 +159,9 @@ #if LINUX || OSX || FREEBSD pthread_attr_t attr; pthread_attr_init(&attr); +#ifdef PTHREAD_STACK_MIN pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); +#endif pthread_create(&thread, &attr, output_thread, NULL); pthread_attr_destroy(&attr); #endif diff --git a/output_vis.c b/output_vis.c index 087836e..ef12f6f 100644 --- a/output_vis.c +++ b/output_vis.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +19,7 @@ * */ -// Export audio samples for visualiser process (16 bit only best endevours) +// Export audio samples for visualizer process (16 bit only best endevours) #include "squeezelite.h" @@ -27,6 +28,18 @@ #include #include #include +#if OSX +#include +#include + +static int pthread_rwlock_timedwrlock( pthread_rwlock_t * restrict rwlock, const struct timespec * restrict abs_timeout ) +{ + ( void )rwlock; + ( void )abs_timeout; + + return 0; +} +#endif #define VIS_BUF_SIZE 16384 #define VIS_LOCK_NS 1000000 // ns to wait for vis wrlock @@ -55,7 +68,17 @@ err = pthread_rwlock_trywrlock(&vis_mmap->rwlock); if (err) { struct timespec ts; +#if OSX + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + ts.tv_sec = mts.tv_sec; + ts.tv_nsec = mts.tv_nsec; +#else clock_gettime(CLOCK_REALTIME, &ts); +#endif ts.tv_nsec += VIS_LOCK_NS; if (ts.tv_nsec > 1000000000) { ts.tv_sec += 1; @@ -116,6 +139,10 @@ mode_t old_mask = umask(000); // allow any user to read our shm when created +#if OSX + /* ftruncate on MacOS only works on the initial segment creation */ + shm_unlink(vis_shm_path); +#endif vis_fd = shm_open(vis_shm_path, O_CREAT | O_RDWR, 0666); if (vis_fd != -1) { if (ftruncate(vis_fd, sizeof(struct vis_t)) == 0) { diff --git a/pcm.c b/pcm.c index c867ef5..da62520 100644 --- a/pcm.c +++ b/pcm.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +21,14 @@ #include "squeezelite.h" +#if BYTES_PER_FRAME == 4 +#define SHIFT 16 +#define OPTR_T u16_t +#else +#define OPTR_T u32_t +#define SHIFT 0 +#endif + extern log_level loglevel; extern struct buffer *streambuf; @@ -28,6 +37,8 @@ extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; + +bool pcm_check_header = false; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) @@ -52,7 +63,7 @@ #define MAX_DECODE_FRAMES 4096 static u32_t sample_rates[] = { - 11025, 22050, 32000, 44100, 48000, 8000, 12000, 16000, 24000, 96000, 88200, 176400, 192000, 352800, 384000 + 11025, 22050, 32000, 44100, 48000, 8000, 12000, 16000, 24000, 96000, 88200, 176400, 192000, 352800, 384000, 705600, 768000 }; static u32_t sample_rate; @@ -104,8 +115,14 @@ ptr += 8; _buf_inc_readp(streambuf, ptr - streambuf->readp); audio_left = len; - LOG_INFO("audio size: %u", audio_left); - limit = true; + + if ((audio_left == 0xFFFFFFFF) || (audio_left == 0x7FFFEFFC)) { + LOG_INFO("wav audio size unknown: %u", audio_left); + limit = false; + } else { + LOG_INFO("wav audio size: %u", audio_left); + limit = true; + } return; } @@ -114,9 +131,14 @@ // following 4 bytes is blocksize - ignored ptr += 8 + 8; _buf_inc_readp(streambuf, ptr + offset - streambuf->readp); - audio_left = len - 8 - offset; - LOG_INFO("audio size: %u", audio_left); - limit = true; + + // Reading from an upsampled stream, length could be wrong. + // Only use length in header for files. + if (stream.state == STREAMING_FILE) { + audio_left = len - 8 - offset; + LOG_INFO("aif audio size: %u", audio_left); + limit = true; + } return; } @@ -161,13 +183,13 @@ static decode_state pcm_decode(void) { unsigned bytes, in, out; frames_t frames, count; - u32_t *optr; + OPTR_T *optr; u8_t *iptr; - u8_t tmp[16]; + u8_t tmp[3*8]; LOCK_S; - if (decode.new_stream && stream.state == STREAMING_FILE) { + if ( decode.new_stream && ( ( stream.state == STREAMING_FILE ) || pcm_check_header ) ) { _check_header(); } @@ -182,7 +204,7 @@ out = process.max_in_frames; ); - if ((stream.state <= DISCONNECT && bytes == 0) || (limit && audio_left == 0)) { + if ((stream.state <= DISCONNECT && bytes < bytes_per_frame) || (limit && audio_left == 0)) { UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; @@ -191,11 +213,29 @@ if (decode.new_stream) { LOG_INFO("setting track_start"); LOCK_O_not_direct; + output.track_start = outputbuf->writep; + decode.new_stream = false; +#if DSD + if (sample_size == 3 && + is_stream_dop(((u8_t *)streambuf->readp) + (bigendian?0:2), + ((u8_t *)streambuf->readp) + (bigendian?0:2) + sample_size, + sample_size * channels, bytes / (sample_size * channels))) { + LOG_INFO("file contains DOP"); + if (output.dsdfmt == DOP_S24_LE || output.dsdfmt == DOP_S24_3LE) + output.next_fmt = output.dsdfmt; + else + output.next_fmt = DOP; + output.next_sample_rate = sample_rate; + output.fade = FADE_INACTIVE; + } else { + output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); + output.next_fmt = PCM; + if (output.fade_mode) _checkfade(true); + } +#else output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); - output.track_start = outputbuf->writep; - IF_DSD( output.next_dop = false; ) if (output.fade_mode) _checkfade(true); - decode.new_stream = false; +#endif UNLOCK_O_not_direct; IF_PROCESS( out = process.max_in_frames; @@ -204,10 +244,10 @@ } IF_DIRECT( - optr = (u32_t *)outputbuf->writep; + optr = (OPTR_T *)outputbuf->writep; ); IF_PROCESS( - optr = (u32_t *)process.inbuf; + optr = (OPTR_T *)process.inbuf; ); iptr = (u8_t *)streambuf->readp; @@ -235,41 +275,67 @@ if (channels == 2) { if (sample_size == 1) { while (count--) { - *optr++ = *iptr++ << 24; + *optr++ = *iptr++ << (24-SHIFT); } } else if (sample_size == 2) { if (bigendian) { - while (count--) { - *optr++ = *(iptr) << 24 | *(iptr+1) << 16; +#if BYTES_PER_FRAME == 4 && !SL_LITTLE_ENDIAN + // while loop below works as is, but memcpy is a win for that 16/16 typical case + memcpy(optr, iptr, count * BYTES_PER_FRAME / 2); +#else + while (count--) { + *optr++ = *(iptr) << (24-SHIFT) | *(iptr+1) << (16-SHIFT); iptr += 2; } - } else { - while (count--) { - *optr++ = *(iptr) << 16 | *(iptr+1) << 24; +#endif + } else { +#if BYTES_PER_FRAME == 4 && SL_LITTLE_ENDIAN + // while loop below works as is, but memcpy is a win for that 16/16 typical case + memcpy(optr, iptr, count * BYTES_PER_FRAME / 2); +#else + while (count--) { + *optr++ = *(iptr) << (16-SHIFT) | *(iptr+1) << (24-SHIFT); iptr += 2; } +#endif } } else if (sample_size == 3) { if (bigendian) { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr) << 8 | *(iptr+1); +#else *optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8; +#endif iptr += 3; } } else { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr+1) | *(iptr+2) << 8; +#else *optr++ = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24; +#endif iptr += 3; } } } else if (sample_size == 4) { if (bigendian) { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr) << 8 | *(iptr+1); +#else *optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3); +#endif iptr += 4; } } else { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr+2) | *(iptr+3) << 8; +#else *optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24; +#endif iptr += 4; } } @@ -277,21 +343,21 @@ } else if (channels == 1) { if (sample_size == 1) { while (count--) { - *optr = *iptr++ << 24; + *optr = *iptr++ << (24-SHIFT); *(optr+1) = *optr; optr += 2; } } else if (sample_size == 2) { if (bigendian) { while (count--) { - *optr = *(iptr) << 24 | *(iptr+1) << 16; + *optr = *(iptr) << (24-SHIFT) | *(iptr+1) << (16-SHIFT); *(optr+1) = *optr; iptr += 2; optr += 2; } } else { while (count--) { - *optr = *(iptr) << 16 | *(iptr+1) << 24; + *optr = *(iptr) << (16-SHIFT) | *(iptr+1) << (24-SHIFT); *(optr+1) = *optr; iptr += 2; optr += 2; @@ -300,14 +366,22 @@ } else if (sample_size == 3) { if (bigendian) { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr) << 8 | *(iptr+1); +#else *optr = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8; +#endif *(optr+1) = *optr; iptr += 3; optr += 2; } } else { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr+1) | *(iptr+2) << 8; +#else *optr = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24; +#endif *(optr+1) = *optr; iptr += 3; optr += 2; @@ -316,14 +390,22 @@ } else if (sample_size == 4) { if (bigendian) { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr) << 8 | *(iptr+1); +#else *optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3); +#endif *(optr+1) = *optr; iptr += 4; optr += 2; } } else { while (count--) { +#if BYTES_PER_FRAME == 4 + *optr++ = *(iptr+2) | *(iptr+3) << 8; +#else *optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24; +#endif *(optr+1) = *optr; iptr += 4; optr += 2; @@ -371,16 +453,36 @@ } struct codec *register_pcm(void) { - static struct codec ret = { - 'p', // id - "aif,pcm", // types - 4096, // min read - 102400, // min space - pcm_open, // open - pcm_close, // close - pcm_decode, // decode - }; - - LOG_INFO("using pcm to decode aif,pcm"); - return &ret; + if ( pcm_check_header ) + { + static struct codec ret = { + 'p', // id + "wav,aif,pcm", // types + 4096, // min read + 102400, // min space + pcm_open, // open + pcm_close, // close + pcm_decode, // decode + }; + + LOG_INFO("using pcm to decode wav,aif,pcm"); + return &ret; + } + else + { + static struct codec ret = { + 'p', // id + "aif,pcm", // types + 4096, // min read + 102400, // min space + pcm_open, // open + pcm_close, // close + pcm_decode, // decode + }; + + LOG_INFO("using pcm to decode aif,pcm"); + return &ret; + } + + return NULL; } diff --git a/process.c b/process.c index 6a107c6..fa1dab5 100644 --- a/process.c +++ b/process.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,7 +50,7 @@ // transfer all processed frames to the output buf static void _write_samples(void) { - size_t frames = process.out_frames; + frames_t frames = process.out_frames; u32_t *iptr = (u32_t *)process.outbuf; unsigned cnt = 10; diff --git a/resample.c b/resample.c index 6157638..1ba5084 100644 --- a/resample.c +++ b/resample.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/rsc/app.bmp b/rsc/app.bmp new file mode 100644 index 0000000..bd58398 Binary files /dev/null and b/rsc/app.bmp differ diff --git a/rsc/icon.icns b/rsc/icon.icns new file mode 100644 index 0000000..217952c Binary files /dev/null and b/rsc/icon.icns differ diff --git a/rsc/jiveapp.icns b/rsc/jiveapp.icns new file mode 100644 index 0000000..dea58f8 Binary files /dev/null and b/rsc/jiveapp.icns differ diff --git a/rsc/jiveapp.ico b/rsc/jiveapp.ico new file mode 100644 index 0000000..cffff5e Binary files /dev/null and b/rsc/jiveapp.ico differ diff --git a/slimproto.c b/slimproto.c index 81d2f30..a9a3340 100644 --- a/slimproto.c +++ b/slimproto.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,12 +17,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * + * Additions (c) Paul Hermann, 2015-2017 under the same license terms + * -Control of Raspberry pi GPIO for amplifier power + * -Launch script on power status change from LMS */ #include "squeezelite.h" #include "slimproto.h" static log_level loglevel; + +#define SQUEEZENETWORK "mysqueezebox.com:3483" #define PORT 3483 @@ -108,8 +114,20 @@ } static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) { - const char *base_cap = "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION; + #define BASE_CAP "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION + #define SSL_CAP "CanHTTPS=1" + const char *base_cap; struct HELO_packet pkt; + +#if USE_SSL +#if !LINKALL && !NO_SSLSYM + if (ssl_loaded) base_cap = SSL_CAP "," BASE_CAP; + else base_cap = BASE_CAP; +#endif + base_cap = SSL_CAP "," BASE_CAP; +#else + base_cap = BASE_CAP; +#endif memset(&pkt, 0, sizeof(pkt)); memcpy(&pkt.opcode, "HELO", 4); @@ -283,7 +301,7 @@ output.pause_frames = interval * status.current_sample_rate / 1000; if (interval) { output.state = OUTPUT_PAUSE_FRAMES; - } else { + } else if (output.state != OUTPUT_OFF) { output.state = OUTPUT_STOPPED; output.stop_time = gettime_ms(); } @@ -309,6 +327,7 @@ output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING; output.start_at = jiffies; UNLOCK_O; + LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms()); sendSTAT("STMr", 0); } @@ -325,6 +344,7 @@ strm->autostart, strm->transition_period, strm->transition_type - '0', strm->format); autostart = strm->autostart - '0'; + sendSTAT("STMf", 0); if (header_len > MAX_HEADER -1) { LOG_WARN("header too long: %u", header_len); @@ -344,7 +364,7 @@ stream_file(header, header_len, strm->threshold * 1024); autostart -= 2; } else { - stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2); + stream_sock(ip, port, strm->flags & 0x20, header, header_len, strm->threshold * 1024, autostart >= 2); } sendSTAT("STMc", 0); sentSTMu = sentSTMo = sentSTMl = false; @@ -451,9 +471,16 @@ static void process_serv(u8_t *pkt, int len) { struct serv_packet *serv = (struct serv_packet *)pkt; + unsigned slimproto_port = 0; + char squeezeserver[] = SQUEEZENETWORK; + + if(pkt[4] == 0 && pkt[5] == 0 && pkt[6] == 0 && pkt[7] == 1) { + server_addr(squeezeserver, &new_server, &slimproto_port); + } else { + new_server = serv->server_ip; + } + LOG_INFO("switch server"); - - new_server = serv->server_ip; if (len - sizeof(struct serv_packet) == 10) { if (!new_server_cap) { @@ -600,6 +627,7 @@ #endif last = now; + LOCK_S; status.stream_full = _buf_used(streambuf); status.stream_size = streambuf->size; @@ -627,8 +655,9 @@ UNLOCK_S; LOCK_D; - if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !sentSTMl - && decode.state == DECODE_READY) { + if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE || + (status.stream_state == DISCONNECT && stream.disconnect == DISCONNECT_OK)) && + !sentSTMl && decode.state == DECODE_READY) { if (autostart == 0) { decode.state = DECODE_RUNNING; _sendSTMl = true; @@ -669,11 +698,12 @@ output.pa_reopen = false; } #endif - if (_start_output && (output.state == OUTPUT_STOPPED || OUTPUT_OFF)) { + if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) { output.state = OUTPUT_BUFFER; } if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT && _decode_state == DECODE_STOPPED) { + _sendSTMu = true; sentSTMu = true; LOG_DEBUG("output underrun"); @@ -681,6 +711,7 @@ output.stop_time = now; } if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) { + _sendSTMo = true; sentSTMo = true; } @@ -730,11 +761,12 @@ wake_signal(wake_e); } -in_addr_t discover_server(void) { +in_addr_t discover_server(char *default_server) { struct sockaddr_in d; struct sockaddr_in s; char *buf; struct pollfd pollinfo; + unsigned port; int disc_sock = socket(AF_INET, SOCK_DGRAM, 0); @@ -767,6 +799,10 @@ LOG_INFO("got response from: %s:%d", inet_ntoa(s.sin_addr), ntohs(s.sin_port)); } + if (default_server) { + server_addr(default_server, &s.sin_addr.s_addr, &port); + } + } while (s.sin_addr.s_addr == 0 && running); closesocket(disc_sock); @@ -777,13 +813,16 @@ #define FIXED_CAP_LEN 256 #define VAR_CAP_LEN 128 -void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname) { +void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate) { struct sockaddr_in serv_addr; static char fixed_cap[FIXED_CAP_LEN], var_cap[VAR_CAP_LEN] = ""; bool reconnect = false; unsigned failed_connect = 0; unsigned slimproto_port = 0; + in_addr_t previous_server = 0; int i; + + memset(&status, 0, sizeof(status)); wake_create(wake_e); @@ -795,7 +834,7 @@ } if (!slimproto_ip) { - slimproto_ip = discover_server(); + slimproto_ip = discover_server(server); } if (!slimproto_port) { @@ -830,7 +869,7 @@ LOCK_O; snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING, - output.supported_rates[0]); + ((maxSampleRate > 0) ? maxSampleRate : output.supported_rates[0])); for (i = 0; i < MAX_CODECS; i++) { if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < FIXED_CAP_LEN - 10) { @@ -852,6 +891,7 @@ while (running) { if (new_server) { + previous_server = slimproto_ip; slimproto_ip = serv_addr.sin_addr.s_addr = new_server; LOG_INFO("switching server to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port)); new_server = 0; @@ -865,12 +905,17 @@ if (connect_timeout(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr), 5) != 0) { - LOG_INFO("unable to connect to server %u", failed_connect); - sleep(5); + if (previous_server) { + slimproto_ip = serv_addr.sin_addr.s_addr = previous_server; + LOG_INFO("new server not reachable, reverting to previous server %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port)); + } else { + LOG_INFO("unable to connect to server %u", failed_connect); + sleep(5); + } // rediscover server if it was not set at startup if (!server && ++failed_connect > 5) { - slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(); + slimproto_ip = serv_addr.sin_addr.s_addr = discover_server(NULL); } } else { @@ -911,6 +956,8 @@ usleep(100000); } + previous_server = 0; + closesocket(sock); } } diff --git a/slimproto.h b/slimproto.h index 319ee8d..e4af804 100644 --- a/slimproto.h +++ b/slimproto.h @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +21,11 @@ // packet formats for slimproto +#ifndef SUN #pragma pack(push, 1) +#else +#pragma pack(1) +#endif // from S:N:Slimproto _hello_handler struct HELO_packet { @@ -173,4 +178,8 @@ u8_t pcm_endianness; }; +#ifndef SUN #pragma pack(pop) +#else +#pragma pack() +#endif diff --git a/squeezelite-dll.sln b/squeezelite-dll.sln new file mode 100755 index 0000000..fa9d0b1 --- /dev/null +++ b/squeezelite-dll.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C++ Express 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "squeezelite-dll", "squeezelite-dll.vcproj", "{7CB5A216-CEFA-4932-93CD-E9BFA33DD349}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.ActiveCfg = Debug|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.Build.0 = Debug|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.ActiveCfg = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/squeezelite-dll.vcproj b/squeezelite-dll.vcproj new file mode 100755 index 0000000..7cbcd11 --- /dev/null +++ b/squeezelite-dll.vcproj @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/squeezelite-x64.sln b/squeezelite-x64.sln new file mode 100644 index 0000000..267d54a --- /dev/null +++ b/squeezelite-x64.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1062 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "squeezelite-x64", "squeezelite-x64.vcxproj", "{7CB5A216-CEFA-4932-93CD-E9BFA33DD349}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.ActiveCfg = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.Build.0 = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|x64.ActiveCfg = Debug|x64 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|x64.Build.0 = Debug|x64 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.ActiveCfg = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.Build.0 = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|x64.ActiveCfg = Release|x64 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BB7D547A-95FA-4A5F-82FC-E013A35756B4} + EndGlobalSection +EndGlobal diff --git a/squeezelite-x64.vcxproj b/squeezelite-x64.vcxproj new file mode 100644 index 0000000..55b80f7 --- /dev/null +++ b/squeezelite-x64.vcxproj @@ -0,0 +1,218 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349} + squeezelite + Win32Proj + + + + Application + v141 + + + Application + v141 + + + Application + v141 + + + Application + v141 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>15.0.28307.799 + + + Debug\ + Debug\ + true + + + true + + + Release\ + Release\ + false + + + false + + + + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + + + true + Console + MachineX86 + + + + + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + true + Console + + + + + include\alac;include\opus;include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;RESAMPLE;ALAC;OPUS;DSD;USE_SSL;FLAC__NO_DLL;LINKALL;%(PreprocessorDefinitions) + MultiThreaded + + Level3 + + + + /NODEFAULTLIB:LIBCMT %(AdditionalOptions) + ws2_32.lib;lib\portaudio.lib;lib\libogg.lib;lib\libvorbisfile.lib;lib\libvorbis.lib;lib\libmad.lib;lib\libFLAC.lib;lib\libfaad.lib;lib\libmpg123.lib;lib\libsoxr.lib;lib\libalac.lib;lib\ssleay32.lib;lib\libeay32.lib;lib\opus.lib;lib\opusfile.lib;%(AdditionalDependencies) + $(OutDir)$(ProjectName).exe + + false + Console + true + true + MachineX86 + + + + + include\alac;include\opus;include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;RESAMPLE;DSD;ALAC;OPUS;USE_SSL;FLAC__NO_DLL;LINKALL;%(PreprocessorDefinitions) + MultiThreaded + + + Level3 + + + + + /NODEFAULTLIB:LIBCMT %(AdditionalOptions) + ws2_32.lib;crypt32.lib;lib\portaudio.lib;lib\libogg.lib;lib\libvorbisfile.lib;lib\libvorbis.lib;lib\libmad.lib;lib\libFLAC.lib;lib\libfaad.lib;lib\libmpg123.lib;lib\libsoxr.lib;lib\libalac.lib;lib\libssl.lib;lib\libcrypto.lib;lib\opus.lib;lib\opusfile.lib;%(AdditionalDependencies) + $(OutDir)$(ProjectName).exe + + + false + Console + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/squeezelite-x86.sln b/squeezelite-x86.sln new file mode 100644 index 0000000..1f14a80 --- /dev/null +++ b/squeezelite-x86.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "squeezelite-x86", "squeezelite-x86.vcproj", "{7CB5A216-CEFA-4932-93CD-E9BFA33DD349}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.ActiveCfg = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.Build.0 = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.ActiveCfg = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/squeezelite-x86.vcproj b/squeezelite-x86.vcproj new file mode 100755 index 0000000..fffbd6e --- /dev/null +++ b/squeezelite-x86.vcproj @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/squeezelite.h b/squeezelite.h index 455fdfd..202321c 100644 --- a/squeezelite.h +++ b/squeezelite.h @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,11 +17,23 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * + * Additions (c) Paul Hermann, 2015-2017 under the same license terms + * -Control of Raspberry pi GPIO for amplifier power + * -Launch script on power status change from LMS */ -// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, IR, DSD, LINKALL to influence build - -#define VERSION "v1.8" +// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, GPIO, IR, DSD, LINKALL to influence build + +#define MAJOR_VERSION "1.9" +#define MINOR_VERSION "8" +#define MICRO_VERSION "1307" + +#if defined(CUSTOM_VERSION) +#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION STR(CUSTOM_VERSION) +#else +#define VERSION "v" MAJOR_VERSION "." MINOR_VERSION "-" MICRO_VERSION +#endif + #if !defined(MODEL_NAME) #define MODEL_NAME SqueezeLite @@ -40,30 +53,42 @@ #define LINUX 0 #define OSX 1 #define WIN 0 +#define PORTAUDIO 1 #define FREEBSD 0 #elif defined (_MSC_VER) #define LINUX 0 #define OSX 0 #define WIN 1 +#define PORTAUDIO 1 #define FREEBSD 0 #elif defined(__FreeBSD__) #define LINUX 0 #define OSX 0 #define WIN 0 +#define PORTAUDIO 1 #define FREEBSD 1 +#elif defined (__sun) +#define SUN 1 +#define LINUX 1 +#define PORTAUDIO 1 +#define PA18API 1 +#define OSX 0 +#define WIN 0 #else #error unknown target #endif -#if LINUX && !defined(PORTAUDIO) -#define ALSA 1 -#define PORTAUDIO 0 -#else -#define ALSA 0 -#define PORTAUDIO 1 -#endif - -#if LINUX && !defined(SELFPIPE) +#if LINUX && !defined(PORTAUDIO) && !defined(PULSEAUDIO) +#define ALSA 1 +#else +#define ALSA 0 +#endif + +#if SUN +#define EVENTFD 0 +#define WINEVENT 0 +#define SELFPIPE 1 +#elif LINUX && !defined(SELFPIPE) #define EVENTFD 1 #define SELFPIPE 0 #define WINEVENT 0 @@ -94,6 +119,13 @@ #define RESAMPLE_MP 0 #endif +#if defined(ALAC) +#undef ALAC +#define ALAC 1 +#else +#define ALAC 0 +#endif + #if defined(FFMPEG) #undef FFMPEG #define FFMPEG 1 @@ -101,7 +133,14 @@ #define FFMPEG 0 #endif -#if LINUX && defined(VISEXPORT) +#if defined(OPUS) +#undef OPUS +#define OPUS 1 +#else +#define OPUS 0 +#endif + +#if (LINUX || OSX) && defined(VISEXPORT) #undef VISEXPORT #define VISEXPORT 1 // visulizer export support uses linux shared memory #else @@ -132,6 +171,19 @@ #define LINKALL 0 #endif +#if defined (USE_SSL) +#undef USE_SSL +#define USE_SSL 1 +#else +#define USE_SSL 0 +#endif + +#if defined (NO_SSLSYM) +#undef NO_SSLSYM +#define NO_SSLSYM 1 +#else +#define NO_SSLSYM 0 +#endif #if !LINKALL @@ -142,6 +194,7 @@ #define LIBMAD "libmad.so.0" #define LIBMPG "libmpg123.so.0" #define LIBVORBIS "libvorbisfile.so.3" +#define LIBOPUS "libopusfile.so.0" #define LIBTREMOR "libvorbisidec.so.1" #define LIBFAAD "libfaad.so.2" #define LIBAVUTIL "libavutil.so.%d" @@ -157,6 +210,7 @@ #define LIBMPG "libmpg123.0.dylib" #define LIBVORBIS "libvorbisfile.3.dylib" #define LIBTREMOR "libvorbisidec.1.dylib" +#define LIBOPUS "libopusfile.0.dylib" #define LIBFAAD "libfaad.2.dylib" #define LIBAVUTIL "libavutil.%d.dylib" #define LIBAVCODEC "libavcodec.%d.dylib" @@ -169,6 +223,7 @@ #define LIBMAD "libmad-0.dll" #define LIBMPG "libmpg123-0.dll" #define LIBVORBIS "libvorbisfile.dll" +#define LIBOPUS "libopusfile-0.dll" #define LIBTREMOR "libvorbisidec.dll" #define LIBFAAD "libfaad2.dll" #define LIBAVUTIL "avutil-%d.dll" @@ -178,15 +233,17 @@ #endif #if FREEBSD -#define LIBFLAC "libFLAC.so.11" -#define LIBMAD "libmad.so.2" +#define LIBFLAC "libFLAC.so.8" +#define LIBMAD "libmad.so.0" #define LIBMPG "libmpg123.so.0" -#define LIBVORBIS "libvorbisfile.so.6" +#define LIBVORBIS "libvorbisfile.so.3" #define LIBTREMOR "libvorbisidec.so.1" +#define LIBOPUS "libopusfile.so.1" #define LIBFAAD "libfaad.so.2" #define LIBAVUTIL "libavutil.so.%d" #define LIBAVCODEC "libavcodec.so.%d" #define LIBAVFORMAT "libavformat.so.%d" +#define LIBSOXR "libsoxr.so.0" #endif #endif // !LINKALL @@ -205,6 +262,10 @@ #endif #define SL_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + +#if SUN || OSXPPC +#undef SL_LITTLE_ENDIAN +#endif #include #include @@ -225,20 +286,32 @@ #include #include #include +#if SUN +#include +#endif /* SUN */ #define STREAM_THREAD_STACK_SIZE 64 * 1024 #define DECODE_THREAD_STACK_SIZE 128 * 1024 #define OUTPUT_THREAD_STACK_SIZE 64 * 1024 #define IR_THREAD_STACK_SIZE 64 * 1024 +#if !OSX #define thread_t pthread_t; +#endif #define closesocket(s) close(s) #define last_error() errno #define ERROR_WOULDBLOCK EWOULDBLOCK +#ifdef SUN +typedef uint8_t u8_t; +typedef uint16_t u16_t; +typedef uint32_t u32_t; +typedef uint64_t u64_t; +#else typedef u_int8_t u8_t; typedef u_int16_t u16_t; typedef u_int32_t u32_t; typedef u_int64_t u64_t; +#endif /* SUN */ typedef int16_t s16_t; typedef int32_t s32_t; typedef int64_t s64_t; @@ -342,7 +415,7 @@ #if (LINUX && __WORDSIZE == 64) || (FREEBSD && __LP64__) #define FMT_u64 "%lu" #define FMT_x64 "%lx" -#elif __GLIBC_HAVE_LONG_LONG || defined __GNUC__ || WIN +#elif __GLIBC_HAVE_LONG_LONG || defined __GNUC__ || WIN || SUN #define FMT_u64 "%llu" #define FMT_x64 "%llx" #else @@ -354,6 +427,12 @@ #define FIXED_ONE 0x10000 #define BYTES_PER_FRAME 8 + +#if BYTES_PER_FRAME == 8 +#define ISAMPLE_T s32_t +#else +#define ISAMPLE_T s16_t +#endif #define min(a,b) (((a) < (b)) ? (a) : (b)) @@ -371,6 +450,9 @@ // utils.c (non logging) typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type; +#if WIN && USE_SSL +char* strcasestr(const char *haystack, const char *needle); +#endif char *next_param(char *src, char c); u32_t gettime_ms(void); @@ -389,6 +471,10 @@ #else #define set_nosigpipe(s) #endif +#if SUN +void init_daemonize(void); +int daemon(int,int); +#endif #if WIN void winsock_init(void); void winsock_close(void); @@ -420,13 +506,14 @@ void _buf_inc_readp(struct buffer *buf, unsigned by); void _buf_inc_writep(struct buffer *buf, unsigned by); void buf_flush(struct buffer *buf); +void _buf_unwrap(struct buffer *buf, size_t cont); void buf_adjust(struct buffer *buf, size_t mod); void _buf_resize(struct buffer *buf, size_t size); void buf_init(struct buffer *buf, size_t size); void buf_destroy(struct buffer *buf); // slimproto.c -void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname); +void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname, int maxSampleRate); void slimproto_stop(void); void wake_controller(void); @@ -453,7 +540,7 @@ void stream_init(log_level level, unsigned stream_buf_size); void stream_close(void); void stream_file(const char *header, size_t header_len, unsigned threshold); -void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait); +void stream_sock(u32_t ip, u16_t port, bool use_ssl, const char *header, size_t header_len, unsigned threshold, bool cont_wait); bool stream_disconnect(void); // decode.c @@ -517,14 +604,19 @@ typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING, OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state; +#if DSD +typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format; +typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE, U8, U16_LE, U16_BE, U32_LE, U32_BE } output_format; +#else typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE } output_format; +#endif typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state; typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir; typedef enum { FADE_NONE = 0, FADE_CROSSFADE, FADE_IN, FADE_OUT, FADE_INOUT } fade_mode; -#define MAX_SUPPORTED_SAMPLERATES 16 -#define TEST_RATES = { 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 } +#define MAX_SUPPORTED_SAMPLERATES 18 +#define TEST_RATES = { 768000, 705600, 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 } struct outputstate { output_state state; @@ -538,7 +630,7 @@ #if PORTAUDIO bool pa_reopen; unsigned latency; - int osx_playnice; + int pa_hostapi_option; #endif int (* write_cb)(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr); unsigned start_frames; @@ -575,10 +667,10 @@ u32_t stop_time; u32_t idle_to; #if DSD - bool next_dop; // set in decode thread - bool dop; - bool has_dop; // set in dop_init - output device supports dop - unsigned dop_delay; // set in dop_init - delay in ms switching to/from dop + dsd_format next_fmt; // set in decode thread + dsd_format outfmt; + dsd_format dsdfmt; // set in dsd_init - output for DSD: DOP, DSD_U8, ... + unsigned dsd_delay; // set in dsd_init - delay in ms switching to/from dop #endif }; @@ -594,9 +686,8 @@ void list_devices(void); void list_mixers(const char *output_device); void set_volume(unsigned left, unsigned right); -bool test_open(const char *device, unsigned rates[]); -void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], - unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute); +bool test_open(const char *device, unsigned rates[], bool userdef_rates); +void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *mixer_device, char *volume_mixer, bool mixer_unmute, bool mixer_linear); void output_close_alsa(void); #endif @@ -604,10 +695,20 @@ #if PORTAUDIO void list_devices(void); void set_volume(unsigned left, unsigned right); -bool test_open(const char *device, unsigned rates[]); +bool test_open(const char *device, unsigned rates[], bool userdef_rates); void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle); void output_close_pa(void); void _pa_open(void); +#endif + +// output_pulse.c +#if PULSEAUDIO +void list_devices(void); +void set_volume(unsigned left, unsigned right); +void set_sample_rate(uint32_t sample_rate); +bool test_open(const char *device, unsigned rates[], bool userdef_rates); +void output_init_pulse(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle); +void output_close_pulse(void); #endif // output_stdout.c @@ -633,23 +734,51 @@ // dop.c #if DSD -bool is_flac_dop(u32_t *lptr, u32_t *rptr, frames_t frames); +bool is_stream_dop(u8_t *lptr, u8_t *rptr, int step, frames_t frames); void update_dop(u32_t *ptr, frames_t frames, bool invert); -void dop_silence_frames(u32_t *ptr, frames_t frames); -void dop_init(bool enable, unsigned delay); +void dsd_silence_frames(u32_t *ptr, frames_t frames); +void dsd_invert(u32_t *ptr, frames_t frames); +void dsd_init(dsd_format format, unsigned delay); #endif // codecs -#define MAX_CODECS 9 +#define MAX_CODECS 10 struct codec *register_flac(void); struct codec *register_pcm(void); struct codec *register_mad(void); struct codec *register_mpg(void); struct codec *register_vorbis(void); +#if ALAC +struct codec *register_alac(void); +#endif struct codec *register_faad(void); struct codec *register_dsd(void); struct codec *register_ff(const char *codec); +#if OPUS +struct codec *register_opus(void); +#endif + +//gpio.c +#if GPIO +void relay( int state); +void relay_script(int state); +int gpio_pin; +bool gpio_active_low; +bool gpio_active; +char *power_script; +// my amp state +int ampstate; +#if RPI +#define PI_INPUT 0 +#define PI_OUTPUT 1 +#define PI_LOW 0 +#define PI_HIGH 1 +void gpioSetMode(unsigned gpio, unsigned mode); +void gpioWrite(unsigned gpio, unsigned level); +int gpioInitialise(void); +#endif +#endif // ir.c #if IR @@ -662,3 +791,11 @@ void ir_init(log_level level, char *lircrc); void ir_close(void); #endif + +// sslsym.c +#if USE_SSL && !LINKALL && !NO_SSLSYM +bool load_ssl_symbols(void); +void free_ssl_symbols(void); +bool ssl_loaded; +#endif + diff --git a/squeezelite.sln b/squeezelite.sln new file mode 100755 index 0000000..f68388b --- /dev/null +++ b/squeezelite.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "squeezelite", "squeezelite.vcproj", "{7CB5A216-CEFA-4932-93CD-E9BFA33DD349}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.ActiveCfg = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Debug|Win32.Build.0 = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.ActiveCfg = Release|Win32 + {7CB5A216-CEFA-4932-93CD-E9BFA33DD349}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/squeezelite.vcproj b/squeezelite.vcproj new file mode 100755 index 0000000..13ab395 --- /dev/null +++ b/squeezelite.vcproj @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sslsym.c b/sslsym.c new file mode 100644 index 0000000..a6dafe7 --- /dev/null +++ b/sslsym.c @@ -0,0 +1,166 @@ +/* + * SSL symbols dynamic loader + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "squeezelite.h" + +#if USE_SSL && !LINKALL && !NO_SSLSYM + +#if WIN +#define dlclose FreeLibrary +#else +#include +#endif + +#include "openssl/ssl.h" +#include "openssl/err.h" +#include +#include +#include +#include +#include + +static void *SSLhandle = NULL; +static void *CRYPThandle = NULL; + +#if WIN +static char *LIBSSL[] = { "libssl.dll", + "ssleay32.dll", NULL }; +static char *LIBCRYPTO[] = { "libcrypto.dll", + "libeay32.dll", NULL }; +#elif OSX +static char *LIBSSL[] = { "libssl.dylib", NULL }; +static char *LIBCRYPTO[] = { "libcrypto.dylib", NULL }; +#else +static char *LIBSSL[] = { "libssl.so", + "libssl.so.1.1", + "libssl.so.1.0.2", + "libssl.so.1.0.1", + "libssl.so.1.0.0", NULL }; +static char *LIBCRYPTO[] = { "libcrypto.so", + "libcrypto.so.1.1", + "libcrypto.so.1.0.2", + "libcrypto.so.1.0.1", + "libcrypto.so.1.0.0", NULL }; +#endif + +#define P0() void +#define P1(t1, p1) t1 p1 +#define P2(t1, p1, t2, p2) t1 p1, t2 p2 +#define P3(t1, p1, t2, p2, t3, p3) t1 p1, t2 p2, t3 p3 +#define P4(t1, p1, t2, p2, t3, p3, t4, p4) t1 p1, t2 p2, t3 p3, t4 p4 +#define P5(t1, p1, t2, p2, t3, p3, t4, p4, t5, p5) t1 p1, t2 p2, t3 p3, t4 p4, t5 p5 +#define P6(t1, p1, t2, p2, t3, p3, t4, p4, t5, p5, t6, p6) t1 p1, t2 p2, t3 p3, t4 p4, t5 p5, t6 p6 +#define V0() +#define V1(t1, p1) p1 +#define V2(t1, p1, t2, p2) p1, p2 +#define V3(t1, p1, t2, p2, t3, p3) p1, p2, p3 +#define V4(t1, p1, t2, p2, t3, p3, t4, p4) p1, p2, p3, p4 +#define V5(t1, p1, t2, p2, t3, p3, t4, p4, t5, p5) p1, p2, p3, p4, p5 +#define V6(t1, p1, t2, p2, t3, p3, t4, p4, t5, p5, t6, p6) p1, p2, p3, p4, p5, p6 + +#define P(n, ...) P##n(__VA_ARGS__) +#define V(n, ...) V##n(__VA_ARGS__) + +#define SYM(fn) dlsym_##fn +#define SYMDECL(fn, ret, n, ...) \ + static ret (*dlsym_##fn)(P(n,__VA_ARGS__)); \ + ret fn(P(n,__VA_ARGS__)) { \ + return (*dlsym_##fn)(V(n,__VA_ARGS__)); \ + } + +#define SYMDECLVOID(fn, n, ...) \ + static void (*dlsym_##fn)(P(n,__VA_ARGS__)); \ + void fn(P(n,__VA_ARGS__)) { \ + (*dlsym_##fn)(V(n,__VA_ARGS__)); \ + } + +#define SYMLOAD(h, fn) dlsym_##fn = dlsym(h, #fn) + +SYMDECL(SSL_read, int, 3, SSL*, s, void*, buf, int, len); +SYMDECL(SSL_write, int, 3, SSL*, s, const void*, buf, int, len); +SYMDECL(SSLv23_client_method, const SSL_METHOD*, 0); +SYMDECL(TLS_client_method, const SSL_METHOD*, 0); +SYMDECL(SSL_library_init, int, 0); +SYMDECL(SSL_CTX_new, SSL_CTX*, 1, const SSL_METHOD *, meth); +SYMDECL(SSL_CTX_ctrl, long, 4, SSL_CTX *, ctx, int, cmd, long, larg, void*, parg); +SYMDECL(SSL_new, SSL*, 1, SSL_CTX*, s); +SYMDECL(SSL_connect, int, 1, SSL*, s); +SYMDECL(SSL_shutdown, int, 1, SSL*, s); +SYMDECL(SSL_get_fd, int, 1, const SSL*, s); +SYMDECL(SSL_set_fd, int, 2, SSL*, s, int, fd); +SYMDECL(SSL_get_error, int, 2, const SSL*, s, int, ret_code); +SYMDECL(SSL_ctrl, long, 4, SSL*, ssl, int, cmd, long, larg, void*, parg); +SYMDECL(SSL_pending, int, 1, const SSL*, s); +SYMDECLVOID(SSL_free, 1, SSL*, s); +SYMDECLVOID(SSL_CTX_free, 1, SSL_CTX *, ctx); +SYMDECL(ERR_get_error, unsigned long, 0); +SYMDECLVOID(ERR_clear_error, 0); + +static void *dlopen_try(char **filenames, int flag) { + void *handle; + for (handle = NULL; !handle && *filenames; filenames++) handle = dlopen(*filenames, flag); + return handle; +} + +static int lambda(void) { + return true; +} + +bool load_ssl_symbols(void) { + CRYPThandle = dlopen_try(LIBCRYPTO, RTLD_NOW); + SSLhandle = dlopen_try(LIBSSL, RTLD_NOW); + + if (!SSLhandle || !CRYPThandle) { + free_ssl_symbols(); + return false; + } + + SYMLOAD(SSLhandle, SSL_CTX_new); + SYMLOAD(SSLhandle, SSL_CTX_ctrl); + SYMLOAD(SSLhandle, SSL_CTX_free); + SYMLOAD(SSLhandle, SSL_ctrl); + SYMLOAD(SSLhandle, SSL_free); + SYMLOAD(SSLhandle, SSL_new); + SYMLOAD(SSLhandle, SSL_connect); + SYMLOAD(SSLhandle, SSL_get_fd); + SYMLOAD(SSLhandle, SSL_set_fd); + SYMLOAD(SSLhandle, SSL_get_error); + SYMLOAD(SSLhandle, SSL_shutdown); + SYMLOAD(SSLhandle, SSL_read); + SYMLOAD(SSLhandle, SSL_write); + SYMLOAD(SSLhandle, SSL_pending); + SYMLOAD(SSLhandle, SSLv23_client_method); + SYMLOAD(SSLhandle, TLS_client_method); + SYMLOAD(SSLhandle, SSL_library_init); + + SYMLOAD(CRYPThandle, ERR_clear_error); + SYMLOAD(CRYPThandle, ERR_get_error); + + // managed deprecated functions + if (!SYM(SSLv23_client_method)) SYM(SSLv23_client_method) = SYM(TLS_client_method); + if (!SYM(SSL_library_init)) SYM(SSL_library_init) = λ + + return true; +} + +void free_ssl_symbols(void) { + if (SSLhandle) dlclose(SSLhandle); + if (CRYPThandle) dlclose(CRYPThandle); +} + +#endif diff --git a/stream.c b/stream.c index 3fdb28b..98369c0 100644 --- a/stream.c +++ b/stream.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,10 +21,20 @@ // stream thread +#define _GNU_SOURCE + #include "squeezelite.h" #include +#if USE_SSL +#include "openssl/ssl.h" +#include "openssl/err.h" +#endif + +#if SUN +#include +#endif static log_level loglevel; static struct buffer buf; @@ -33,10 +44,64 @@ #define UNLOCK mutex_unlock(streambuf->mutex) static sockfd fd; +static struct sockaddr_in addr; +static char host[256]; +static int header_mlen; struct streamstate stream; -static void send_header(void) { +#if USE_SSL +#define _last_error() ERROR_WOULDBLOCK + +static SSL_CTX *SSLctx; +SSL *ssl; + +static int _recv(SSL *ssl, int fd, void *buffer, size_t bytes, int options) { + int n; + if (!ssl) return recv(fd, buffer, bytes, options); + n = SSL_read(ssl, (u8_t*) buffer, bytes); + if (n <= 0 && SSL_get_error(ssl, n) == SSL_ERROR_ZERO_RETURN) return 0; + return n; +} + +static int _send(SSL *ssl, int fd, void *buffer, size_t bytes, int options) { + int n; + if (!ssl) return send(fd, buffer, bytes, options); + while (1) { + int err; + ERR_clear_error(); + if ((n = SSL_write(ssl, (u8_t*) buffer, bytes)) >= 0) return n; + err = SSL_get_error(ssl, n); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) continue; + LOG_INFO("SSL write error %d", err ); + return n; + } +} + +/* +can't mimic exactly poll as SSL is a real pain. Even if SSL_pending returns +0, there might be bytes to read but when select (poll) return > 0, there might +be no frame available. As well select (poll) < 0 does not mean that there is +no data pending +*/ +static int _poll(SSL *ssl, struct pollfd *pollinfo, int timeout) { + if (!ssl) return poll(pollinfo, 1, timeout); + if (pollinfo->events & POLLIN && SSL_pending(ssl)) { + if (pollinfo->events & POLLOUT) poll(pollinfo, 1, 0); + pollinfo->revents = POLLIN; + return 1; + } + return poll(pollinfo, 1, timeout); +} +#else +#define _recv(ssl, fc, buf, n, opt) recv(fd, buf, n, opt) +#define _send(ssl, fd, buf, n, opt) send(fd, buf, n, opt) +#define _poll(ssl, pollinfo, timeout) poll(pollinfo, 1, timeout) +#define _last_error() last_error() +#endif // USE_SSL + + +static bool send_header(void) { char *ptr = stream.header; int len = stream.header_len; @@ -44,9 +109,9 @@ ssize_t n; while (len) { - n = send(fd, ptr, len, MSG_NOSIGNAL); + n = _send(ssl, fd, ptr, len, MSG_NOSIGNAL); if (n <= 0) { - if (n < 0 && last_error() == ERROR_WOULDBLOCK && try < 10) { + if (n < 0 && _last_error() == ERROR_WOULDBLOCK && try < 10) { LOG_SDEBUG("retrying (%d) writing to socket", ++try); usleep(1000); continue; @@ -55,13 +120,14 @@ stream.disconnect = LOCAL_DISCONNECT; stream.state = DISCONNECT; wake_controller(); - return; + return false; } LOG_SDEBUG("wrote %d bytes to socket", n); ptr += n; len -= n; } LOG_SDEBUG("wrote header"); + return true; } static bool running = true; @@ -69,13 +135,77 @@ static void _disconnect(stream_state state, disconnect_code disconnect) { stream.state = state; stream.disconnect = disconnect; +#if USE_SSL + if (ssl) { + SSL_shutdown(ssl); + SSL_free(ssl); + ssl = NULL; + } +#endif closesocket(fd); fd = -1; wake_controller(); } +static int connect_socket(bool use_ssl) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + + if (sock < 0) { + LOG_ERROR("failed to create socket"); + return -1; + } + + LOG_INFO("connecting to %s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + set_nonblock(sock); + set_nosigpipe(sock); + + if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) { + LOG_INFO("unable to connect to server"); + closesocket(sock); + return -1; + } + +#if USE_SSL + if (use_ssl) { + ssl = SSL_new(SSLctx); + SSL_set_fd(ssl, sock); + + // add SNI + if (*host) SSL_set_tlsext_host_name(ssl, host); + + while (1) { + int status, err = 0; + + ERR_clear_error(); + status = SSL_connect(ssl); + + // successful negotiation + if (status == 1) break; + + // error or non-blocking requires more time + if (status < 0) { + err = SSL_get_error(ssl, status); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + usleep(1000); + continue; + } + } + + LOG_WARN("unable to open SSL socket %d (%d)", status, err); + closesocket(sock); + SSL_free(ssl); + ssl = NULL; + + return -1; + } + } +#endif + + return sock; +} + static void *stream_thread() { - while (running) { struct pollfd pollinfo; @@ -122,7 +252,7 @@ UNLOCK; - if (poll(&pollinfo, 1, 100)) { + if (_poll(ssl, &pollinfo, 100)) { LOCK; @@ -133,9 +263,9 @@ } if ((pollinfo.revents & POLLOUT) && stream.state == SEND_HEADERS) { - send_header(); + if (send_header()) stream.state = RECV_HEADERS; + header_mlen = stream.header_len; stream.header_len = 0; - stream.state = RECV_HEADERS; UNLOCK; continue; } @@ -149,13 +279,32 @@ char c; static int endtok; - int n = recv(fd, &c, 1, 0); + int n = _recv(ssl, fd, &c, 1, 0); if (n <= 0) { - if (n < 0 && last_error() == ERROR_WOULDBLOCK) { + if (n < 0 && _last_error() == ERROR_WOULDBLOCK) { UNLOCK; continue; } LOG_INFO("error reading headers: %s", n ? strerror(last_error()) : "closed"); +#if USE_SSL + if (!ssl && !stream.header_len) { + int sock; + closesocket(fd); + fd = -1; + stream.header_len = header_mlen; + LOG_INFO("now attempting with SSL"); + + // must be performed locked in case slimproto sends a disconnects + sock = connect_socket(true); + + if (sock >= 0) { + fd = sock; + stream.state = SEND_HEADERS; + UNLOCK; + continue; + } + } +#endif _disconnect(STOPPED, LOCAL_DISCONNECT); UNLOCK; continue; @@ -191,9 +340,9 @@ if (stream.meta_left == 0) { // read meta length u8_t c; - int n = recv(fd, &c, 1, 0); + int n = _recv(ssl, fd, &c, 1, 0); if (n <= 0) { - if (n < 0 && last_error() == ERROR_WOULDBLOCK) { + if (n < 0 && _last_error() == ERROR_WOULDBLOCK) { UNLOCK; continue; } @@ -208,9 +357,9 @@ } if (stream.meta_left) { - int n = recv(fd, stream.header + stream.header_len, stream.meta_left, 0); + int n = _recv(ssl, fd, stream.header + stream.header_len, stream.meta_left, 0); if (n <= 0) { - if (n < 0 && last_error() == ERROR_WOULDBLOCK) { + if (n < 0 && _last_error() == ERROR_WOULDBLOCK) { UNLOCK; continue; } @@ -244,12 +393,12 @@ space = min(space, stream.meta_next); } - n = recv(fd, streambuf->writep, space, 0); + n = _recv(ssl, fd, streambuf->writep, space, 0); if (n == 0) { LOG_INFO("end of stream"); _disconnect(DISCONNECT, DISCONNECT_OK); } - if (n < 0 && last_error() != ERROR_WOULDBLOCK) { + if (n < 0 && _last_error() != ERROR_WOULDBLOCK) { LOG_INFO("error reading: %s", strerror(last_error())); _disconnect(DISCONNECT, REMOTE_DISCONNECT); } @@ -260,6 +409,9 @@ if (stream.meta_interval) { stream.meta_next -= n; } + } else { + UNLOCK; + continue; } if (stream.state == STREAMING_BUFFERING && stream.bytes > stream.threshold) { @@ -278,6 +430,12 @@ LOG_SDEBUG("poll timeout"); } } + +#if USE_SSL + if (SSLctx) { + SSL_CTX_free(SSLctx); + } +#endif return 0; } @@ -296,6 +454,26 @@ exit(0); } +#if USE_SSL +#if !LINKALL && !NO_SSLSYM + if (ssl_loaded) { +#endif + SSL_library_init(); + SSLctx = SSL_CTX_new(SSLv23_client_method()); + if (SSLctx == NULL) { + LOG_ERROR("unable to allocate SSL context"); + exit(0); + } + SSL_CTX_set_options(SSLctx, SSL_OP_NO_SSLv2); +#if !LINKALL && !NO_SSLSYM + } +#endif + ssl = NULL; +#endif + +#if SUN + signal(SIGPIPE, SIG_IGN); /* Force sockets to return -1 with EPIPE on pipe signal */ +#endif stream.state = STOPPED; stream.header = malloc(MAX_HEADER); *stream.header = '\0'; @@ -309,7 +487,9 @@ #if LINUX || OSX || FREEBSD pthread_attr_t attr; pthread_attr_init(&attr); +#ifdef PTHREAD_STACK_MIN pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + STREAM_THREAD_STACK_SIZE); +#endif pthread_create(&thread, &attr, stream_thread, NULL); pthread_attr_destroy(&attr); #endif @@ -366,28 +546,26 @@ UNLOCK; } -void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) { - struct sockaddr_in addr; - - int sock = socket(AF_INET, SOCK_STREAM, 0); - - if (sock < 0) { - LOG_ERROR("failed to create socket"); - return; - } +void stream_sock(u32_t ip, u16_t port, bool use_ssl, const char *header, size_t header_len, unsigned threshold, bool cont_wait) { + char *p; + int sock; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = ip; addr.sin_port = port; - LOG_INFO("connecting to %s:%d", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - set_nonblock(sock); - set_nosigpipe(sock); - - if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) { - LOG_INFO("unable to connect to server"); + *host = '\0'; + p = strcasestr(header,"Host:"); + if (p) sscanf(p, "Host:%255[^:]", host); + + port = ntohs(port); + sock = connect_socket(use_ssl || port == 443); + + // try one more time with plain socket + if (sock < 0 && port == 443 && !use_ssl) sock = connect_socket(false); + + if (sock < 0) { LOCK; stream.state = DISCONNECT; stream.disconnect = UNREACHABLE; @@ -422,6 +600,13 @@ bool stream_disconnect(void) { bool disc = false; LOCK; +#if USE_SSL + if (ssl) { + SSL_shutdown(ssl); + SSL_free(ssl); + ssl = NULL; + } +#endif if (fd != -1) { closesocket(fd); fd = -1; diff --git a/tools/alsacap.c b/tools/alsacap.c new file mode 100644 index 0000000..1a848a4 --- /dev/null +++ b/tools/alsacap.c @@ -0,0 +1,638 @@ +/* + * ALSA parameter test program + * + * Copyright (c) 2007 Volker Schatz (alsacap at the domain volkerschatz.com) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * + * This program was originally written by Volker Schatz. Shawn Wilson + * bundled it into an autotools package and cut and pasted some code + * from the alsa speaker-test command to display min/max buffer and + * period sizes. + * + */ + + +/*============================================================================ + Includes +============================================================================*/ + +#include +#include +#include +#include +#include +#include + + +/*============================================================================ + Constant and type definitions +============================================================================*/ + +#define RATE_KHZ_LIMIT 200 + +#define HWP_END 0 +#define HWP_RATE 1 +#define HWP_NCH 2 +#define HWP_FORMAT 3 +#define SIZE_HWP 7 + +typedef struct { + int recdevices, verbose, card, dev; + char *device; + int hwparams[SIZE_HWP]; +} +aiopts; + + +/*============================================================================ + Global variables +============================================================================*/ + +static snd_ctl_t *handle= NULL; +static snd_pcm_t *pcm= NULL; +static snd_ctl_card_info_t *info; +static snd_pcm_info_t *pcminfo; +static snd_pcm_hw_params_t *pars; +static snd_pcm_format_mask_t *fmask; + + +/*============================================================================ + Prototypes +============================================================================*/ + +void usagemsg(int code); +void errnumarg(char optchar); +void errarg(char optchar); +void errtoomany(); + +void scancards(snd_pcm_stream_t stream, int thecard, int thedev); +int sc_errcheck(int retval, const char *doingwhat, int cardnr, int devnr); + +void testconfig(snd_pcm_stream_t stream, const char *device, const int *hwpars); +void tc_errcheck(int retval, const char *doingwhat); + +const char *alsaerrstr(const int errcode); +const char *dirstr(int dir); + +int parse_alsaformat(const char *fmtstr); +const char *alsafmtstr(int fmtnum); + +void printfmtmask(const snd_pcm_format_mask_t *fmask); + + +/*============================================================================ + Main program +============================================================================*/ + +int main(int argc, char **argv) +{ + aiopts options= { 0, 1, -1, -1, NULL }; + snd_pcm_stream_t stream; + char *argpar; + int argind, hwpind; + + snd_ctl_card_info_alloca(&info); + snd_pcm_info_alloca(&pcminfo); + snd_pcm_hw_params_alloca(&pars); + snd_pcm_format_mask_alloca(&fmask); + + hwpind= 0; + for( argind= 1; argind< argc; ++argind ) + { + if( argv[argind][0]!='-' ) { + fprintf(stderr, "Unrecognised command-line argument `%s'.\n", argv[argind]); + usagemsg(1); + } + if( argv[argind][2] ) argpar= argv[argind]+2; + else { + if( argind+1 >= argc ) argpar= NULL; + else argpar= argv[argind+1]; + } + if( argv[argind][1]=='h' || !strcmp(argv[argind]+1, "-help") ) + usagemsg(0); + else if( argv[argind][1]=='R' ) { + options.recdevices= 1; + argpar= NULL; // set to NULL if unused to keep track of next arg index + } + else if( argv[argind][1]=='C' ) { + if( !argpar || !isdigit(*argpar) ) errnumarg('C'); + options.card= strtol(argpar, NULL, 0); + } + else if( argv[argind][1]=='D' ) { + if( !argpar || !isdigit(*argpar) ) errnumarg('D'); + options.dev= strtol(argpar, NULL, 0); + } + else if( argv[argind][1]=='d' ) { + if( !argpar ) errarg('d'); + options.device= argpar; + } + else if( argv[argind][1]=='r' ) { + if( !argpar || !isdigit(*argpar) ) errnumarg('r'); + if( hwpind+3 > SIZE_HWP ) errtoomany(); + options.hwparams[hwpind++]= HWP_RATE; + options.hwparams[hwpind]= strtol(argpar, NULL, 0); + if( options.hwparams[hwpind] <= RATE_KHZ_LIMIT ) + options.hwparams[hwpind] *= 1000; // sanity check: Hz or kHz ? + ++hwpind; + } + else if( argv[argind][1]=='c' ) { + if( !argpar || !isdigit(*argpar) ) errnumarg('c'); + if( hwpind+3 > SIZE_HWP ) errtoomany(); + options.hwparams[hwpind++]= HWP_NCH; + options.hwparams[hwpind++]= strtol(argpar, NULL, 0); + } + else if( argv[argind][1]=='f' ) { + if( !argpar ) errarg('f'); + if( hwpind+3 > SIZE_HWP ) errtoomany(); + options.hwparams[hwpind++]= HWP_FORMAT; + options.hwparams[hwpind++]= parse_alsaformat(argpar); + } + else { + fprintf(stderr, "Unrecognised command-line option `%s'.\n", argv[argind]); + usagemsg(1); + } + if( argpar && !argv[argind][2] ) + ++argind; // additional increment if separate parameter argument was used + } + options.hwparams[hwpind]= HWP_END; + if( options.dev >= 0 && options.card < 0 ) { + fprintf(stderr, "The card has to be specified with -C if a device number is given (-D).\n"); + exit(1); + } + if( options.device && (options.card>=0 || options.dev>=0) ) { + fprintf(stderr, "Specifying a device name (-d) and a card and possibly device number (-C, -D) is mutually exclusive.\n"); + exit(1); + } + stream= options.recdevices? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK; + + if( !options.device ) + scancards(stream, options.card, options.dev); + else + testconfig(stream, options.device, options.hwparams); + +} + + + +/*============================================================================ + Usage message and command-line argument error functions +============================================================================*/ + +void usagemsg(int code) +{ + fprintf(stderr, "Usage: alsacap [-R] [-C [-D ]]\n" + " alsacap [-R] -d [-r |-c <# of channels>|-f ]...\n" + "ALSA capability lister.\n" + "First form: Scans one or all soundcards known to ALSA for devices, \n" + "subdevices and parameter ranges. -R causes a scan for recording\n" + "rather than playback devices. The other options specify the sound\n" + "card and possibly the device by number.\n" + "Second form: Displays ranges of configuration parameters for the given\n" + "ALSA device. Unlike with the first form, a non-hardware device may be\n" + "given. Up to three optional command-line arguments fix the rate,\n" + "number of channels and sample format in the order in which they are\n" + "given. The remaining parameter ranges are output. If unique, the\n" + "number of significant bits of the sample values is output. (Some\n" + "sound cards ignore some of the bits.)\n"); + exit(code); +} + +void errnumarg(char optchar) +{ + fprintf(stderr, "The -%c option requires a numerical argument! Aborting.\n", optchar); + exit(1); +} + +void errarg(char optchar) +{ + fprintf(stderr, "The -%c option requires an argument! Aborting.\n", optchar); + exit(1); +} + +void errtoomany() +{ + fprintf(stderr, "Too many -r/-c/-f options given! (Maximum is %d.) Aborting.\n", (SIZE_HWP-1)/2); + exit(1); +} + + +/*============================================================================ + Function for scanning all cards +============================================================================*/ + +#define HWCARDTEMPL "hw:%d" +#define HWDEVTEMPL "hw:%d,%d" +#define HWDEVLEN 32 + +void scancards(snd_pcm_stream_t stream, int thecard, int thedev) +{ + char hwdev[HWDEVLEN+1]; + unsigned min, max; + int card, err, dev, subd, nsubd; + snd_pcm_uframes_t period_size_min; + snd_pcm_uframes_t period_size_max; + snd_pcm_uframes_t buffer_size_min; + snd_pcm_uframes_t buffer_size_max; + + + printf("*** Scanning for %s devices", + stream == SND_PCM_STREAM_CAPTURE? "recording" : "playback"); + if( thecard >= 0 ) + printf(" on card %d", thecard); + if( thedev >= 0 ) + printf(", device %d", thedev); + printf(" ***\n"); + + hwdev[HWDEVLEN]= 0; + + if( thecard >= 0 ) + card= thecard; + else { + card= -1; + if( snd_card_next(&card) < 0 ) + return; + } + + + while( card >= 0 ) + { + snprintf(hwdev, HWDEVLEN, HWCARDTEMPL, card); + err= snd_ctl_open(&handle, hwdev, 0); + if( sc_errcheck(err, "opening control interface", card, -1) ) goto nextcard; + err= snd_ctl_card_info(handle, info); + if( sc_errcheck(err, "obtaining card info", card, -1) ) { + snd_ctl_close(handle); + goto nextcard; + } + printf("\nCard %d, ID `%s', name `%s'\n", + card, snd_ctl_card_info_get_id(info), + snd_ctl_card_info_get_name(info)); + if( thedev >= 0 ) + dev= thedev; + else { + dev= -1; + if( snd_ctl_pcm_next_device(handle, &dev) < 0 ) { + snd_ctl_close(handle); + goto nextcard; + } + } + while( dev >= 0 ) + { + snd_pcm_info_set_device(pcminfo, dev); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, stream); + err= snd_ctl_pcm_info(handle, pcminfo); + + if( thedev<0 && err == -ENOENT ) + goto nextdev; + if( sc_errcheck(err, "obtaining device info", card, dev) ) + goto nextdev; + nsubd= snd_pcm_info_get_subdevices_count(pcminfo); + if( sc_errcheck(nsubd, "obtaining device info", card, dev) ) + goto nextdev; + + printf( + " Device %d, ID `%s', name `%s', %d subdevices (%d available)\n", + dev, snd_pcm_info_get_id(pcminfo), snd_pcm_info_get_name(pcminfo), + nsubd, snd_pcm_info_get_subdevices_avail(pcminfo)); + snprintf(hwdev, HWDEVLEN, HWDEVTEMPL, card, dev); + + err= snd_pcm_open(&pcm, hwdev, stream, SND_PCM_NONBLOCK); + if( sc_errcheck(err, "opening sound device", card, dev) ) goto nextdev; + err= snd_pcm_hw_params_any(pcm, pars); + if( sc_errcheck(err, "obtaining hardware parameters", card, dev) ) { + snd_pcm_close(pcm); + goto nextdev; + } + snd_pcm_hw_params_get_channels_min(pars, &min); + snd_pcm_hw_params_get_channels_max(pars, &max); + if( min == max ) + if( min == 1 ) + printf(" 1 channel, "); + else + printf(" %d channels, ", min); + else + printf(" %u..%u channels, ", min, max); + + /* Find and print out min/max sampling rates. */ + snd_pcm_hw_params_get_rate_min(pars, &min, NULL); + snd_pcm_hw_params_get_rate_max(pars, &max, NULL); + printf("sampling rate %u..%u Hz\n", min, max); + + /* Find and print out possible PCM formats. */ + snd_pcm_hw_params_get_format_mask(pars, fmask); + printf(" Sample formats: "); + printfmtmask(fmask); + printf("\n"); + + /* Find and print out min/max buffer and period sizes. */ + err = snd_pcm_hw_params_get_buffer_size_min( + pars, &buffer_size_min); + err = snd_pcm_hw_params_get_buffer_size_max( + pars, &buffer_size_max); + err = snd_pcm_hw_params_get_period_size_min( + pars, &period_size_min, NULL); + err = snd_pcm_hw_params_get_period_size_max( + pars, &period_size_max, NULL); + + printf(" Buffer size range from %lu to %lu\n", + buffer_size_min, buffer_size_max); + printf(" Period size range from %lu to %lu\n", + period_size_min, period_size_max); + + + snd_pcm_close(pcm); + pcm= NULL; + + for( subd= 0; subd< nsubd; ++subd ) { + snd_pcm_info_set_subdevice(pcminfo, subd); + err= snd_ctl_pcm_info(handle, pcminfo); + if( sc_errcheck(err, "obtaining subdevice info", card, dev) ) + goto nextdev; + + printf(" Subdevice %d, name `%s'\n", + subd, snd_pcm_info_get_subdevice_name(pcminfo)); + } + + nextdev: + if( thedev >= 0 || snd_ctl_pcm_next_device(handle, &dev) < 0 ) + break; + } + snd_ctl_close(handle); + nextcard: + if( thecard >= 0 || snd_card_next(&card) < 0 ) + break; + } +} + + +int sc_errcheck(int retval, const char *doingwhat, int cardnr, int devnr) +{ + if( retval<0 ) { + if( devnr>= 0 ) + fprintf(stderr, "Error %s for card %d, device %d: %s. Skipping.\n", doingwhat, cardnr, devnr, alsaerrstr(retval)); + else + fprintf(stderr, "Error %s for card %d: %s. Skipping.\n", doingwhat, cardnr, alsaerrstr(retval)); + return 1; + } + return 0; +} + + + +/*============================================================================ + Function for investigating device configurations +============================================================================*/ + +void testconfig(snd_pcm_stream_t stream, const char *device, const int *hwpars) +{ + unsigned min, max, param; + int err, count, dir, result; + snd_pcm_uframes_t period_size_min; + snd_pcm_uframes_t period_size_max; + snd_pcm_uframes_t buffer_size_min; + snd_pcm_uframes_t buffer_size_max; + + printf("*** Exploring configuration space of device `%s' for %s ***\n", device, + stream==SND_PCM_STREAM_CAPTURE? "recording" : "playback"); + err= snd_pcm_open(&pcm, device, stream, SND_PCM_NONBLOCK); + tc_errcheck(err, "opening sound device"); + err= snd_pcm_hw_params_any(pcm, pars); + tc_errcheck(err, "initialising hardware parameters"); + for( count= 0; hwpars[count]!=HWP_END; count += 2 ) + + switch(hwpars[count]) + { + case HWP_RATE:param= hwpars[count+1]; + err= snd_pcm_hw_params_set_rate_near(pcm, pars, ¶m, &result); + if( err<0 ) + fprintf(stderr, "Could not set sampling rate to %d Hz: %s. " + "Continuing regardless.\n", hwpars[count+1], alsaerrstr(err)); + else + printf("Set sampling rate %d Hz --> got %u Hz, %s requested.\n", hwpars[count+1], param, dirstr(dir)); + break; + case HWP_NCH:err= snd_pcm_hw_params_set_channels(pcm, pars, hwpars[count+1]); + if( err<0 ) + fprintf(stderr, "Could not set # of channels to %d: %s. " + "Continuing regardless.\n", hwpars[count+1], alsaerrstr(err)); + else + printf("Set number of channels to %d.\n", hwpars[count+1]); + break; + case HWP_FORMAT:err= snd_pcm_hw_params_set_format(pcm, pars, hwpars[count+1]); + if( err<0 ) + fprintf(stderr, "Could not set sample format to %s: %s." + " Continuing regardless.\n", alsafmtstr(hwpars[count+1]), alsaerrstr(err)); + else + printf("Set sample format to %s.\n", alsafmtstr(hwpars[count+1])); + break; + default: + break; + } + if( count>0 ) + printf("Parameter ranges remaining after these settings:\n"); + snd_pcm_hw_params_get_channels_min(pars, &min); + snd_pcm_hw_params_get_channels_max(pars, &max); + if( min==max ) + if( min==1 ) + printf("1 channel\n"); + else + printf("%u channels\n", min); + else + printf("%u..%u channels\n", min, max); + snd_pcm_hw_params_get_rate_min(pars, &min, NULL); + snd_pcm_hw_params_get_rate_max(pars, &max, NULL); + if( min==max ) + printf("Sampling rate %u Hz\n", min); + else + printf("Sampling rate %u..%u Hz\n", min, max); + + /* Find and print out possible PCM formats. */ + snd_pcm_hw_params_get_format_mask(pars, fmask); + printf(" Sample formats: "); + printfmtmask(fmask); + printf("\n"); + + /* Find and print out min/max buffer and period sizes. */ + err = snd_pcm_hw_params_get_buffer_size_min( + pars, &buffer_size_min); + err = snd_pcm_hw_params_get_buffer_size_max( + pars, &buffer_size_max); + err = snd_pcm_hw_params_get_period_size_min( + pars, &period_size_min, NULL); + err = snd_pcm_hw_params_get_period_size_max( + pars, &period_size_max, NULL); + + printf(" Buffer size range from %lu to %lu\n", + buffer_size_min, buffer_size_max); + printf(" Period size range from %lu to %lu\n", + period_size_min, period_size_max); + + result= snd_pcm_hw_params_get_sbits(pars); + if( result >= 0 ) // only available if bit width of all formats is the same + printf("Significant bits: %d\n", result); + snd_pcm_close(pcm); +} + + +void tc_errcheck(int retval, const char *doingwhat) +{ + if( retval<0 ) { + fprintf(stderr, "Error %s: %s. Aborting.\n", doingwhat, alsaerrstr(retval)); + if( pcm ) + snd_pcm_close(pcm); + exit(1); + } +} + + +/*============================================================================ + String-building functions +============================================================================*/ + +struct alsaerr { int err; char *msg; }; +struct alsaerr aelist[]= { + -EBADFD, "PCM device is in a bad state", + -EPIPE, "An underrun occurred", + -ESTRPIPE, "A suspend event occurred", + -ENOTTY, "Hotplug device has been removed", + -ENODEV, "Hotplug device has been removed", + -ENOENT, "Device does not exist", + 0, NULL +}; +const char *alsaerrstr(const int errcode) +{ + struct alsaerr *search; + + if( errcode >= 0 ) + return "No error"; + for( search= aelist; search->msg && search->err!=errcode; ++search); + if( search->msg ) + return search->msg; + else + return strerror(-errcode); +} + + +const char *dirstr(int dir) +{ + if( !dir ) + return "="; + else if( dir<0 ) + return "<"; + else + return ">"; +} + + +/*============================================================================ + Functions for parsing and string output of ALSA sample formats +============================================================================*/ + +struct fmtdef { char *fmtname; int format; }; +static struct fmtdef fmtlist[]= { + "S8", SND_PCM_FORMAT_S8, + "U8", SND_PCM_FORMAT_U8, + "S16_LE", SND_PCM_FORMAT_S16_LE, + "S16_BE", SND_PCM_FORMAT_S16_BE, + "U16_LE", SND_PCM_FORMAT_U16_LE, + "U16_BE", SND_PCM_FORMAT_U16_BE, + "S24_LE", SND_PCM_FORMAT_S24_LE, + "S24_BE", SND_PCM_FORMAT_S24_BE, + "U24_LE", SND_PCM_FORMAT_U24_LE, + "U24_BE", SND_PCM_FORMAT_U24_BE, + "S32_LE", SND_PCM_FORMAT_S32_LE, + "S32_BE", SND_PCM_FORMAT_S32_BE, + "U32_LE", SND_PCM_FORMAT_U32_LE, + "U32_BE", SND_PCM_FORMAT_U32_BE, + "FLOAT_LE", SND_PCM_FORMAT_FLOAT_LE, + "FLOAT_BE", SND_PCM_FORMAT_FLOAT_BE, + "FLOAT64_LE", SND_PCM_FORMAT_FLOAT64_LE, + "FLOAT64_BE", SND_PCM_FORMAT_FLOAT64_BE, + "IEC958_SUBFRAME_LE", SND_PCM_FORMAT_IEC958_SUBFRAME_LE, + "IEC958_SUBFRAME_BE", SND_PCM_FORMAT_IEC958_SUBFRAME_BE, + "MU_LAW", SND_PCM_FORMAT_MU_LAW, + "A_LAW", SND_PCM_FORMAT_A_LAW, + "IMA_ADPCM", SND_PCM_FORMAT_IMA_ADPCM, + "MPEG", SND_PCM_FORMAT_MPEG, + "GSM", SND_PCM_FORMAT_GSM, + "SPECIAL", SND_PCM_FORMAT_SPECIAL, + "S24_3LE", SND_PCM_FORMAT_S24_3LE, + "S24_3BE", SND_PCM_FORMAT_S24_3BE, + "U24_3LE", SND_PCM_FORMAT_U24_3LE, + "U24_3BE", SND_PCM_FORMAT_U24_3BE, + "S20_3LE", SND_PCM_FORMAT_S20_3LE, + "S20_3BE", SND_PCM_FORMAT_S20_3BE, + "U20_3LE", SND_PCM_FORMAT_U20_3LE, + "U20_3BE", SND_PCM_FORMAT_U20_3BE, + "S18_3LE", SND_PCM_FORMAT_S18_3LE, + "S18_3BE", SND_PCM_FORMAT_S18_3BE, + "U18_3LE", SND_PCM_FORMAT_U18_3LE, + "U18_3BE", SND_PCM_FORMAT_U18_3BE, + "S16", SND_PCM_FORMAT_S16, + "U16", SND_PCM_FORMAT_U16, + "S24", SND_PCM_FORMAT_S24, + "U24", SND_PCM_FORMAT_U24, + "S32", SND_PCM_FORMAT_S32, + "U32", SND_PCM_FORMAT_U32, + "FLOAT", SND_PCM_FORMAT_FLOAT, + "FLOAT64", SND_PCM_FORMAT_FLOAT64, + "IEC958_SUBFRAME", SND_PCM_FORMAT_IEC958_SUBFRAME, + NULL, 0 +}; + +int parse_alsaformat(const char *fmtstr) +{ + struct fmtdef *search; + + for( search= fmtlist; search->fmtname && strcmp(search->fmtname, fmtstr); ++search ); + if( !search->fmtname ) { + fprintf(stderr, "Unknown sample format `%s'. Aborting.\n", fmtstr); + exit(1); + } + return search->format; +} + +const char *alsafmtstr(int fmtnum) +{ + struct fmtdef *search; + + for( search= fmtlist; search->fmtname && search->format!=fmtnum; ++search ); + if( !search->fmtname ) + return "(unknown)"; + else + return search->fmtname; +} + + +/*============================================================================ + Printout functions +============================================================================*/ + +void printfmtmask(const snd_pcm_format_mask_t *fmask) +{ + int fmt, prevformat= 0; + + for( fmt= 0; fmt <= SND_PCM_FORMAT_LAST; ++fmt ) + if( snd_pcm_format_mask_test(fmask, (snd_pcm_format_t)fmt) ) { + if( prevformat ) + printf(", "); + printf("%s", snd_pcm_format_name((snd_pcm_format_t)fmt)); + prevformat= 1; + } + if( !prevformat ) + printf("(none)"); +} + + diff --git a/tools/find_servers.c b/tools/find_servers.c new file mode 100644 index 0000000..2eea86b --- /dev/null +++ b/tools/find_servers.c @@ -0,0 +1,290 @@ +/* + * SlimProtoLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SlimProtoLib; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __WIN32__ + #include + #include + #include "poll.h" + #define CLOSESOCKET(s) closesocket(s) + #define MSG_DONTWAIT (0) +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #define CLOSESOCKET(s) close(s) +#endif + +#define BUF_LENGTH 4096 + +/* fprintf(stderr, __VA_ARGS__) */ +#define DEBUGF(...) +#define VDEBUGF(...) + +#define packN4(ptr, off, v) { ptr[off] = (char)(v >> 24) & 0xFF; ptr[off+1] = (v >> 16) & 0xFF; ptr[off+2] = (v >> 8) & 0xFF; ptr[off+3] = v & 0xFF; } +#define packN2(ptr, off, v) { ptr[off] = (char)(v >> 8) & 0xFF; ptr[off+1] = v & 0xFF; } +#define packC(ptr, off, v) { ptr[off] = v & 0xFF; } +#define packA4(ptr, off, v) { strncpy((char*)(&ptr[off]), v, 4); } + +#define unpackN4(ptr, off) ((ptr[off] << 24) | (ptr[off+1] << 16) | (ptr[off+2] << 8) | ptr[off+3]) +#define unpackN2(ptr, off) ((ptr[off] << 8) | ptr[off+1]) +#define unpackC(ptr, off) (ptr[off]) + +#define bool int +#define true 1 +#define false 0 + +#define DISCOVERY_PKTSIZE 1516 +#define SLIMPROTO_DISCOVERY "eNAME\0JSON\0" + +int slimproto_discover(char *server_addr, int server_addr_len, int port, unsigned int *jsonport, bool scan) +{ + int sockfd; + int try; + char *packet; + int pktlen; + int pktidx; + char *t; + unsigned int l; + char *v; + char *server_name; + char *server_json; + struct pollfd pollfd; + struct sockaddr_in sendaddr; + struct sockaddr_in recvaddr; +#ifdef __WIN32__ + WSADATA info; +#endif + + socklen_t sockaddr_len = sizeof(sendaddr); + + int broadcast=1; + int serveraddr_len = -1; + +#ifdef __WIN32__ + /* Need to initialize winsock if scanning on windows as slimproto_init has not been called */ + if ( scan ) + { + if (WSAStartup(MAKEWORD(1,1), &info) != 0) + { + fprintf(stderr, "Cannot initialize WinSock"); + return -1; + } + } +#endif + + if((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) == -1) + { + perror("sockfd"); + return -1; + } + + pollfd.fd = sockfd; + pollfd.events = POLLIN; + + if((setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const void*) &broadcast, sizeof broadcast)) == -1) + { + perror("setsockopt - SO_BROADCAST"); + return -1; + } + + sendaddr.sin_family = AF_INET; + sendaddr.sin_port = htons(0); + sendaddr.sin_addr.s_addr = INADDR_ANY; + memset(sendaddr.sin_zero,'\0',sizeof sendaddr.sin_zero); + + if(bind(sockfd, (struct sockaddr*) &sendaddr, sizeof sendaddr) == -1) + { + perror("bind"); + return -1; + } + + recvaddr.sin_family = AF_INET; + recvaddr.sin_port = htons(port); + recvaddr.sin_addr.s_addr = INADDR_BROADCAST; + + memset(recvaddr.sin_zero,'\0',sizeof recvaddr.sin_zero); + + packet = malloc ( sizeof ( char ) * DISCOVERY_PKTSIZE ); + v = malloc ( sizeof ( char ) * 256 ); + t = malloc ( sizeof ( char ) * 256 ); + server_name = malloc ( sizeof ( char ) * 256 ); + server_json = malloc ( sizeof ( char ) * 256 ); + if ( (packet == NULL) || + (v == NULL) || + (t == NULL) || + (server_name == NULL) || + (server_json == NULL) ) + { + perror("malloc"); + return -1; + } + + for (try = 0; try < 5; try ++) + { + if (sendto(sockfd, SLIMPROTO_DISCOVERY, sizeof(SLIMPROTO_DISCOVERY), 0, + (struct sockaddr *)&recvaddr, sizeof(recvaddr)) == -1) + { + CLOSESOCKET(sockfd); + perror("sendto"); + return -1; + } + + DEBUGF("slimproto_discover: discovery packet sent\n"); + + /* Wait up to 1 second for response */ + while (poll(&pollfd, 1, 1000)) + { + memset(packet,0,sizeof(packet)); + + pktlen = recvfrom(sockfd, packet, DISCOVERY_PKTSIZE, MSG_DONTWAIT, + (struct sockaddr *)&sendaddr, &sockaddr_len); + + if ( pktlen == -1 ) continue; + + /* Invalid response packet, try again */ + if ( packet[0] != 'E') continue; + + memset(server_name,0,sizeof(server_name)); + memset(server_json,0,sizeof(server_json)); + + VDEBUGF("slimproto_discover: pktlen:%d\n",pktlen); + + /* Skip the E */ + pktidx = 1; + + while ( pktidx < (pktlen - 5) ) + { + strncpy ( t, &packet[pktidx], pktidx + 3 ); + t[4] = '\0'; + l = (unsigned int) ( packet[pktidx + 4] ); + strncpy ( v, &packet[pktidx + 5], pktidx + 4 + l); + v[l] = '\0'; + pktidx = pktidx + 5 + l; + + if ( memcmp ( t, "NAME", 4 ) == 0 ) + { + strncpy ( server_name, v, l ); + server_name[l] = '\0'; + } + else if ( memcmp ( t, "JSON", 4 ) == 0 ) + { + strncpy ( server_json, v, l ); + server_json[l] = '\0'; + } + + VDEBUGF("slimproto_discover: key: %s len: %d value: %s pktidx: %d\n", + t, l, v, pktidx); + } + + inet_ntop(AF_INET, &sendaddr.sin_addr.s_addr, server_addr, server_addr_len); + + *jsonport = (unsigned int) strtoul(server_json, NULL, 10); + + DEBUGF("slimproto_discover: discovered %s:%u (%s)\n", + server_name, *jsonport, server_addr); + + serveraddr_len = strlen(server_addr); + + /* Server(s) responded, so don't try again */ + try = 5; + + if ( scan ) + printf("%s:%u (%s)\n", server_name, *jsonport, server_addr); + else + break ; /* Return first server that replied */ + } + } + + CLOSESOCKET(sockfd); + + if ( scan ) + { + strcpy ( server_addr, "0.0.0.0" ); + *jsonport = 0; + serveraddr_len = -1; +#ifdef __WIN32__ + WSACleanup(); +#endif + } + + if ( server_json != NULL ) + free (server_json); + + if ( server_name != NULL ) + free (server_name); + + if ( t != NULL ) + free (t); + + if ( v != NULL ) + free (v); + + if ( packet != NULL ) + free (packet); + + DEBUGF("slimproto_discover: end\n"); + + return serveraddr_len ; +} + +static void license(void) { + printf( "\n" + "This program is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.\n\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program. If not, see .\n\n" + "The source is available from https://github.com/ralph-irving/squeezelite\n" + ); +} + +int main(int argc, char **argv) +{ + char slimserver_address[256] = "127.0.0.1"; + int port = 3483; + unsigned int json; + int len ; + + if (argc > 1) + { + license(); + exit (1); + } + + /* Scan */ + len = slimproto_discover(slimserver_address, sizeof (slimserver_address), port, &json, true); + + VDEBUGF("main: slimproto_discover_scan: address:%s len:%d json:%u\n", slimserver_address, len, json ); + + return 0; +} + diff --git a/tools/gpiopower.sh b/tools/gpiopower.sh new file mode 100644 index 0000000..f499738 --- /dev/null +++ b/tools/gpiopower.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +# Define GPIO +GPIO_OUT=18 + +# Turn on, and turn off functions +turn_on() { +echo "1" > /sys/class/gpio/gpio$GPIO_OUT/value +} + +turn_off() { +echo "0" > /sys/class/gpio/gpio$GPIO_OUT/value +} + +init_gpio_out() { +#================================================= ========================== +# Initial GPIO OUT setup +#--------------------------------------------------------------------------- +sudo sh -c 'echo '"$GPIO_OUT"' > /sys/class/gpio/export' +# Relay is active low, so this reverses the logic +sudo sh -c 'echo "1" > /sys/class/gpio/gpio'"$GPIO_OUT"'/active_low' +sudo sh -c 'echo "out" > /sys/class/gpio/gpio'"$GPIO_OUT"'/direction' +sudo sh -c 'echo "0" > /sys/class/gpio/gpio'"$GPIO_OUT"'/value' +#--------------------------------------------------------------------------- +} + +case "${1}" in + +# 2 from cmdline is for first initialization commands, this is run once +2) +init_gpio_out +;; + +# 0 from cmdline is for Off Commands +0) + turn_off +;; + +# 1 from cmdline is for On Commands +1) + turn_on +;; +esac diff --git a/tools/lircd.conf b/tools/lircd.conf new file mode 100644 index 0000000..927d86c --- /dev/null +++ b/tools/lircd.conf @@ -0,0 +1,67 @@ +# +# using lirc-0.9.4(userspace) on Thu Apr 30 10:29:59 2020 +# +# contributed by Ralph Irving +# +# brand: Slim Devices +# model no. of remote control: Squeezebox3 Touch +# devices being controlled by this remote: Squeezelite +# + +begin remote + + name Slim_Devices_Squeezebox3 + bits 16 + flags SPACE_ENC|CONST_LENGTH + eps 30 + aeps 100 + + header 9100 4416 + one 649 1593 + zero 647 473 + ptrail 649 + pre_data_bits 16 + pre_data 0x7689 + gap 107995 + min_repeat 1 + suppress_repeat 6 + toggle_bit_mask 0x0 + + begin codes + KEY_VOLUMEDOWN 0x00FF + KEY_VOLUMEUP 0x807F + KEY_REWIND 0xC03F + KEY_FORWARD 0xA05F + KEY_PAUSE 0x20DF + KEY_PLAY 0x10EF + KEY_POWER 0x40BF + KEY_UP 0xE01F + KEY_LEFT 0x906F + KEY_RIGHT 0xD02F + KEY_DOWN 0xB04F + KEY_1 0xF00F + KEY_2 0x08F7 + KEY_3 0x8877 + KEY_4 0x48B7 + KEY_5 0xC837 + KEY_6 0x28D7 + KEY_7 0xA857 + KEY_8 0x6897 + KEY_9 0xE817 + KEY_0 0x9867 + KEY_FAVORITES 0x18E7 + KEY_SEARCH 0x58A7 + KEY_SHUFFLE 0xD827 + KEY_MEDIA_REPEAT 0x38C7 + KEY_SLEEP 0xB847 + KEY_INSERT 0x609F ## Add + KEY_BRIGHTNESS_CYCLE 0x04FB ## Brightness + KEY_TEXT 0xF807 ## Size + KEY_TITLE 0x7887 ## Now Playing + KEY_FAVORITES 0xE21D ## Touch:FAVOURITES + KEY_SEARCH 0x629D ## Touch:SEARCH + KEY_HOME 0x22DD ## Touch:HOME + KEY_TITLE 0xA25D ## Touch:NOW PLAYING + end codes + +end remote diff --git a/tools/lircrc b/tools/lircrc new file mode 100644 index 0000000..b0e6de0 --- /dev/null +++ b/tools/lircrc @@ -0,0 +1,127 @@ +############################ +## KEY_POWER (0x768940bf) ## +############################ + +begin + remote = Slim_Devices_Squeezebox3 + button = KEY_POWER + repeat = 0 + prog = squeezelite + config = power +end + +################################# +## KEY_VOLUMEDOWN (0x768900ff) ## +################################# + +begin + remote = Slim_Devices_Squeezebox3 + button = KEY_VOLUMEDOWN + repeat = 0 + prog = squeezelite + config = voldown +end + +############################### +## KEY_VOLUMEUP (0x7689807f) ## +############################### + +begin + remote = Slim_Devices_Squeezebox3 + button = KEY_VOLUMEUP + repeat = 0 + prog = squeezelite + config = volup +end + +########################### +## KEY_MUTE (0x7689c43b) ## +########################### + +begin + remote = Slim_Devices_Squeezebox3 + button = Brightness + repeat = 0 + prog = squeezelite + config = muting +end + +########################### +## KEY_PLAY (0x768910ef) ## +########################### + +begin + remote = Slim_Devices_Squeezebox3 + button = KEY_PLAY + repeat = 0 + prog = squeezelite + config = play +end + +############################ +## KEY_PAUSE (0x768920df) ## +############################ + +begin + remote = Slim_Devices_Squeezebox3 + button = KEY_PAUSE + repeat = 0 + prog = squeezelite + config = pause +end + +########################### +## KEY_NEXT (0x7689a05f) ## +########################### + +begin + remote = Slim_Devices_Squeezebox3 + button = KEY_FORWARD + repeat = 0 + prog = squeezelite + config = fwd +end + +############################### +## KEY_PREVIOUS (0x7689c03f) ## +############################### + +begin + remote = Slim_Devices_Squeezebox3 + button = KEY_REWIND + repeat = 0 + prog = squeezelite + config = rew +end + +########################### +## power_on (0x76898f70) ## +########################### + +############################ +## power_off (0x76898778) ## +############################ + +########################### +## preset_1 (0x76898a75) ## +########################### + +########################### +## preset_2 (0x76894ab5) ## +########################### + +########################### +## preset_3 (0x7689ca35) ## +########################### + +########################### +## preset_4 (0x76892ad5) ## +########################### + +########################### +## preset_5 (0x7689aa55) ## +########################### + +########################### +## preset_6 (0x76896a95) ## +########################### diff --git a/tools/setrpath.c b/tools/setrpath.c new file mode 100644 index 0000000..6069199 --- /dev/null +++ b/tools/setrpath.c @@ -0,0 +1,214 @@ +/************************************************************************/ +/* setrpath */ +/* */ +/* By Davin Milun (milun@cs.buffalo.edu) */ +/* Last modified: Sun Feb 26 12:21:06 EST 1995 */ +/* */ +/* Program to set the RPATH in an ELF executable. */ +/* However, it cannot set the RPATH longer than the RPATH set at */ +/* compile time. */ +/* */ +/* Send any bug reports/fixes/suggestions to milun@cs.buffalo.edu */ +/************************************************************************/ + +/************************************************************************/ +/* Copyright (C) 1995, Davin Milun */ +/* Permission to use and modify this software for any purpose other */ +/* than its incorporation into a commercial product is hereby granted */ +/* without fee. */ +/* */ +/* Permission to copy and distribute this software only for */ +/* non-commercial use is also granted without fee, provided, however, */ +/* that the above copyright notice appear in all copies, that both that */ +/* copyright notice and this permission notice appear in supporting */ +/* documentation. The author makes no representations about the */ +/* suitability of this software for any purpose. It is provided */ +/* ``as is'' without express or implied warranty. */ +/* */ +/* Compile: gcc -o setrpath -lelf -O setrpath.c */ +/************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define USAGE "USAGE:\t%s [-f] \n\ +\t%s -r \n" + +#define ORNULL(s) (s?s:"(null)") + +int +main(int argc, char *argv[]) +{ + +int file; +Elf *elf; +Elf_Scn *scn, *strscn; +Elf32_Shdr *scn_shdr; +Elf32_Ehdr *scn_ehdr; +Elf_Data *data, *strdata; +Elf32_Dyn *dyn; +size_t strscnndx; +int oldlen, newlen=0, extra_space; +char *oldrpath, *newrpath=NULL; +unsigned char *strbuffer; +int strbuffersize; +int forceflag=0, readonly=0; +int hasrpath=0; + +extern char *optarg; +extern int optind; +int c; + +while ((c = getopt(argc, argv, "fr")) != EOF){ + switch(c){ + case 'f': forceflag=1; + break; + case 'r': readonly=1; + break; + case '?': fprintf(stderr, USAGE, argv[0], argv[0]); + break; + } + } + +if (argc != (optind+2-readonly)) { + fprintf(stderr,"Wrong number of arguments\n"); + fprintf(stderr, USAGE, argv[0], argv[0]); + exit(1); + } + +if (!readonly){ + newrpath = strdup(argv[optind+1]); + newlen = strlen(newrpath); + } + +if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr,"Old version of ELF.\n"); + exit(2); + } + +if ((file = open(argv[optind],(readonly?O_RDONLY:O_RDWR))) == -1) { + fprintf(stderr,"Cannot open %s for %s\n",(readonly?"reading":"writing"), + ORNULL(argv[optind])); + perror("open"); + exit(3); + } + +elf = elf_begin(file, (readonly?ELF_C_READ:ELF_C_RDWR), (Elf *)NULL); + +if (elf_kind(elf) != ELF_K_ELF) { + fprintf(stderr,"%s is not an ELF file.\n",argv[optind]); + exit(4); + } + +if ((scn_ehdr = elf32_getehdr(elf)) == 0) { + fprintf(stderr,"elf32_getehdr failed.\n"); + exit(5); + } + +scn = NULL; + +/* Process sections */ +while ((scn = elf_nextscn(elf, scn)) != NULL) { + scn_shdr = elf32_getshdr(scn); + + /* Only look at SHT_DYNAMIC section */ + if (scn_shdr->sh_type == SHT_DYNAMIC) { + data = NULL; + + /* Process data blocks in the section */ + while ((data = elf_getdata(scn, data)) != NULL) { + dyn = (Elf32_Dyn *) data->d_buf; + + /* Process entries in dynamic linking table */ + while (dyn->d_tag != DT_NULL) { + /* Look at DT_RPATH entry */ + if (dyn->d_tag == DT_RPATH) { + hasrpath++; + scn_shdr = elf32_getshdr(scn); + strscnndx = scn_shdr->sh_link; + oldrpath = elf_strptr(elf, strscnndx, dyn->d_un.d_ptr); + printf("%s RPATH: %s\n",(readonly?"Current":"Old"),ORNULL(oldrpath)); + oldlen = strlen(oldrpath); + + if (readonly) { /* Quit now if readonly*/ + elf_end(elf); + exit(0); + } + + /* Load the section that contains the strings */ + strscn = elf_getscn(elf,strscnndx); + strdata = NULL; + while ((strdata = elf_getdata(strscn, strdata)) != NULL) { + strbuffersize = strdata->d_size; + strbuffer = strdata->d_buf; + + /* Get next data block if needed */ + if ((dyn->d_un.d_ptr > (strdata->d_off + strdata->d_size)) || + (dyn->d_un.d_ptr < strdata->d_off)) { + fprintf(stderr,"The string table is not in one data block\n"); + fprintf(stderr,"This is not handled by this program\n"); + exit(6); + } + + /* See if there is "slack" after end of RPATH */ + extra_space = strdata->d_size - (dyn->d_un.d_ptr + oldlen + 1); + + /* Mark the data block as dirty */ + elf_flagdata(strdata,ELF_C_SET,ELF_F_DIRTY); + + if (newlen > (oldlen + extra_space)) { + fprintf(stderr,"New RPATH would be longer than current RPATH \ +plus any extra space.\n"); + fprintf(stderr,"Aborting...\n"); + exit(7); + } + + if ((newlen > oldlen) && !forceflag) { + fprintf(stderr,"New RPATH would be longer than current RPATH.\n"); + fprintf(stderr,"(Use -f to use any extra space in string table)\n"); + exit(8); + } + + /* Since it will fit in the old place, we can do it */ + memmove(&strbuffer[dyn->d_un.d_ptr], newrpath, newlen); + strbuffer[dyn->d_un.d_ptr+newlen] = 0; + + } /* while elf_getdata on the string table */ + + } /* if (dyn->d_tag == DT_RPATH) */ + dyn++; + } /* while (dyn->d_tag != DT_NULL) */ + } /* while elf_getdata */ + } /* if SHT_DYNAMIC */ + } /* while elf_nextscn */ + +if (readonly) { + printf("ELF file \"%s\" contains no RPATH.\n",argv[optind]); + exit(0); + } + +if (hasrpath) { + if (elf_update(elf, ELF_C_WRITE) == -1 ) { + fprintf(stderr,"elf_update failed.\n"); + exit(9); + } + + printf("New RPATH set to: %s\n", ORNULL(newrpath)); + } else { + fprintf(stderr,"ELF file \"%s\" contains no RPATH - cannot set one.\n",argv[optind]); + exit(10); + } + +elf_end(elf); + +exit(0); +} diff --git a/utils.c b/utils.c index ba67d77..766031e 100644 --- a/utils.c +++ b/utils.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2017, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,8 +31,23 @@ #include #endif #endif +#if SUN +#include +#include +#include +#include +#include +#include +#include +#include +#endif #if WIN #include +#if USE_SSL +#include +#include +#include +#endif #endif #if OSX #include @@ -62,6 +78,7 @@ va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); + va_end(args); fflush(stderr); } @@ -89,7 +106,11 @@ #else #if LINUX || FREEBSD struct timespec ts; +#ifdef CLOCK_MONOTONIC if (!clock_gettime(CLOCK_MONOTONIC, &ts)) { +#else + if (!clock_gettime(CLOCK_REALTIME, &ts)) { +#endif return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } #endif @@ -100,14 +121,29 @@ } // mac address -#if LINUX +#if LINUX && !defined(SUN) // search first 4 interfaces returned by IFCONF void get_mac(u8_t mac[]) { + char *utmac; struct ifconf ifc; struct ifreq *ifr, *ifend; struct ifreq ifreq; struct ifreq ifs[4]; + utmac = getenv("UTMAC"); + if (utmac) + { + if ( strlen(utmac) == 17 ) + { + if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", + &mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6) + { + return; + } + } + + } + mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0; int s = socket(AF_INET, SOCK_DGRAM, 0); @@ -121,7 +157,7 @@ for (ifr = ifc.ifc_req; ifr < ifend; ifr++) { if (ifr->ifr_addr.sa_family == AF_INET) { - strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name)); + strncpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifreq.ifr_name) - 1); if (ioctl (s, SIOCGIFHWADDR, &ifreq) == 0) { memcpy(mac, ifreq.ifr_hwaddr.sa_data, 6); if (mac[0]+mac[1]+mac[2] != 0) { @@ -133,6 +169,72 @@ } close(s); +} +#endif + +#if SUN +void get_mac(u8_t mac[]) { + struct arpreq parpreq; + struct sockaddr_in *psa; + struct in_addr inaddr; + struct hostent *phost; + char hostname[MAXHOSTNAMELEN]; + char **paddrs; + char *utmac; + int sock; + int status=0; + + utmac = getenv("UTMAC"); + if (utmac) + { + if ( strlen(utmac) == 17 ) + { + if (sscanf(utmac,"%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", + &mac[0],&mac[1],&mac[2],&mac[3],&mac[4],&mac[5]) == 6) + { + return; + } + } + + } + + mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0; + + gethostname(hostname, MAXHOSTNAMELEN); + + phost = gethostbyname(hostname); + + paddrs = phost->h_addr_list; + memcpy(&inaddr.s_addr, *paddrs, sizeof(inaddr.s_addr)); + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + if(sock == -1) + { + mac[5] = 1; + return; + } + + memset(&parpreq, 0, sizeof(struct arpreq)); + psa = (struct sockaddr_in *) &parpreq.arp_pa; + memset(psa, 0, sizeof(struct sockaddr_in)); + psa->sin_family = AF_INET; + memcpy(&psa->sin_addr, *paddrs, sizeof(struct in_addr)); + + status = ioctl(sock, SIOCGARP, &parpreq); + + if(status == -1) + { + mac[5] = 2; + return; + } + + mac[0] = (unsigned char) parpreq.arp_ha.sa_data[0]; + mac[1] = (unsigned char) parpreq.arp_ha.sa_data[1]; + mac[2] = (unsigned char) parpreq.arp_ha.sa_data[2]; + mac[3] = (unsigned char) parpreq.arp_ha.sa_data[3]; + mac[4] = (unsigned char) parpreq.arp_ha.sa_data[4]; + mac[5] = (unsigned char) parpreq.arp_ha.sa_data[5]; } #endif @@ -386,3 +488,38 @@ } } #endif + +#if WIN && USE_SSL +char *strcasestr(const char *haystack, const char *needle) { + size_t length_needle; + size_t length_haystack; + size_t i; + + if (!haystack || !needle) + return NULL; + + length_needle = strlen(needle); + length_haystack = strlen(haystack) - length_needle + 1; + + for (i = 0; i < length_haystack; i++) + { + size_t j; + + for (j = 0; j < length_needle; j++) + { + unsigned char c1; + unsigned char c2; + + c1 = haystack[i+j]; + c2 = needle[j]; + if (toupper(c1) != toupper(c2)) + goto next; + } + return (char *) haystack + i; + next: + ; + } + + return NULL; +} +#endif diff --git a/vorbis.c b/vorbis.c index 0809bee..4050d96 100644 --- a/vorbis.c +++ b/vorbis.c @@ -2,6 +2,7 @@ * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012-2015, triode1@btinternet.com + * Ralph Irving 2015-2020, ralph_irving@hotmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +21,24 @@ #include "squeezelite.h" +/* +* with some low-end CPU, the decode call takes a fair bit of time and if the outputbuf is locked during that +* period, the output_thread (or equivalent) will be locked although there is plenty of samples available. +* Normally, with PRIO_INHERIT, that thread should increase decoder priority and get the lock quickly but it +* seems that when the streambuf has plenty of data, the decode thread grabs the CPU to much, even it the output +* thread has a higher priority. Using an interim buffer where vorbis decoder writes the output is not great from +* an efficiency (one extra memory copy) point of view, but it allows the lock to not be kept for too long +*/ +#if EMBEDDED +#define FRAME_BUF 2048 +#endif + +#if BYTES_PER_FRAME == 4 +#define ALIGN(n) (n) +#else +#define ALIGN(n) (n << 16) +#endif + // automatically select between floating point (preferred) and fixed point libraries: // NOTE: works with Tremor version here: http://svn.xiph.org/trunk/Tremor, not vorbisidec.1.0.2 currently in ubuntu @@ -27,11 +46,18 @@ // tremor's OggVorbis_File struct is normally smaller so this is ok, but padding added to malloc in case it is bigger #define OV_EXCLUDE_STATIC_CALLBACKS +#ifdef TREMOR_ONLY +#include +#else #include +#endif struct vorbis { OggVorbis_File *vf; bool opened; +#if FRAME_BUF + u8_t *write_buf; +#endif #if !LINKALL // vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library vorbis_info *(* ov_info)(OggVorbis_File *vf, int link); @@ -76,7 +102,9 @@ #if LINKALL #define OV(h, fn, ...) (ov_ ## fn)(__VA_ARGS__) #define TREMOR(h) 0 +#if !WIN extern int ov_read_tremor(); // needed to enable compilation, not linked +#endif #else #define OV(h, fn, ...) (h)->ov_##fn(__VA_ARGS__) #define TREMOR(h) (h)->ov_read_tremor @@ -86,51 +114,46 @@ static size_t _read_cb(void *ptr, size_t size, size_t nmemb, void *datasource) { size_t bytes; + LOCK_S; + bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, size * nmemb); memcpy(ptr, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); + UNLOCK_S; + return bytes / size; } // these are needed for older versions of tremor, later versions and libvorbis allow NULL to be used -static int _seek_cb(void *datasource, ogg_int64_t offset, int whence) { return -1; } +static int _seek_cb(void *datasource, ogg_int64_t offset, int whence) { return -1; } static int _close_cb(void *datasource) { return 0; } static long _tell_cb(void *datasource) { return 0; } static decode_state vorbis_decode(void) { static int channels; - bool end; frames_t frames; int bytes, s, n; u8_t *write_buf; LOCK_S; - LOCK_O_direct; - end = (stream.state <= DISCONNECT); - - IF_DIRECT( - frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; - ); - IF_PROCESS( - frames = process.max_in_frames; - ); - - if (!frames && end) { - UNLOCK_O_direct; + + if (stream.state <= DISCONNECT && !_buf_used(streambuf)) { UNLOCK_S; return DECODE_COMPLETE; } - + + UNLOCK_S; + if (decode.new_stream) { ov_callbacks cbs; int err; struct vorbis_info *info; cbs.read_func = _read_cb; - + if (TREMOR(v)) { cbs.seek_func = _seek_cb; cbs.close_func = _close_cb; cbs.tell_func = _tell_cb; } else { @@ -139,81 +162,102 @@ if ((err = OV(v, open_callbacks, streambuf, v->vf, NULL, 0, cbs)) < 0) { LOG_WARN("open_callbacks error: %d", err); - UNLOCK_O_direct; - UNLOCK_S; return DECODE_COMPLETE; } + v->opened = true; - info = OV(v, info, v->vf, -1); LOG_INFO("setting track_start"); - LOCK_O_not_direct; - output.next_sample_rate = decode_newstream(info->rate, output.supported_rates); - IF_DSD( output.next_dop = false; ) + LOCK_O; + output.next_sample_rate = decode_newstream(info->rate, output.supported_rates); + IF_DSD( output.next_fmt = PCM; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; - UNLOCK_O_not_direct; - - IF_PROCESS( - frames = process.max_in_frames; - ); + UNLOCK_O; channels = info->channels; if (channels > 2) { LOG_WARN("too many channels: %d", channels); - UNLOCK_O_direct; - UNLOCK_S; return DECODE_ERROR; } } - - bytes = frames * 2 * channels; // samples returned are 16 bits - + +#if FRAME_BUF IF_DIRECT( + frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; + frames = min(frames, FRAME_BUF); + write_buf = v->write_buf; + ); +#else + LOCK_O_direct; + IF_DIRECT( + frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; write_buf = outputbuf->writep; ); +#endif IF_PROCESS( + frames = process.max_in_frames; write_buf = process.inbuf; ); + + bytes = frames * 2 * channels; // samples returned are 16 bits // write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them +#ifdef TREMOR_ONLY + n = OV(v, read, v->vf, (char *)write_buf, bytes, &s); +#else if (!TREMOR(v)) { #if SL_LITTLE_ENDIAN n = OV(v, read, v->vf, (char *)write_buf, bytes, 0, 2, 1, &s); #else n = OV(v, read, v->vf, (char *)write_buf, bytes, 1, 2, 1, &s); #endif +#if !WIN } else { n = OV(v, read_tremor, v->vf, (char *)write_buf, bytes, &s); - } +#endif + } +#endif + +#if FRAME_BUF + LOCK_O_direct; +#endif if (n > 0) { - frames_t count; s16_t *iptr; - s32_t *optr; + ISAMPLE_T *optr; frames = n / 2 / channels; count = frames * channels; - // work backward to unpack samples to 4 bytes per sample - iptr = (s16_t *)write_buf + count; - optr = (s32_t *)write_buf + frames * 2; + // work backward to unpack samples (if needed) + iptr = (s16_t *) write_buf + count; + optr = (ISAMPLE_T *) write_buf + frames * 2; if (channels == 2) { +#if BYTES_PER_FRAME == 4 +#if FRAME_BUF + // copy needed only when DIRECT and FRAME_BUF + IF_DIRECT( + memcpy(outputbuf->writep, write_buf, frames * BYTES_PER_FRAME); + ) +#endif +#else while (count--) { - *--optr = *--iptr << 16; + *--optr = ALIGN(*--iptr); } +#endif } else if (channels == 1) { while (count--) { - *--optr = *--iptr << 16; - *--optr = *iptr << 16; + *--optr = ALIGN(*--iptr); + *--optr = ALIGN(*iptr); } } - + IF_DIRECT( _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); ); @@ -225,27 +269,27 @@ } else if (n == 0) { - LOG_INFO("end of stream"); - UNLOCK_O_direct; - UNLOCK_S; - return DECODE_COMPLETE; + if (stream.state <= DISCONNECT) { + LOG_INFO("partial decode"); + UNLOCK_O_direct; + return DECODE_COMPLETE; + } else { + LOG_INFO("no frame decoded"); + } } else if (n == OV_HOLE) { // recoverable hole in stream, seen when skipping LOG_DEBUG("hole in stream"); - + } else { LOG_INFO("ov_read error: %d", n); UNLOCK_O_direct; - UNLOCK_S; return DECODE_COMPLETE; } UNLOCK_O_direct; - UNLOCK_S; - return DECODE_RUNNING; } @@ -253,6 +297,9 @@ if (!v->vf) { v->vf = malloc(sizeof(OggVorbis_File) + 128); // add some padding as struct size may be larger memset(v->vf, 0, sizeof(OggVorbis_File) + 128); +#if FRAME_BUF + v->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME); +#endif } else { if (v->opened) { OV(v, clear, v->vf); @@ -267,6 +314,10 @@ v->opened = false; } free(v->vf); +#if FRAME_BUF + free(v->write_buf); + v->write_buf = NULL; +#endif v->vf = NULL; } @@ -307,7 +358,7 @@ static struct codec ret = { 'o', // id "ogg", // types - 2048, // min read + 4096, // min read 20480, // min space vorbis_open, // open vorbis_close, // close