initial commit

This commit is contained in:
philippe44
2019-05-19 00:11:01 -07:00
parent a5b37a13c9
commit 9b5bdd8cb4
17 changed files with 5996 additions and 0 deletions

5
CMakeLists.txt Normal file
View File

@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.5)
set(COMPONENT_SRCS "scan.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

68
Kconfig.projbuild Normal file
View File

@@ -0,0 +1,68 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
choice SCAN_METHOD
prompt "scan method"
default WIFI_FAST_SCAN
help
scan method for the esp32 to use
config WIFI_FAST_SCAN
bool "fast"
config WIFI_ALL_CHANNEL_SCAN
bool "all"
endchoice
choice SORT_METHOD
prompt "sort method"
default WIFI_CONNECT_AP_BY_SIGNAL
help
sort method for the esp32 to use
config WIFI_CONNECT_AP_BY_SIGNAL
bool "rssi"
config WIFI_CONNECT_AP_BY_SECURITY
bool "authmode"
endchoice
config FAST_SCAN_THRESHOLD
bool "fast scan threshold"
default y
help
wifi fast scan threshold
config FAST_SCAN_MINIMUM_SIGNAL
int "fast scan minimum rssi"
depends on FAST_SCAN_THRESHOLD
range -127 0
default -127
help
rssi is use to measure the signal
choice FAST_SCAN_WEAKEST_AUTHMODE
prompt "fast scan weakest authmode"
depends on FAST_SCAN_THRESHOLD
default EXAMPLE_OPEN
config EXAMPLE_OPEN
bool "open"
config EXAMPLE_WEP
bool "wep"
config EXAMPLE_WPA
bool "wpa"
config EXAMPLE_WPA2
bool "wpa2"
endchoice
endmenu

115
buffer.c Normal file
View File

@@ -0,0 +1,115 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
// fifo bufffers
#define _GNU_SOURCE
#include "squeezelite.h"
// _* called with muxtex locked
inline unsigned _buf_used(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->size - (buf->readp - buf->writep);
}
unsigned _buf_space(struct buffer *buf) {
return buf->size - _buf_used(buf) - 1; // reduce by one as full same as empty otherwise
}
unsigned _buf_cont_read(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->writep - buf->readp : buf->wrap - buf->readp;
}
unsigned _buf_cont_write(struct buffer *buf) {
return buf->writep >= buf->readp ? buf->wrap - buf->writep : buf->readp - buf->writep;
}
void _buf_inc_readp(struct buffer *buf, unsigned by) {
buf->readp += by;
if (buf->readp >= buf->wrap) {
buf->readp -= buf->size;
}
}
void _buf_inc_writep(struct buffer *buf, unsigned by) {
buf->writep += by;
if (buf->writep >= buf->wrap) {
buf->writep -= buf->size;
}
}
void buf_flush(struct buffer *buf) {
mutex_lock(buf->mutex);
buf->readp = buf->buf;
buf->writep = buf->buf;
mutex_unlock(buf->mutex);
}
// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
void buf_adjust(struct buffer *buf, size_t mod) {
size_t size;
mutex_lock(buf->mutex);
size = ((unsigned)(buf->base_size / mod)) * mod;
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
mutex_unlock(buf->mutex);
}
// called with mutex locked to resize, does not retain contents, reverts to original size if fails
void _buf_resize(struct buffer *buf, size_t size) {
free(buf->buf);
buf->buf = malloc(size);
if (!buf->buf) {
size = buf->size;
buf->buf= malloc(size);
if (!buf->buf) {
size = 0;
}
}
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
}
void buf_init(struct buffer *buf, size_t size) {
buf->buf = malloc(size);
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
mutex_create_p(buf->mutex);
}
void buf_destroy(struct buffer *buf) {
if (buf->buf) {
free(buf->buf);
buf->buf = NULL;
buf->size = 0;
buf->base_size = 0;
mutex_destroy(buf->mutex);
}
}

8
component.mk Normal file
View File

@@ -0,0 +1,8 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
CFLAGS += -DPOSIX -DLINKALL -DLOOPBACK -DDACAUDIO

296
decode.c Normal file
View File

@@ -0,0 +1,296 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
// decode thread
#include "squeezelite.h"
log_level loglevel;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct processstate process;
struct decodestate decode;
struct codec *codecs[MAX_CODECS];
struct codec *codec;
static bool running = true;
#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)
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
#if PROCESS
#define IF_DIRECT(x) if (decode.direct) { x }
#define IF_PROCESS(x) if (!decode.direct) { x }
#define MAY_PROCESS(x) { x }
#else
#define IF_DIRECT(x) { x }
#define IF_PROCESS(x)
#define MAY_PROCESS(x)
#endif
static void *decode_thread() {
while (running) {
size_t bytes, space, min_space;
bool toend;
bool ran = false;
LOCK_S;
bytes = _buf_used(streambuf);
toend = (stream.state <= DISCONNECT);
UNLOCK_S;
LOCK_O;
space = _buf_space(outputbuf);
UNLOCK_O;
LOCK_D;
if (decode.state == DECODE_RUNNING && codec) {
LOG_SDEBUG("streambuf bytes: %u outputbuf space: %u", bytes, space);
IF_DIRECT(
min_space = codec->min_space;
);
IF_PROCESS(
min_space = process.max_out_frames * BYTES_PER_FRAME;
);
if (space > min_space && (bytes > codec->min_read_bytes || toend)) {
decode.state = codec->decode();
IF_PROCESS(
if (process.in_frames) {
process_samples();
}
if (decode.state == DECODE_COMPLETE) {
process_drain();
}
);
if (decode.state != DECODE_RUNNING) {
LOG_INFO("decode %s", decode.state == DECODE_COMPLETE ? "complete" : "error");
LOCK_O;
if (output.fade_mode) _checkfade(false);
UNLOCK_O;
wake_controller();
}
ran = true;
}
}
UNLOCK_D;
if (!ran) {
usleep(100000);
}
}
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 = NULL;
loglevel = level;
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 || (order_codecs = strstr(include_codecs, "dsd"))))
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_dsd());
#endif
#if 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 (!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 || (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 || POSIX
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
#if WIN
thread = CreateThread(NULL, DECODE_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&decode_thread, NULL, 0, NULL);
#endif
decode.new_stream = true;
decode.state = DECODE_STOPPED;
MAY_PROCESS(
decode.direct = true;
decode.process = false;
);
}
void decode_close(void) {
LOG_INFO("close decode");
LOCK_D;
if (codec) {
codec->close();
codec = NULL;
}
running = false;
UNLOCK_D;
#if LINUX || OSX || FREEBSD
pthread_join(thread, NULL);
#endif
mutex_destroy(decode.mutex);
}
void decode_flush(void) {
LOG_INFO("decode flush");
LOCK_D;
decode.state = DECODE_STOPPED;
IF_PROCESS(
process_flush();
);
UNLOCK_D;
}
unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]) {
// called with O locked to get sample rate for potentially processed output stream
// release O mutex during process_newstream as it can take some time
MAY_PROCESS(
if (decode.process) {
UNLOCK_O;
sample_rate = process_newstream(&decode.direct, sample_rate, supported_rates);
LOCK_O;
}
);
return sample_rate;
}
void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) {
int i;
LOG_INFO("codec open: '%c'", format);
LOCK_D;
decode.new_stream = true;
decode.state = DECODE_STOPPED;
MAY_PROCESS(
decode.direct = true; // potentially changed within codec when processing enabled
);
// find the required codec
for (i = 0; i < MAX_CODECS; ++i) {
if (codecs[i] && codecs[i]->id == format) {
if (codec && codec != codecs[i]) {
LOG_INFO("closing codec: '%c'", codec->id);
codec->close();
}
codec = codecs[i];
codec->open(sample_size, sample_rate, channels, endianness);
decode.state = DECODE_READY;
UNLOCK_D;
return;
}
}
UNLOCK_D;
LOG_ERROR("codec not found");
}

12
esp32.c Normal file
View File

@@ -0,0 +1,12 @@
#include <signal.h>
#include "esp_system.h"
#include "squeezelite.h"
void get_mac(u8_t mac[]) {
esp_read_mac(mac, ESP_MAC_WIFI_STA);
}
_sig_func_ptr signal(int sig, _sig_func_ptr func) {
return NULL;
}

837
main.c Normal file
View File

@@ -0,0 +1,837 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* 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 <signal.h>
#define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith, 2015-2019 Ralph Irving."
#define CODECS_BASE "flac,pcm,mp3,ogg"
#if NO_FAAD
#define CODECS_AAC ""
#else
#define CODECS_AAC ",aac"
#endif
#if FFMPEG
#define CODECS_FF ",wma,alac"
#else
#define CODECS_FF ""
#endif
#if DSD
#define CODECS_DSD ",dsd"
#else
#define CODECS_DSD ""
#endif
#define CODECS_MP3 " (mad,mpg for specific mp3 codec)"
#define CODECS CODECS_BASE CODECS_AAC CODECS_FF CODECS_DSD CODECS_MP3
static void usage(const char *argv0) {
printf(TITLE " See -t for license terms\n"
"Usage: %s [options]\n"
" -s <server>[:<port>]\tConnect to specified server, otherwise uses autodiscovery to find server\n"
" -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n"
" -l \t\t\tList output devices\n"
#if ALSA
" -a <b>:<p>:<f>:<m>\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 PA18API
" -a <frames>:<buffers>\tSpecify output target 4 byte frames per buffer, number of buffers\n"
#elif OSX && !defined(OSXPPC)
" -a <l>:<r>\t\tSpecify Portaudio params to open output device, l = target latency in ms, r = allow OSX to resample (0|1)\n"
#elif WIN
" -a <l>:<e>\t\tSpecify Portaudio params to open output device, l = target latency in ms, e = use exclusive mode for WASAPI (0|1)\n"
#else
" -a <l>\t\tSpecify Portaudio params to open output device, l = target latency in ms\n"
#endif
#endif
" -a <f>\t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n"
" -b <stream>:<output>\tSpecify internal Stream and Output buffer sizes in Kbytes\n"
" -c <codec1>,<codec2>\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 <timeout>\t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n"
#if !IR
" -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n"
#else
" -d <log>=<level>\tSet logging level, logs: all|slimproto|stream|decode|output|ir, level: info|debug|sdebug\n"
#endif
#if defined(GPIO) && defined(RPI)
" -G <Rpi GPIO#>:<H/L>\tSpecify the BCM GPIO# to use for Amp Power Relay and if the output should be Active High or Low\n"
#endif
" -e <codec1>,<codec2>\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n"
" -f <logfile>\t\tWrite debug to logfile\n"
#if IR
" -i [<filename>]\tEnable lirc remote control support (lirc config file ~/.lircrc used if filename not specified)\n"
#endif
" -m <mac addr>\t\tSet mac address, format: ab:cd:ef:12:34:56\n"
" -M <modelname>\tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n"
" -n <name>\t\tSet the player name\n"
" -N <filename>\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 <priority>\t\tSet real time priority of output thread (1-99)\n"
#endif
#if LINUX || FREEBSD || SUN
" -P <filename>\t\tStore the process id (PID) in filename\n"
#endif
" -r <rates>[:<delay>]\tSample rates supported, allows output to be off when squeezelite is started; rates = <maxrate>|<minrate>-<maxrate>|<rate1>,<rate2>,<rate3>; delay = optional delay switching rates in ms\n"
#if GPIO
" -S <Power Script>\tAbsolute path to script to launch on power commands from LMS\n"
#endif
#if RESAMPLE
" -R -u [params]\tResample, params = <recipe>:<flags>:<attenuation>:<precision>:<passband_end>:<stopband_start>:<phase_response>,\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"
" \t\t\t flags = num in hex,\n"
" \t\t\t attenuation = attenuation in dB to apply (default is -1db if not explicitly set),\n"
" \t\t\t precision = number of bits precision (NB. HQ = 20. VHQ = 28),\n"
" \t\t\t passband_end = number in percent (0dB pt. bandwidth to preserve. nyquist = 100%%),\n"
" \t\t\t stopband_start = number in percent (Aliasing/imaging control. > passband_end),\n"
" \t\t\t phase_response = 0-100 (0 = minimum / 50 = linear / 100 = maximum)\n"
#endif
#if DSD
#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"
#endif
# if ALSA
" -O <mixer device>\tSpecify mixer device, defaults to 'output device'\n"
" -L \t\t\tList volume controls for output device\n"
" -U <control>\t\tUnmute ALSA control and set to full volume (not supported with -V)\n"
" -V <control>\t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n"
" -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 <rate>\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 SUN
" SOLARIS"
#elif LINUX
" LINUX"
#endif
#if WIN
" WIN"
#endif
#if OSX
" OSX"
#endif
#if OSXPPC
"PPC"
#endif
#if FREEBSD
" FREEBSD"
#endif
#if ALSA
" ALSA"
#endif
#if PORTAUDIO
" PORTAUDIO"
#if PA18API
"18"
#endif
#endif
#if EVENTFD
" EVENTFD"
#endif
#if SELFPIPE
" SELFPIPE"
#endif
#if LOOPBACK
" LOOPBACK"
#endif
#if WINEVENT
" WINEVENT"
#endif
#if RESAMPLE_MP
" RESAMPLE_MP"
#else
#if RESAMPLE
" RESAMPLE"
#endif
#endif
#if FFMPEG
" FFMPEG"
#endif
#if NO_FAAD
" NO_FAAD"
#endif
#if VISEXPORT
" VISEXPORT"
#endif
#if IR
" IR"
#endif
#if GPIO
" GPIO"
#endif
#if RPI
" RPI"
#endif
#if DSD
" DSD"
#endif
#if USE_SSL
" SSL"
#endif
#if LINKALL
" LINKALL"
#endif
#if STATUSHACK
" STATUSHACK"
#endif
"\n\n",
argv0);
}
static void license(void) {
printf(TITLE "\n\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 <http://www.gnu.org/licenses/>.\n"
#if DSD
"\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 GPIO
"\nAdditions (c) Paul Hermann, 2015, 2017 under the same license terms\n"
"- Launch a script on power status change\n"
"- Control of Raspberry pi GPIO for amplifier power\n"
#endif
#if RPI
"\nContains wiringpi GPIO Interface library Copyright (c) 2012-2017\n"
"Gordon Henderson, which is subject to its own license.\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"
"<https://sourceforge.net/projects/lmsclients/files/source/>\n"
#endif
"\n"
);
}
static void sighandler(int signum) {
slimproto_stop();
// remove ourselves in case above does not work, second SIGINT will cause non gracefull shutdown
signal(signum, SIG_DFL);
}
int main(int argc, char **argv) {
char *server = NULL;
char *output_device = "default";
char *include_codecs = NULL;
char *exclude_codecs = "";
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;
unsigned output_buf_size = 0; // set later
unsigned rates[MAX_SUPPORTED_SAMPLERATES] = { 0 };
unsigned rate_delay = 0;
char *resample = NULL;
char *output_params = NULL;
unsigned idle = 0;
#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
unsigned dsd_delay = 0;
dsd_format dsd_outfmt = PCM;
#endif
#if VISEXPORT
bool visexport = false;
#endif
#if IR
char *lircrc = NULL;
#endif
log_level log_output = lWARN;
log_level log_stream = lWARN;
log_level log_decode = lWARN;
log_level log_slimproto = lWARN;
#if IR
log_level log_ir = lWARN;
#endif
int maxSampleRate = 0;
char *optarg = NULL;
int optind = 1;
int i;
#define MAXCMDLINE 512
char cmdline[MAXCMDLINE] = "";
get_mac(mac);
for (i = 0; i < argc && (strlen(argv[i]) + strlen(cmdline) + 2 < MAXCMDLINE); i++) {
strcat(cmdline, argv[i]);
strcat(cmdline, " ");
}
while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') {
char *opt = argv[optind] + 1;
if (strstr("oabcCdefmMnNpPrs"
#if ALSA
"UVO"
#endif
/*
* only allow '-Z <rate>' 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?W"
#if ALSA
"LX"
#endif
#if RESAMPLE
"uR"
#endif
#if DSD
"D"
#endif
#if VISEXPORT
"v"
#endif
#if IR
"i"
#endif
#if defined(GPIO) && defined(RPI)
"G"
#endif
#if GPIO
"S"
#endif
, opt)) {
optarg = NULL;
optind += 1;
} else {
fprintf(stderr, "\nOption error: -%s\n\n", opt);
usage(argv[0]);
exit(1);
}
switch (opt[0]) {
case 'o':
output_device = optarg;
#if ALSA
mixer_device = optarg;
#endif
break;
case 'a':
output_params = optarg;
break;
case 'b':
{
char *s = next_param(optarg, ':');
char *o = next_param(NULL, ':');
if (s) stream_buf_size = atoi(s) * 1024;
if (o) output_buf_size = atoi(o) * 1024;
}
break;
case 'c':
include_codecs = optarg;
break;
case 'C':
if (atoi(optarg) > 0) {
idle = atoi(optarg) * 1000;
}
break;
case 'e':
exclude_codecs = optarg;
break;
case 'd':
{
char *l = strtok(optarg, "=");
char *v = strtok(NULL, "=");
log_level new = lWARN;
if (l && v) {
if (!strcmp(v, "info")) new = lINFO;
if (!strcmp(v, "debug")) new = lDEBUG;
if (!strcmp(v, "sdebug")) new = lSDEBUG;
if (!strcmp(l, "all") || !strcmp(l, "slimproto")) log_slimproto = new;
if (!strcmp(l, "all") || !strcmp(l, "stream")) log_stream = new;
if (!strcmp(l, "all") || !strcmp(l, "decode")) log_decode = new;
if (!strcmp(l, "all") || !strcmp(l, "output")) log_output = new;
#if IR
if (!strcmp(l, "all") || !strcmp(l, "ir")) log_ir = new;
#endif
} else {
fprintf(stderr, "\nDebug settings error: -d %s\n\n", optarg);
usage(argv[0]);
exit(1);
}
}
break;
case 'f':
logfile = optarg;
break;
case 'm':
{
int byte = 0;
char *tmp;
if (!strncmp(optarg, "00:04:20", 8)) {
LOG_ERROR("ignoring mac address from hardware player range 00:04:20:**:**:**");
} else {
char *t = strtok(optarg, ":");
while (t && byte < 6) {
mac[byte++] = (u8_t)strtoul(t, &tmp, 16);
t = strtok(NULL, ":");
}
}
}
break;
case 'M':
modelname = optarg;
break;
case 'r':
{
char *rstr = next_param(optarg, ':');
char *dstr = next_param(NULL, ':');
if (rstr && strstr(rstr, ",")) {
// parse sample rates and sort them
char *r = next_param(rstr, ',');
unsigned tmp[MAX_SUPPORTED_SAMPLERATES] = { 0 };
int i, j;
int last = 999999;
for (i = 0; r && i < MAX_SUPPORTED_SAMPLERATES; ++i) {
tmp[i] = atoi(r);
r = next_param(NULL, ',');
}
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
int largest = 0;
for (j = 0; j < MAX_SUPPORTED_SAMPLERATES; ++j) {
if (tmp[j] > largest && tmp[j] < last) {
largest = tmp[j];
}
}
rates[i] = last = largest;
}
} else if (rstr) {
// optstr is <min>-<max> or <max>, extract rates from test rates within this range
unsigned ref[] TEST_RATES;
char *str1 = next_param(rstr, '-');
char *str2 = next_param(NULL, '-');
unsigned max = str2 ? atoi(str2) : (str1 ? atoi(str1) : ref[0]);
unsigned min = str1 && str2 ? atoi(str1) : 0;
unsigned tmp;
int i, j;
if (max < min) { tmp = max; max = min; min = tmp; }
rates[0] = max;
for (i = 0, j = 1; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
if (ref[i] < rates[j-1] && ref[i] >= min) {
rates[j++] = ref[i];
}
}
}
if (dstr) {
rate_delay = atoi(dstr);
}
if (rates[0]) {
user_rates = true;
}
}
break;
case 's':
server = optarg;
break;
case 'n':
name = optarg;
break;
case 'N':
namefile = optarg;
break;
case 'W':
pcm_check_header = true;
break;
#if ALSA
case 'p':
rt_priority = atoi(optarg);
if (rt_priority > 99 || rt_priority < 1) {
fprintf(stderr, "\nError: invalid priority: %s\n\n", optarg);
usage(argv[0]);
exit(1);
}
break;
#endif
#if LINUX || FREEBSD || SUN
case 'P':
pidfile = optarg;
break;
#endif
#ifndef DACAUDIO
case 'l':
list_devices();
exit(0);
break;
#endif
#if RESAMPLE
case 'u':
case 'R':
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
resample = argv[optind++];
} else {
resample = "";
}
break;
case 'Z':
maxSampleRate = atoi(optarg);
break;
#endif
#if DSD
case 'D':
dsd_outfmt = DOP;
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
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
#if VISEXPORT
case 'v':
visexport = true;
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':
if (output_mixer) {
fprintf(stderr, "-U and -V option should not be used at same time\n");
exit(1);
}
output_mixer = optarg;
break;
#endif
#if IR
case 'i':
if (optind < argc && argv[optind] && argv[optind][0] != '-') {
lircrc = argv[optind++];
} else {
lircrc = "~/.lircrc"; // liblirc_client will expand ~/
}
break;
#endif
#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':
license();
exit(0);
case '?':
usage(argv[0]);
exit(0);
default:
fprintf(stderr, "Arg error: %s\n", argv[optind]);
break;
}
}
// warn if command line includes something which isn't parsed
if (optind < argc) {
fprintf(stderr, "\nError: command line argument error\n\n");
usage(argv[0]);
exit(1);
}
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
#if defined(SIGQUIT)
signal(SIGQUIT, sighandler);
#endif
#if defined(SIGHUP)
signal(SIGHUP, sighandler);
#endif
#if USE_SSL && !LINKALL
ssl_loaded = load_ssl_symbols();
#endif
// set the output buffer size if not specified on the command line, take account of resampling
if (!output_buf_size) {
output_buf_size = OUTPUTBUF_SIZE;
if (resample) {
unsigned scale = 8;
if (rates[0]) {
scale = rates[0] / 44100;
if (scale > 8) scale = 8;
if (scale < 1) scale = 1;
}
output_buf_size *= scale;
}
}
if (logfile) {
if (!freopen(logfile, "a", stderr)) {
fprintf(stderr, "error opening logfile %s: %s\n", logfile, strerror(errno));
} else {
if (log_output >= lINFO || log_stream >= lINFO || log_decode >= lINFO || log_slimproto >= lINFO) {
fprintf(stderr, "\n%s\n", cmdline);
}
}
}
#if LINUX || FREEBSD || SUN
if (pidfile) {
if (!(pidfp = fopen(pidfile, "w")) ) {
fprintf(stderr, "Error opening pidfile %s: %s\n", pidfile, strerror(errno));
exit(1);
}
pidfile = realpath(pidfile, NULL); // daemonize will change cwd
}
if (daemonize) {
if (daemon(0, logfile ? 1 : 0)) {
fprintf(stderr, "error daemonizing: %s\n", strerror(errno));
}
}
if (pidfp) {
fprintf(pidfp, "%d\n", (int) getpid());
fclose(pidfp);
}
#endif
#if WIN
winsock_init();
#endif
stream_init(log_stream, stream_buf_size);
#if DACAUDIO
output_init_dac(log_output, output_buf_size, output_params, rates, rate_delay, idle);
#else
if (!strcmp(output_device, "-")) {
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, 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
}
#endif
#if DSD
dsd_init(dsd_outfmt, dsd_delay);
#endif
#if VISEXPORT
if (visexport) {
output_vis_init(log_output, mac);
}
#endif
decode_init(log_decode, include_codecs, exclude_codecs);
#if RESAMPLE
if (resample) {
process_init(resample);
}
#endif
#if IR
if (lircrc) {
ir_init(log_ir, lircrc);
}
#endif
if (name && namefile) {
fprintf(stderr, "-n and -N option should not be used at same time\n");
exit(1);
}
slimproto(log_slimproto, server, mac, name, namefile, modelname, maxSampleRate);
decode_close();
stream_close();
#if DACAUDIO
output_close_dac();
#else
if (!strcmp(output_device, "-")) {
output_close_stdout();
} else {
#if ALSA
output_close_alsa();
#endif
#if PORTAUDIO
output_close_pa();
#endif
}
#endif
#if IR
ir_close();
#endif
#if WIN
winsock_close();
#endif
#if LINUX || FREEBSD || SUN
if (pidfile) {
unlink(pidfile);
free(pidfile);
}
#endif
#if USE_SSL && !LINKALL
free_ssl_symbols();
#endif
exit(0);
}

450
output.c Normal file
View File

@@ -0,0 +1,450 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
// Common output function
#include "squeezelite.h"
static log_level loglevel;
struct outputstate output;
static struct buffer buf;
struct buffer *outputbuf = &buf;
u8_t *silencebuf;
#if DSD
u8_t *silencebuf_dsd;
#endif
bool user_rates = false;
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
// functions starting _* are called with mutex locked
frames_t _output_frames(frames_t avail) {
frames_t frames, size;
bool silence;
s32_t cross_gain_in = 0, cross_gain_out = 0; s32_t *cross_ptr = NULL;
s32_t gainL = output.current_replay_gain ? gain(output.gainL, output.current_replay_gain) : output.gainL;
s32_t gainR = output.current_replay_gain ? gain(output.gainR, output.current_replay_gain) : output.gainR;
if (output.invert) { gainL = -gainL; gainR = -gainR; }
frames = _buf_used(outputbuf) / BYTES_PER_FRAME;
silence = false;
// start when threshold met
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 100 && frames > output.start_frames) {
output.state = OUTPUT_RUNNING;
LOG_INFO("start buffer frames: %u", frames);
wake_controller();
}
// skip ahead - consume outputbuf but play nothing
if (output.state == OUTPUT_SKIP_FRAMES) {
if (frames > 0) {
frames_t skip = min(frames, output.skip_frames);
LOG_INFO("skip %u of %u frames", skip, output.skip_frames);
frames -= skip;
output.frames_played += skip;
while (skip > 0) {
frames_t cont_frames = min(skip, _buf_cont_read(outputbuf) / BYTES_PER_FRAME);
skip -= cont_frames;
_buf_inc_readp(outputbuf, cont_frames * BYTES_PER_FRAME);
}
}
output.state = OUTPUT_RUNNING;
}
// pause frames - play silence for required frames
if (output.state == OUTPUT_PAUSE_FRAMES) {
LOG_INFO("pause %u frames", output.pause_frames);
if (output.pause_frames == 0) {
output.state = OUTPUT_RUNNING;
} else {
silence = true;
frames = min(avail, output.pause_frames);
frames = min(frames, MAX_SILENCE_FRAMES);
output.pause_frames -= frames;
}
}
// start at - play silence until jiffies reached
if (output.state == OUTPUT_START_AT) {
u32_t now = gettime_ms();
if (now >= output.start_at || output.start_at > now + 10000) {
output.state = OUTPUT_RUNNING;
} else {
u32_t delta_frames = (output.start_at - now) * output.current_sample_rate / 1000;
silence = true;
frames = min(avail, delta_frames);
frames = min(frames, MAX_SILENCE_FRAMES);
}
}
// play silence if buffering or no frames
if (output.state <= OUTPUT_BUFFER || frames == 0) {
silence = true;
frames = min(avail, MAX_SILENCE_FRAMES);
}
LOG_SDEBUG("avail: %d frames: %d silence: %d", avail, frames, silence);
frames = min(frames, avail);
size = frames;
while (size > 0) {
frames_t out_frames;
frames_t cont_frames = _buf_cont_read(outputbuf) / BYTES_PER_FRAME;
int wrote;
if (output.track_start && !silence) {
if (output.track_start == outputbuf->readp) {
unsigned delay = 0;
if (output.current_sample_rate != output.next_sample_rate) {
delay = output.rate_delay;
}
IF_DSD(
if (output.outfmt != output.next_fmt) {
delay = output.dsd_delay;
}
)
frames -= size;
// add silence delay in two halves, before and after track start on rate or pcm-dop change
if (delay) {
output.state = OUTPUT_PAUSE_FRAMES;
if (!output.delay_active) {
output.pause_frames = output.current_sample_rate * delay / 2000;
output.delay_active = true; // first delay - don't process track start
break;
} else {
output.pause_frames = output.next_sample_rate * delay / 2000;
output.delay_active = false; // second delay - process track start
}
}
LOG_INFO("track start sample rate: %u replay_gain: %u", output.next_sample_rate, output.next_replay_gain);
output.frames_played = 0;
output.track_started = true;
output.track_start_time = gettime_ms();
output.current_sample_rate = output.next_sample_rate;
IF_DSD(
output.outfmt = output.next_fmt;
)
if (output.fade == FADE_INACTIVE || output.fade_mode != FADE_CROSSFADE) {
output.current_replay_gain = output.next_replay_gain;
}
output.track_start = NULL;
break;
} else if (output.track_start > outputbuf->readp) {
// reduce cont_frames so we find the next track start at beginning of next chunk
cont_frames = min(cont_frames, (output.track_start - outputbuf->readp) / BYTES_PER_FRAME);
}
}
IF_DSD(
if (output.outfmt != PCM) {
gainL = gainR = FIXED_ONE;
}
)
if (output.fade && !silence) {
if (output.fade == FADE_DUE) {
if (output.fade_start == outputbuf->readp) {
LOG_INFO("fade start reached");
output.fade = FADE_ACTIVE;
} else if (output.fade_start > outputbuf->readp) {
cont_frames = min(cont_frames, (output.fade_start - outputbuf->readp) / BYTES_PER_FRAME);
}
}
if (output.fade == FADE_ACTIVE) {
// find position within fade
frames_t cur_f = outputbuf->readp >= output.fade_start ? (outputbuf->readp - output.fade_start) / BYTES_PER_FRAME :
(outputbuf->readp + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
frames_t dur_f = output.fade_end >= output.fade_start ? (output.fade_end - output.fade_start) / BYTES_PER_FRAME :
(output.fade_end + outputbuf->size - output.fade_start) / BYTES_PER_FRAME;
if (cur_f >= dur_f) {
if (output.fade_mode == FADE_INOUT && output.fade_dir == FADE_DOWN) {
LOG_INFO("fade down complete, starting fade up");
output.fade_dir = FADE_UP;
output.fade_start = outputbuf->readp;
output.fade_end = outputbuf->readp + dur_f * BYTES_PER_FRAME;
if (output.fade_end >= outputbuf->wrap) {
output.fade_end -= outputbuf->size;
}
cur_f = 0;
} else if (output.fade_mode == FADE_CROSSFADE) {
LOG_INFO("crossfade complete");
if (_buf_used(outputbuf) >= dur_f * BYTES_PER_FRAME) {
_buf_inc_readp(outputbuf, dur_f * BYTES_PER_FRAME);
LOG_INFO("skipped crossfaded start");
} else {
LOG_WARN("unable to skip crossfaded start");
}
output.fade = FADE_INACTIVE;
output.current_replay_gain = output.next_replay_gain;
} else {
LOG_INFO("fade complete");
output.fade = FADE_INACTIVE;
}
}
// if fade in progress set fade gain, ensure cont_frames reduced so we get to end of fade at start of chunk
if (output.fade) {
if (output.fade_end > outputbuf->readp) {
cont_frames = min(cont_frames, (output.fade_end - outputbuf->readp) / BYTES_PER_FRAME);
}
if (output.fade_dir == FADE_UP || output.fade_dir == FADE_DOWN) {
// fade in, in-out, out handled via altering standard gain
s32_t fade_gain;
if (output.fade_dir == FADE_DOWN) {
cur_f = dur_f - cur_f;
}
fade_gain = to_gain((float)cur_f / (float)dur_f);
gainL = gain(gainL, fade_gain);
gainR = gain(gainR, fade_gain);
if (output.invert) { gainL = -gainL; gainR = -gainR; }
}
if (output.fade_dir == FADE_CROSS) {
// cross fade requires special treatment - performed later based on these values
// support different replay gain for old and new track by retaining old value until crossfade completes
if (_buf_used(outputbuf) / BYTES_PER_FRAME > dur_f + size) {
cross_gain_in = to_gain((float)cur_f / (float)dur_f);
cross_gain_out = FIXED_ONE - cross_gain_in;
if (output.current_replay_gain) {
cross_gain_out = gain(cross_gain_out, output.current_replay_gain);
}
if (output.next_replay_gain) {
cross_gain_in = gain(cross_gain_in, output.next_replay_gain);
}
gainL = output.gainL;
gainR = output.gainR;
if (output.invert) { gainL = -gainL; gainR = -gainR; }
cross_ptr = (s32_t *)(output.fade_end + cur_f * BYTES_PER_FRAME);
} else {
LOG_INFO("unable to continue crossfade - too few samples");
output.fade = FADE_INACTIVE;
}
}
}
}
}
out_frames = !silence ? min(size, cont_frames) : size;
wrote = output.write_cb(out_frames, silence, gainL, gainR, cross_gain_in, cross_gain_out, &cross_ptr);
if (wrote <= 0) {
frames -= size;
break;
} else {
out_frames = (frames_t)wrote;
}
size -= out_frames;
_vis_export(outputbuf, &output, out_frames, silence);
if (!silence) {
_buf_inc_readp(outputbuf, out_frames * BYTES_PER_FRAME);
output.frames_played += out_frames;
}
}
LOG_SDEBUG("wrote %u frames", frames);
return frames;
}
void _checkfade(bool start) {
frames_t bytes;
LOG_INFO("fade mode: %u duration: %u %s", output.fade_mode, output.fade_secs, start ? "track-start" : "track-end");
bytes = output.next_sample_rate * BYTES_PER_FRAME * output.fade_secs;
if (output.fade_mode == FADE_INOUT) {
/* 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))) {
bytes = min(bytes, outputbuf->size - BYTES_PER_FRAME); // shorter than full buffer otherwise start and end align
LOG_INFO("fade IN: %u frames", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_UP;
output.fade_start = outputbuf->writep;
output.fade_end = output.fade_start + bytes;
if (output.fade_end >= outputbuf->wrap) {
output.fade_end -= outputbuf->size;
}
}
if (!start && (output.fade_mode == FADE_OUT || output.fade_mode == FADE_INOUT)) {
bytes = min(_buf_used(outputbuf), bytes);
LOG_INFO("fade %s: %u frames", output.fade_mode == FADE_INOUT ? "IN-OUT" : "OUT", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_DOWN;
output.fade_start = outputbuf->writep - bytes;
if (output.fade_start < outputbuf->buf) {
output.fade_start += outputbuf->size;
}
output.fade_end = outputbuf->writep;
}
if (start && output.fade_mode == FADE_CROSSFADE) {
if (_buf_used(outputbuf) != 0) {
if (output.next_sample_rate != output.current_sample_rate) {
LOG_INFO("crossfade disabled as sample rates differ");
return;
}
bytes = min(bytes, _buf_used(outputbuf)); // max of current remaining samples from previous track
bytes = min(bytes, (frames_t)(outputbuf->size * 0.9)); // max of 90% of outputbuf as we consume additional buffer during crossfade
LOG_INFO("CROSSFADE: %u frames", bytes / BYTES_PER_FRAME);
output.fade = FADE_DUE;
output.fade_dir = FADE_CROSS;
output.fade_start = outputbuf->writep - bytes;
if (output.fade_start < outputbuf->buf) {
output.fade_start += outputbuf->size;
}
output.fade_end = outputbuf->writep;
output.track_start = output.fade_start;
} else if (outputbuf->size == OUTPUTBUF_SIZE && outputbuf->readp == outputbuf->buf) {
// if default setting used and nothing in buffer attempt to resize to provide full crossfade support
LOG_INFO("resize outputbuf for crossfade");
_buf_resize(outputbuf, OUTPUTBUF_SIZE_CROSSFADE);
#if LINUX || FREEBSD
touch_memory(outputbuf->buf, outputbuf->size);
#endif
}
}
}
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle) {
unsigned i;
loglevel = level;
output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
LOG_DEBUG("outputbuf size: %u", output_buf_size);
buf_init(outputbuf, output_buf_size);
if (!outputbuf->buf) {
LOG_ERROR("unable to malloc output buffer");
exit(0);
}
silencebuf = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
if (!silencebuf) {
LOG_ERROR("unable to malloc silence buffer");
exit(0);
}
memset(silencebuf, 0, MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
IF_DSD(
silencebuf_dsd = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME);
if (!silencebuf_dsd) {
LOG_ERROR("unable to malloc silence dsd buffer");
exit(0);
}
dsd_silence_frames((u32_t *)silencebuf_dsd, MAX_SILENCE_FRAMES);
)
LOG_DEBUG("idle timeout: %u", idle);
output.state = idle ? OUTPUT_OFF: OUTPUT_STOPPED;
output.device = device;
output.fade = FADE_INACTIVE;
output.invert = false;
output.error_opening = false;
output.idle_to = (u32_t) idle;
/* 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];
}
}
#ifndef DACAUDIO
else {
if (!test_open(output.device, output.supported_rates, user_rates)) {
LOG_ERROR("unable to open output device: %s", output.device);
exit(0);
}
}
#endif
if (user_rates) {
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
output.supported_rates[i] = rates[i];
}
}
// set initial sample rate, preferring 44100
for (i = 0; i < MAX_SUPPORTED_SAMPLERATES; ++i) {
if (output.supported_rates[i] == 44100) {
output.default_sample_rate = 44100;
break;
}
}
if (!output.default_sample_rate) {
output.default_sample_rate = output.supported_rates[0];
}
output.current_sample_rate = output.default_sample_rate;
if (loglevel >= lINFO) {
char rates_buf[10 * MAX_SUPPORTED_SAMPLERATES] = "";
for (i = 0; output.supported_rates[i]; ++i) {
char s[10];
sprintf(s, "%d ", output.supported_rates[i]);
strcat(rates_buf, s);
}
LOG_INFO("supported rates: %s", rates_buf);
}
}
void output_close_common(void) {
buf_destroy(outputbuf);
free(silencebuf);
IF_DSD(
free(silencebuf_dsd);
)
}
void output_flush(void) {
LOG_INFO("flush output buffer");
buf_flush(outputbuf);
LOCK;
output.fade = FADE_INACTIVE;
if (output.state != OUTPUT_OFF) {
output.state = OUTPUT_STOPPED;
if (output.error_opening) {
output.current_sample_rate = output.default_sample_rate;
}
output.delay_active = false;
}
output.frames_played = 0;
UNLOCK;
}

154
output_dac.c Normal file
View File

@@ -0,0 +1,154 @@
#include "squeezelite.h"
#include <signal.h>
static log_level loglevel;
static bool running = true;
extern struct outputstate output;
extern struct buffer *outputbuf;
#define LOCK mutex_lock(outputbuf->mutex)
#define UNLOCK mutex_unlock(outputbuf->mutex)
#define FRAME_BLOCK MAX_SILENCE_FRAMES
extern u8_t *silencebuf;
// buffer to hold output data so we can block on writing outside of output lock, allocated on init
static u8_t *buf;
static unsigned buffill;
static int bytes_per_frame;
static thread_type thread;
static int _dac_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);
static void *output_thread();
void set_volume(unsigned left, unsigned right) {}
void output_init_dac(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
LOG_INFO("init output DAC");
buf = malloc(FRAME_BLOCK * BYTES_PER_FRAME);
if (!buf) {
LOG_ERROR("unable to malloc buf");
return;
}
buffill = 0;
memset(&output, 0, sizeof(output));
output.format = S32_LE;
output.start_frames = FRAME_BLOCK * 2;
output.write_cb = &_dac_write_frames;
output.rate_delay = rate_delay;
if (params) {
if (!strcmp(params, "32")) output.format = S32_LE;
if (!strcmp(params, "24")) output.format = S24_3LE;
if (!strcmp(params, "16")) output.format = S16_LE;
}
// ensure output rate is specified to avoid test open
if (!rates[0]) {
rates[0] = 44100;
}
output_init_common(level, "-", output_buf_size, rates, 0);
#if LINUX || OSX || FREEBSD || POSIX
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
#if WIN
thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL);
#endif
}
void output_close_dac(void) {
LOG_INFO("close output");
LOCK;
running = false;
UNLOCK;
free(buf);
output_close_common();
}
static int _dac_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) {
u8_t *obuf;
if (!silence) {
if (output.fade == FADE_ACTIVE && output.fade_dir == FADE_CROSS && *cross_ptr) {
_apply_cross(outputbuf, out_frames, cross_gain_in, cross_gain_out, cross_ptr);
}
obuf = outputbuf->readp;
} else {
obuf = silencebuf;
}
_scale_and_pack_frames(buf + buffill * bytes_per_frame, (s32_t *)(void *)obuf, out_frames, gainL, gainR, output.format);
buffill += out_frames;
return (int)out_frames;
}
static void *output_thread() {
LOCK;
switch (output.format) {
case S32_LE:
bytes_per_frame = 4 * 2; break;
case S24_3LE:
bytes_per_frame = 3 * 2; break;
case S16_LE:
bytes_per_frame = 2 * 2; break;
default:
bytes_per_frame = 4 * 2; break;
break;
}
UNLOCK;
while (running) {
LOCK;
output.device_frames = 0;
output.updated = gettime_ms();
output.frames_played_dmp = output.frames_played;
_output_frames(FRAME_BLOCK);
UNLOCK;
if (buffill) {
//fwrite(buf, bytes_per_frame, buffill, stdout);
buffill = 0;
}
}
return 0;
}

365
output_pack.c Normal file
View File

@@ -0,0 +1,365 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
// Scale and pack functions
#include "squeezelite.h"
#define MAX_SCALESAMPLE 0x7fffffffffffLL
#define MIN_SCALESAMPLE -MAX_SCALESAMPLE
// inlining these on windows prevents them being linkable...
#if !WIN
inline
#endif
s32_t gain(s32_t gain, s32_t sample) {
s64_t res = (s64_t)gain * (s64_t)sample;
if (res > MAX_SCALESAMPLE) res = MAX_SCALESAMPLE;
if (res < MIN_SCALESAMPLE) res = MIN_SCALESAMPLE;
return (s32_t) (res >> 16);
}
#if !WIN
inline
#endif
s32_t to_gain(float f) {
return (s32_t)(f * 65536.0F);
}
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;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
*(optr++) = (*(inputptr) >> 16 & 0x0000ffff) | (*(inputptr + 1) & 0xffff0000);
inputptr += 2;
}
} else {
while (cnt--) {
*(optr++) = (gain(gainL, *(inputptr)) >> 16 & 0x0000ffff) | (gain(gainR, *(inputptr+1)) & 0xffff0000);
inputptr += 2;
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0x00ff0000) << 8 | (lsample & 0xff000000) >> 8 |
(rsample & 0x00ff0000) >> 8 | (rsample & 0xff000000) >> 24;
}
}
#endif
}
break;
case S24_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
*(optr++) = *(inputptr++) >> 8;
*(optr++) = *(inputptr++) >> 8;
}
} else {
while (cnt--) {
*(optr++) = gain(gainL, *(inputptr++)) >> 8;
*(optr++) = gain(gainR, *(inputptr++)) >> 8;
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt--) {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) =
(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
*(optr++) =
(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
}
} else {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) =
(lsample & 0xff000000) >> 16 | (lsample & 0x00ff0000) | (lsample & 0x0000ff00 << 16);
*(optr++) =
(rsample & 0xff000000) >> 16 | (rsample & 0x00ff0000) | (rsample & 0x0000ff00 << 16);
}
}
#endif
}
break;
case S24_3LE:
{
u8_t *optr = (u8_t *)(void *)outputptr;
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
while (cnt) {
// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
// falls through to exception case when not aligned or if less than 2 frames to move
if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
u32_t *o_ptr = (u32_t *)(void *)optr;
while (cnt >= 2) {
s32_t l1 = *(inputptr++); s32_t r1 = *(inputptr++);
s32_t l2 = *(inputptr++); s32_t r2 = *(inputptr++);
#if SL_LITTLE_ENDIAN
*(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16;
*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8;
*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
#else
*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
(r1 & 0x0000ff00) >> 8;
*(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
(l2 & 0x00ff0000) >> 16;
*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
(r2 & 0xff000000) >> 24;
#endif
optr += 12;
cnt -= 2;
}
} else {
s32_t lsample = *(inputptr++);
s32_t rsample = *(inputptr++);
*(optr++) = (lsample & 0x0000ff00) >> 8;
*(optr++) = (lsample & 0x00ff0000) >> 16;
*(optr++) = (lsample & 0xff000000) >> 24;
*(optr++) = (rsample & 0x0000ff00) >> 8;
*(optr++) = (rsample & 0x00ff0000) >> 16;
*(optr++) = (rsample & 0xff000000) >> 24;
cnt--;
}
}
} else {
while (cnt) {
// attempt to do 32 bit memory accesses - move 2 frames at once: 16 bytes -> 12 bytes
// falls through to exception case when not aligned or if less than 2 frames to move
if (((uintptr_t)optr & 0x3) == 0 && cnt >= 2) {
u32_t *o_ptr = (u32_t *)(void *)optr;
while (cnt >= 2) {
s32_t l1 = gain(gainL, *(inputptr++)); s32_t r1 = gain(gainR, *(inputptr++));
s32_t l2 = gain(gainL, *(inputptr++)); s32_t r2 = gain(gainR, *(inputptr++));
#if SL_LITTLE_ENDIAN
*(o_ptr++) = (l1 & 0xffffff00) >> 8 | (r1 & 0x0000ff00) << 16;
*(o_ptr++) = (r1 & 0xffff0000) >> 16 | (l2 & 0x00ffff00) << 8;
*(o_ptr++) = (l2 & 0xff000000) >> 24 | (r2 & 0xffffff00);
#else
*(o_ptr++) = (l1 & 0x0000ff00) << 16 | (l1 & 0x00ff0000) | (l1 & 0xff000000) >> 16 |
(r1 & 0x0000ff00) >> 8;
*(o_ptr++) = (r1 & 0x00ff0000) << 8 | (r1 & 0xff000000) >> 8 | (l2 & 0x0000ff00) |
(l2 & 0x00ff0000) >> 16;
*(o_ptr++) = (l2 & 0xff000000) | (r2 & 0x0000ff00) << 8 | (r2 & 0x00ff0000) >> 8 |
(r2 & 0xff000000) >> 24;
#endif
optr += 12;
cnt -= 2;
}
} else {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(inputptr++));
*(optr++) = (lsample & 0x0000ff00) >> 8;
*(optr++) = (lsample & 0x00ff0000) >> 16;
*(optr++) = (lsample & 0xff000000) >> 24;
*(optr++) = (rsample & 0x0000ff00) >> 8;
*(optr++) = (rsample & 0x00ff0000) >> 16;
*(optr++) = (rsample & 0xff000000) >> 24;
cnt--;
}
}
}
}
break;
case S32_LE:
{
u32_t *optr = (u32_t *)(void *)outputptr;
#if SL_LITTLE_ENDIAN
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
memcpy(outputptr, inputptr, cnt * BYTES_PER_FRAME);
} else {
while (cnt--) {
*(optr++) = gain(gainL, *(inputptr++));
*(optr++) = gain(gainR, *(inputptr++));
}
}
#else
if (gainL == FIXED_ONE && gainR == FIXED_ONE) {
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 {
while (cnt--) {
s32_t lsample = gain(gainL, *(inputptr++));
s32_t rsample = gain(gainR, *(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;
default:
break;
}
}
#if !WIN
inline
#endif
void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) {
s32_t *ptr = (s32_t *)(void *)outputbuf->readp;
frames_t count = out_frames * 2;
while (count--) {
if (*cross_ptr > (s32_t *)outputbuf->wrap) {
*cross_ptr -= outputbuf->size / BYTES_PER_FRAME * 2;
}
*ptr = gain(cross_gain_out, *ptr) + gain(cross_gain_in, **cross_ptr);
ptr++; (*cross_ptr)++;
}
}
#if !WIN
inline
#endif
void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR) {
s32_t *ptrL = (s32_t *)(void *)outputbuf->readp;
s32_t *ptrR = (s32_t *)(void *)outputbuf->readp + 1;
while (count--) {
*ptrL = gain(gainL, *ptrL);
*ptrR = gain(gainR, *ptrR);
ptrL += 2;
ptrR += 2;
}
}

438
pcm.c Normal file
View File

@@ -0,0 +1,438 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
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;
bool pcm_check_header = false;
#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
#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, 705600, 768000
};
static u32_t sample_rate;
static u32_t sample_size;
static u32_t channels;
static bool bigendian;
static bool limit;
static u32_t audio_left;
static u32_t bytes_per_frame;
typedef enum { UNKNOWN = 0, WAVE, AIFF } header_format;
static void _check_header(void) {
u8_t *ptr = streambuf->readp;
unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
header_format format = UNKNOWN;
// simple parsing of wav and aiff headers and get to samples
if (bytes > 12) {
if (!memcmp(ptr, "RIFF", 4) && !memcmp(ptr+8, "WAVE", 4)) {
LOG_INFO("WAVE");
format = WAVE;
} else if (!memcmp(ptr, "FORM", 4) && (!memcmp(ptr+8, "AIFF", 4) || !memcmp(ptr+8, "AIFC", 4))) {
LOG_INFO("AIFF");
format = AIFF;
}
}
if (format != UNKNOWN) {
ptr += 12;
bytes -= 12;
while (bytes >= 8) {
char id[5];
unsigned len;
memcpy(id, ptr, 4);
id[4] = '\0';
if (format == WAVE) {
len = *(ptr+4) | *(ptr+5) << 8 | *(ptr+6) << 16| *(ptr+7) << 24;
} else {
len = *(ptr+4) << 24 | *(ptr+5) << 16 | *(ptr+6) << 8 | *(ptr+7);
}
LOG_INFO("header: %s len: %d", id, len);
if (format == WAVE && !memcmp(ptr, "data", 4)) {
ptr += 8;
_buf_inc_readp(streambuf, ptr - streambuf->readp);
audio_left = len;
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;
}
if (format == AIFF && !memcmp(ptr, "SSND", 4) && bytes >= 16) {
unsigned offset = *(ptr+8) << 24 | *(ptr+9) << 16 | *(ptr+10) << 8 | *(ptr+11);
// following 4 bytes is blocksize - ignored
ptr += 8 + 8;
_buf_inc_readp(streambuf, ptr + offset - streambuf->readp);
// 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;
}
if (format == WAVE && !memcmp(ptr, "fmt ", 4) && bytes >= 24) {
// override the server parsed values with our own
channels = *(ptr+10) | *(ptr+11) << 8;
sample_rate = *(ptr+12) | *(ptr+13) << 8 | *(ptr+14) << 16 | *(ptr+15) << 24;
sample_size = (*(ptr+22) | *(ptr+23) << 8) / 8;
bigendian = 0;
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
}
if (format == AIFF && !memcmp(ptr, "COMM", 4) && bytes >= 26) {
int exponent;
// override the server parsed values with our own
channels = *(ptr+8) << 8 | *(ptr+9);
sample_size = (*(ptr+14) << 8 | *(ptr+15)) / 8;
bigendian = 1;
// sample rate is encoded as IEEE 80 bit extended format
// make some assumptions to simplify processing - only use first 32 bits of mantissa
exponent = ((*(ptr+16) & 0x7f) << 8 | *(ptr+17)) - 16383 - 31;
sample_rate = *(ptr+18) << 24 | *(ptr+19) << 16 | *(ptr+20) << 8 | *(ptr+21);
while (exponent < 0) { sample_rate >>= 1; ++exponent; }
while (exponent > 0) { sample_rate <<= 1; --exponent; }
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
}
if (bytes >= len + 8) {
ptr += len + 8;
bytes -= (len + 8);
} else {
LOG_WARN("run out of data");
return;
}
}
} else {
LOG_WARN("unknown format - can't parse header");
}
}
static decode_state pcm_decode(void) {
unsigned bytes, in, out;
frames_t frames, count;
u32_t *optr;
u8_t *iptr;
u8_t tmp[16];
LOCK_S;
if ( decode.new_stream && ( ( stream.state == STREAMING_FILE ) || pcm_check_header ) ) {
_check_header();
}
LOCK_O_direct;
bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
IF_DIRECT(
out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
);
IF_PROCESS(
out = process.max_in_frames;
);
if ((stream.state <= DISCONNECT && bytes == 0) || (limit && audio_left == 0)) {
UNLOCK_O_direct;
UNLOCK_S;
return DECODE_COMPLETE;
}
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);
if (output.fade_mode) _checkfade(true);
#endif
UNLOCK_O_not_direct;
IF_PROCESS(
out = process.max_in_frames;
);
bytes_per_frame = channels * sample_size;
}
IF_DIRECT(
optr = (u32_t *)outputbuf->writep;
);
IF_PROCESS(
optr = (u32_t *)process.inbuf;
);
iptr = (u8_t *)streambuf->readp;
in = bytes / bytes_per_frame;
// handle frame wrapping round end of streambuf
// - only need if resizing of streambuf does not avoid this, could occur in localfile case
if (in == 0 && bytes > 0 && _buf_used(streambuf) >= bytes_per_frame) {
memcpy(tmp, iptr, bytes);
memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes);
iptr = tmp;
in = 1;
}
frames = min(in, out);
frames = min(frames, MAX_DECODE_FRAMES);
if (limit && frames * bytes_per_frame > audio_left) {
LOG_INFO("reached end of audio");
frames = audio_left / bytes_per_frame;
}
count = frames * channels;
if (channels == 2) {
if (sample_size == 1) {
while (count--) {
*optr++ = *iptr++ << 24;
}
} else if (sample_size == 2) {
if (bigendian) {
while (count--) {
*optr++ = *(iptr) << 24 | *(iptr+1) << 16;
iptr += 2;
}
} else {
while (count--) {
*optr++ = *(iptr) << 16 | *(iptr+1) << 24;
iptr += 2;
}
}
} else if (sample_size == 3) {
if (bigendian) {
while (count--) {
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
iptr += 3;
}
} else {
while (count--) {
*optr++ = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
iptr += 3;
}
}
} else if (sample_size == 4) {
if (bigendian) {
while (count--) {
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
iptr += 4;
}
} else {
while (count--) {
*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
iptr += 4;
}
}
}
} else if (channels == 1) {
if (sample_size == 1) {
while (count--) {
*optr = *iptr++ << 24;
*(optr+1) = *optr;
optr += 2;
}
} else if (sample_size == 2) {
if (bigendian) {
while (count--) {
*optr = *(iptr) << 24 | *(iptr+1) << 16;
*(optr+1) = *optr;
iptr += 2;
optr += 2;
}
} else {
while (count--) {
*optr = *(iptr) << 16 | *(iptr+1) << 24;
*(optr+1) = *optr;
iptr += 2;
optr += 2;
}
}
} else if (sample_size == 3) {
if (bigendian) {
while (count--) {
*optr = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8;
*(optr+1) = *optr;
iptr += 3;
optr += 2;
}
} else {
while (count--) {
*optr = *(iptr) << 8 | *(iptr+1) << 16 | *(iptr+2) << 24;
*(optr+1) = *optr;
iptr += 3;
optr += 2;
}
}
} else if (sample_size == 4) {
if (bigendian) {
while (count--) {
*optr++ = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
*(optr+1) = *optr;
iptr += 4;
optr += 2;
}
} else {
while (count--) {
*optr++ = *(iptr) | *(iptr+1) << 8 | *(iptr+2) << 16 | *(iptr+3) << 24;
*(optr+1) = *optr;
iptr += 4;
optr += 2;
}
}
}
} else {
LOG_ERROR("unsupported channels");
}
LOG_SDEBUG("decoded %u frames", frames);
_buf_inc_readp(streambuf, frames * bytes_per_frame);
if (limit) {
audio_left -= frames * bytes_per_frame;
}
IF_DIRECT(
_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
);
IF_PROCESS(
process.in_frames = frames;
);
UNLOCK_O_direct;
UNLOCK_S;
return DECODE_RUNNING;
}
static void pcm_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
sample_size = size - '0' + 1;
sample_rate = sample_rates[rate - '0'];
channels = chan - '0';
bigendian = (endianness == '0');
limit = false;
LOG_INFO("pcm size: %u rate: %u chan: %u bigendian: %u", sample_size, sample_rate, channels, bigendian);
buf_adjust(streambuf, sample_size * channels);
}
static void pcm_close(void) {
buf_adjust(streambuf, 1);
}
struct codec *register_pcm(void) {
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;
}

148
scan.c Normal file
View File

@@ -0,0 +1,148 @@
/* Scan Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
/*
This example shows how to use the All Channel Scan or Fast Scan to connect
to a Wi-Fi network.
In the Fast Scan mode, the scan will stop as soon as the first network matching
the SSID is found. In this mode, an application can set threshold for the
authentication mode and the Signal strength. Networks that do not meet the
threshold requirements will be ignored.
In the All Channel Scan mode, the scan will end only after all the channels
are scanned, and connection will start with the best network. The networks
can be sorted based on Authentication Mode or Signal Strength. The priority
for the Authentication mode is: WPA2 > WPA > WEP > Open
*/
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "sys/socket.h"
#include "string.h"
/*Set the SSID and Password via "make menuconfig"*/
#define DEFAULT_SSID CONFIG_WIFI_SSID
#define DEFAULT_PWD CONFIG_WIFI_PASSWORD
#if CONFIG_WIFI_ALL_CHANNEL_SCAN
#define DEFAULT_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN
#elif CONFIG_WIFI_FAST_SCAN
#define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
#else
#define DEFAULT_SCAN_METHOD WIFI_FAST_SCAN
#endif /*CONFIG_SCAN_METHOD*/
#if CONFIG_WIFI_CONNECT_AP_BY_SIGNAL
#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
#elif CONFIG_WIFI_CONNECT_AP_BY_SECURITY
#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY
#else
#define DEFAULT_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL
#endif /*CONFIG_SORT_METHOD*/
#if CONFIG_FAST_SCAN_THRESHOLD
#define DEFAULT_RSSI CONFIG_FAST_SCAN_MINIMUM_SIGNAL
#if CONFIG_EXAMPLE_OPEN
#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
#elif CONFIG_EXAMPLE_WEP
#define DEFAULT_AUTHMODE WIFI_AUTH_WEP
#elif CONFIG_EXAMPLE_WPA
#define DEFAULT_AUTHMODE WIFI_AUTH_WPA_PSK
#elif CONFIG_EXAMPLE_WPA2
#define DEFAULT_AUTHMODE WIFI_AUTH_WPA2_PSK
#else
#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
#endif
#else
#define DEFAULT_RSSI -127
#define DEFAULT_AUTHMODE WIFI_AUTH_OPEN
#endif /*CONFIG_FAST_SCAN_THRESHOLD*/
static const char *TAG = "scan";
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip: %s", ip4addr_ntoa(&event->ip_info.ip));
}
}
/* Initialize Wi-Fi as sta and set scan method */
static void wifi_scan(void)
{
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
wifi_config_t wifi_config = {
.sta = {
.ssid = DEFAULT_SSID,
.password = DEFAULT_PWD,
.scan_method = DEFAULT_SCAN_METHOD,
.sort_method = DEFAULT_SORT_METHOD,
.threshold.rssi = DEFAULT_RSSI,
.threshold.authmode = DEFAULT_AUTHMODE,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
}
int main(int argc, char**argv);
void app_main()
{
int i;
char **argv, *_argv[] = {
"squeezelite-esp32",
"-n",
"ESP32",
"-d",
"all=info",
"-d",
"slimproto=debug",
"-b",
"128:2000",
};
// can't do strtok on FLASH strings
argv = malloc(sizeof(_argv));
for (i = 0; i < sizeof(_argv)/sizeof(char*); i++) {
argv[i] = strdup(_argv[i]);
}
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
wifi_scan();
main(sizeof(_argv)/sizeof(char*), argv);
}

976
slimproto.c Normal file
View File

@@ -0,0 +1,976 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* 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
#define MAXBUF 4096
#if SL_LITTLE_ENDIAN
#define LOCAL_PLAYER_IP 0x0100007f // 127.0.0.1
#define LOCAL_PLAYER_PORT 0x9b0d // 3483
#else
#define LOCAL_PLAYER_IP 0x7f000001 // 127.0.0.1
#define LOCAL_PLAYER_PORT 0x0d9b // 3483
#endif
static sockfd sock = -1;
static in_addr_t slimproto_ip = 0;
extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct codec *codecs[];
#if IR
extern struct irstate ir;
#endif
event_event wake_e;
#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)
#define LOCK_D mutex_lock(decode.mutex)
#define UNLOCK_D mutex_unlock(decode.mutex)
#if IR
#define LOCK_I mutex_lock(ir.mutex)
#define UNLOCK_I mutex_unlock(ir.mutex)
#endif
static struct {
u32_t updated;
u32_t stream_start;
u32_t stream_full;
u32_t stream_size;
u64_t stream_bytes;
u32_t output_full;
u32_t output_size;
u32_t frames_played;
u32_t device_frames;
u32_t current_sample_rate;
u32_t last;
stream_state stream_state;
} status;
int autostart;
bool sentSTMu, sentSTMo, sentSTMl;
u32_t new_server;
char *new_server_cap;
#define PLAYER_NAME_LEN 64
char player_name[PLAYER_NAME_LEN + 1] = "";
const char *name_file = NULL;
void send_packet(u8_t *packet, size_t len) {
u8_t *ptr = packet;
unsigned try = 0;
ssize_t n;
while (len) {
n = send(sock, ptr, len, MSG_NOSIGNAL);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK && try < 10) {
LOG_DEBUG("retrying (%d) writing to socket", ++try);
usleep(1000);
continue;
}
LOG_INFO("failed writing to socket: %s", strerror(last_error()));
return;
}
ptr += n;
len -= n;
}
}
static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) {
#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
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);
pkt.length = htonl(sizeof(struct HELO_packet) - 8 + strlen(base_cap) + strlen(fixed_cap) + strlen(var_cap));
pkt.deviceid = 12; // squeezeplay
pkt.revision = 0;
packn(&pkt.wlan_channellist, reconnect ? 0x4000 : 0x0000);
packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
memcpy(pkt.mac, mac, 6);
LOG_INFO("mac: %02x:%02x:%02x:%02x:%02x:%02x", pkt.mac[0], pkt.mac[1], pkt.mac[2], pkt.mac[3], pkt.mac[4], pkt.mac[5]);
LOG_INFO("cap: %s%s%s", base_cap, fixed_cap, var_cap);
send_packet((u8_t *)&pkt, sizeof(pkt));
send_packet((u8_t *)base_cap, strlen(base_cap));
send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
send_packet((u8_t *)var_cap, strlen(var_cap));
}
static void sendSTAT(const char *event, u32_t server_timestamp) {
struct STAT_packet pkt;
u32_t now = gettime_ms();
u32_t ms_played;
if (status.current_sample_rate && status.frames_played && status.frames_played > status.device_frames) {
ms_played = (u32_t)(((u64_t)(status.frames_played - status.device_frames) * (u64_t)1000) / (u64_t)status.current_sample_rate);
#ifndef STATUSHACK
if (now > status.updated) ms_played += (now - status.updated);
#endif
LOG_SDEBUG("ms_played: %u (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
} else if (status.frames_played && now > status.stream_start) {
ms_played = now - status.stream_start;
LOG_SDEBUG("ms_played: %u using elapsed time (frames_played: %u device_frames: %u)", ms_played, status.frames_played, status.device_frames);
} else {
LOG_SDEBUG("ms_played: 0");
ms_played = 0;
}
memset(&pkt, 0, sizeof(struct STAT_packet));
memcpy(&pkt.opcode, "STAT", 4);
pkt.length = htonl(sizeof(struct STAT_packet) - 8);
memcpy(&pkt.event, event, 4);
// num_crlf
// mas_initialized; mas_mode;
packN(&pkt.stream_buffer_fullness, status.stream_full);
packN(&pkt.stream_buffer_size, status.stream_size);
packN(&pkt.bytes_received_H, (u64_t)status.stream_bytes >> 32);
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
pkt.signal_strength = 0xffff;
packN(&pkt.jiffies, now);
packN(&pkt.output_buffer_size, status.output_size);
packN(&pkt.output_buffer_fullness, status.output_full);
packN(&pkt.elapsed_seconds, ms_played / 1000);
// voltage;
packN(&pkt.elapsed_milliseconds, ms_played);
pkt.server_timestamp = server_timestamp; // keep this is server format - don't unpack/pack
// error_code;
LOG_DEBUG("STAT: %s", event);
if (loglevel == lSDEBUG) {
LOG_SDEBUG("received bytesL: %u streambuf: %u outputbuf: %u calc elapsed: %u real elapsed: %u (diff: %d) device: %u delay: %d",
(u32_t)status.stream_bytes, status.stream_full, status.output_full, ms_played, now - status.stream_start,
ms_played - now + status.stream_start, status.device_frames * 1000 / status.current_sample_rate, now - status.updated);
}
send_packet((u8_t *)&pkt, sizeof(pkt));
}
static void sendDSCO(disconnect_code disconnect) {
struct DSCO_packet pkt;
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "DSCO", 4);
pkt.length = htonl(sizeof(pkt) - 8);
pkt.reason = disconnect & 0xFF;
LOG_DEBUG("DSCO: %d", disconnect);
send_packet((u8_t *)&pkt, sizeof(pkt));
}
static void sendRESP(const char *header, size_t len) {
struct RESP_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "RESP", 4);
pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
LOG_DEBUG("RESP");
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)header, len);
}
static void sendMETA(const char *meta, size_t len) {
struct META_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "META", 4);
pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
LOG_DEBUG("META");
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)meta, len);
}
static void sendSETDName(const char *name) {
struct SETD_header pkt_header;
memset(&pkt_header, 0, sizeof(pkt_header));
memcpy(&pkt_header.opcode, "SETD", 4);
pkt_header.id = 0; // id 0 is playername S:P:Squeezebox2
pkt_header.length = htonl(sizeof(pkt_header) + strlen(name) + 1 - 8);
LOG_DEBUG("set playername: %s", name);
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
send_packet((u8_t *)name, strlen(name) + 1);
}
#if IR
void sendIR(u32_t code, u32_t ts) {
struct IR_packet pkt;
memset(&pkt, 0, sizeof(pkt));
memcpy(&pkt.opcode, "IR ", 4);
pkt.length = htonl(sizeof(pkt) - 8);
packN(&pkt.jiffies, ts);
pkt.ir_code = htonl(code);
LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
send_packet((u8_t *)&pkt, sizeof(pkt));
}
#endif
static void process_strm(u8_t *pkt, int len) {
struct strm_packet *strm = (struct strm_packet *)pkt;
LOG_DEBUG("strm command %c", strm->command);
switch(strm->command) {
case 't':
sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
break;
case 'q':
decode_flush();
output_flush();
status.frames_played = 0;
stream_disconnect();
sendSTAT("STMf", 0);
buf_flush(streambuf);
break;
case 'f':
decode_flush();
output_flush();
status.frames_played = 0;
if (stream_disconnect()) {
sendSTAT("STMf", 0);
}
buf_flush(streambuf);
break;
case 'p':
{
unsigned interval = unpackN(&strm->replay_gain);
LOCK_O;
output.pause_frames = interval * status.current_sample_rate / 1000;
if (interval) {
output.state = OUTPUT_PAUSE_FRAMES;
} else {
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
}
UNLOCK_O;
if (!interval) sendSTAT("STMp", 0);
LOG_DEBUG("pause interval: %u", interval);
}
break;
case 'a':
{
unsigned interval = unpackN(&strm->replay_gain);
LOCK_O;
output.skip_frames = interval * status.current_sample_rate / 1000;
output.state = OUTPUT_SKIP_FRAMES;
UNLOCK_O;
LOG_DEBUG("skip ahead interval: %u", interval);
}
break;
case 'u':
{
unsigned jiffies = unpackN(&strm->replay_gain);
LOCK_O;
output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING;
output.start_at = jiffies;
#ifdef STATUSHACK
status.frames_played = output.frames_played;
#endif
UNLOCK_O;
LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms());
sendSTAT("STMr", 0);
}
break;
case 's':
{
unsigned header_len = len - sizeof(struct strm_packet);
char *header = (char *)(pkt + sizeof(struct strm_packet));
in_addr_t ip = (in_addr_t)strm->server_ip; // keep in network byte order
u16_t port = strm->server_port; // keep in network byte order
if (ip == 0) ip = slimproto_ip;
LOG_DEBUG("strm s autostart: %c transition period: %u transition type: %u codec: %c",
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);
break;
}
if (strm->format != '?') {
codec_open(strm->format, strm->pcm_sample_size, strm->pcm_sample_rate, strm->pcm_channels, strm->pcm_endianness);
} else if (autostart >= 2) {
// extension to slimproto to allow server to detect codec from response header and send back in codc message
LOG_DEBUG("streaming unknown codec");
} else {
LOG_WARN("unknown codec requires autostart >= 2");
break;
}
if (ip == LOCAL_PLAYER_IP && port == LOCAL_PLAYER_PORT) {
// extension to slimproto for LocalPlayer - header is filename not http header, don't expect cont
stream_file(header, header_len, strm->threshold * 1024);
autostart -= 2;
} else {
stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2);
}
sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false;
LOCK_O;
output.threshold = strm->output_threshold;
output.next_replay_gain = unpackN(&strm->replay_gain);
output.fade_mode = strm->transition_type - '0';
output.fade_secs = strm->transition_period;
output.invert = (strm->flags & 0x03) == 0x03;
LOG_DEBUG("set fade mode: %u", output.fade_mode);
UNLOCK_O;
}
break;
default:
LOG_WARN("unhandled strm %c", strm->command);
break;
}
}
static void process_cont(u8_t *pkt, int len) {
struct cont_packet *cont = (struct cont_packet *)pkt;
cont->metaint = unpackN(&cont->metaint);
LOG_DEBUG("cont metaint: %u loop: %u", cont->metaint, cont->loop);
if (autostart > 1) {
autostart -= 2;
LOCK_S;
if (stream.state == STREAMING_WAIT) {
stream.state = STREAMING_BUFFERING;
stream.meta_interval = stream.meta_next = cont->metaint;
}
UNLOCK_S;
wake_controller();
}
}
static void process_codc(u8_t *pkt, int len) {
struct codc_packet *codc = (struct codc_packet *)pkt;
LOG_DEBUG("codc: %c", codc->format);
codec_open(codc->format, codc->pcm_sample_size, codc->pcm_sample_rate, codc->pcm_channels, codc->pcm_endianness);
}
static void process_aude(u8_t *pkt, int len) {
struct aude_packet *aude = (struct aude_packet *)pkt;
LOG_DEBUG("enable spdif: %d dac: %d", aude->enable_spdif, aude->enable_dac);
LOCK_O;
if (!aude->enable_spdif && output.state != OUTPUT_OFF) {
output.state = OUTPUT_OFF;
}
if (aude->enable_spdif && output.state == OUTPUT_OFF && !output.idle_to) {
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
}
UNLOCK_O;
}
static void process_audg(u8_t *pkt, int len) {
struct audg_packet *audg = (struct audg_packet *)pkt;
audg->gainL = unpackN(&audg->gainL);
audg->gainR = unpackN(&audg->gainR);
LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust);
set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE);
}
static void process_setd(u8_t *pkt, int len) {
struct setd_packet *setd = (struct setd_packet *)pkt;
// handle player name query and change
if (setd->id == 0) {
if (len == 5) {
if (strlen(player_name)) {
sendSETDName(player_name);
}
} else if (len > 5) {
strncpy(player_name, setd->data, PLAYER_NAME_LEN);
player_name[PLAYER_NAME_LEN] = '\0';
LOG_INFO("set name: %s", setd->data);
// confirm change to server
sendSETDName(setd->data);
// write name to name_file if -N option set
if (name_file) {
FILE *fp = fopen(name_file, "w");
if (fp) {
LOG_INFO("storing name in %s", name_file);
fputs(player_name, fp);
fclose(fp);
} else {
LOG_WARN("unable to store new name in %s", name_file);
}
}
}
}
}
#define SYNC_CAP ",SyncgroupID="
#define SYNC_CAP_LEN 13
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");
if (len - sizeof(struct serv_packet) == 10) {
if (!new_server_cap) {
new_server_cap = malloc(SYNC_CAP_LEN + 10 + 1);
}
new_server_cap[0] = '\0';
strcat(new_server_cap, SYNC_CAP);
strncat(new_server_cap, (const char *)(pkt + sizeof(struct serv_packet)), 10);
} else {
if (new_server_cap) {
free(new_server_cap);
new_server_cap = NULL;
}
}
}
struct handler {
char opcode[5];
void (*handler)(u8_t *, int);
};
static struct handler handlers[] = {
{ "strm", process_strm },
{ "cont", process_cont },
{ "codc", process_codc },
{ "aude", process_aude },
{ "audg", process_audg },
{ "setd", process_setd },
{ "serv", process_serv },
{ "", NULL },
};
static void process(u8_t *pack, int len) {
struct handler *h = handlers;
while (h->handler && strncmp((char *)pack, h->opcode, 4)) { h++; }
if (h->handler) {
LOG_DEBUG("%s", h->opcode);
h->handler(pack, len);
} else {
pack[4] = '\0';
LOG_WARN("unhandled %s", (char *)pack);
}
}
static bool running;
static void slimproto_run() {
static u8_t buffer[MAXBUF];
int expect = 0;
int got = 0;
u32_t now;
static u32_t last = 0;
event_handle ehandles[2];
int timeouts = 0;
set_readwake_handles(ehandles, sock, wake_e);
while (running && !new_server) {
bool wake = false;
event_type ev;
if ((ev = wait_readwake(ehandles, 1000)) != EVENT_TIMEOUT) {
if (ev == EVENT_READ) {
if (expect > 0) {
int n = recv(sock, buffer + got, expect, 0);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
continue;
}
LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
return;
}
expect -= n;
got += n;
if (expect == 0) {
process(buffer, got);
got = 0;
}
} else if (expect == 0) {
int n = recv(sock, buffer + got, 2 - got, 0);
if (n <= 0) {
if (n < 0 && last_error() == ERROR_WOULDBLOCK) {
continue;
}
LOG_INFO("error reading from socket: %s", n ? strerror(last_error()) : "closed");
return;
}
got += n;
if (got == 2) {
expect = buffer[0] << 8 | buffer[1]; // length pack 'n'
got = 0;
if (expect > MAXBUF) {
LOG_ERROR("FATAL: slimproto packet too big: %d > %d", expect, MAXBUF);
return;
}
}
} else {
LOG_ERROR("FATAL: negative expect");
return;
}
}
if (ev == EVENT_WAKE) {
wake = true;
}
timeouts = 0;
} else if (++timeouts > 35) {
// expect message from server every 5 seconds, but 30 seconds on mysb.com so timeout after 35 seconds
LOG_INFO("No messages from server - connection dead");
return;
}
// update playback state when woken or every 100ms
now = gettime_ms();
if (wake || now - last > 100 || last > now) {
bool _sendSTMs = false;
bool _sendDSCO = false;
bool _sendRESP = false;
bool _sendMETA = false;
bool _sendSTMd = false;
bool _sendSTMt = false;
bool _sendSTMl = false;
bool _sendSTMu = false;
bool _sendSTMo = false;
bool _sendSTMn = false;
bool _stream_disconnect = false;
bool _start_output = false;
decode_state _decode_state;
disconnect_code disconnect_code;
static char header[MAX_HEADER];
size_t header_len = 0;
#if IR
bool _sendIR = false;
u32_t ir_code, ir_ts;
#endif
last = now;
LOCK_S;
status.stream_full = _buf_used(streambuf);
status.stream_size = streambuf->size;
status.stream_bytes = stream.bytes;
status.stream_state = stream.state;
if (stream.state == DISCONNECT) {
disconnect_code = stream.disconnect;
stream.state = STOPPED;
_sendDSCO = true;
}
if (!stream.sent_headers &&
(stream.state == STREAMING_HTTP || stream.state == STREAMING_WAIT || stream.state == STREAMING_BUFFERING)) {
header_len = stream.header_len;
memcpy(header, stream.header, header_len);
_sendRESP = true;
stream.sent_headers = true;
}
if (stream.meta_send) {
header_len = stream.header_len;
memcpy(header, stream.header, header_len);
_sendMETA = true;
stream.meta_send = false;
}
UNLOCK_S;
LOCK_D;
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;
sentSTMl = true;
} else if (autostart == 1) {
decode.state = DECODE_RUNNING;
_start_output = true;
}
// autostart 2 and 3 require cont to be received first
}
if (decode.state == DECODE_COMPLETE || decode.state == DECODE_ERROR) {
if (decode.state == DECODE_COMPLETE) _sendSTMd = true;
if (decode.state == DECODE_ERROR) _sendSTMn = true;
decode.state = DECODE_STOPPED;
if (status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) {
_stream_disconnect = true;
}
}
_decode_state = decode.state;
UNLOCK_D;
LOCK_O;
status.output_full = _buf_used(outputbuf);
status.output_size = outputbuf->size;
status.frames_played = output.frames_played_dmp;
status.current_sample_rate = output.current_sample_rate;
status.updated = output.updated;
status.device_frames = output.device_frames;
if (output.track_started) {
_sendSTMs = true;
output.track_started = false;
status.stream_start = output.track_start_time;
#ifdef STATUSHACK
status.frames_played = output.frames_played;
#endif
}
#if PORTAUDIO
if (output.pa_reopen) {
_pa_open();
output.pa_reopen = false;
}
#endif
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");
output.state = OUTPUT_STOPPED;
output.stop_time = now;
}
if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
_sendSTMo = true;
sentSTMo = true;
}
if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) {
output.state = OUTPUT_OFF;
LOG_DEBUG("output timeout");
}
if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
_sendSTMt = true;
status.last = now;
}
UNLOCK_O;
#if IR
LOCK_I;
if (ir.code) {
_sendIR = true;
ir_code = ir.code;
ir_ts = ir.ts;
ir.code = 0;
}
UNLOCK_I;
#endif
if (_stream_disconnect) stream_disconnect();
// send packets once locks released as packet sending can block
if (_sendDSCO) sendDSCO(disconnect_code);
if (_sendSTMs) sendSTAT("STMs", 0);
if (_sendSTMd) sendSTAT("STMd", 0);
if (_sendSTMt) sendSTAT("STMt", 0);
if (_sendSTMl) sendSTAT("STMl", 0);
if (_sendSTMu) sendSTAT("STMu", 0);
if (_sendSTMo) sendSTAT("STMo", 0);
if (_sendSTMn) sendSTAT("STMn", 0);
if (_sendRESP) sendRESP(header, header_len);
if (_sendMETA) sendMETA(header, header_len);
#if IR
if (_sendIR) sendIR(ir_code, ir_ts);
#endif
}
}
}
// called from other threads to wake state machine above
void wake_controller(void) {
wake_signal(wake_e);
}
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);
socklen_t enable = 1;
setsockopt(disc_sock, SOL_SOCKET, SO_BROADCAST, (const void *)&enable, sizeof(enable));
buf = "e";
memset(&d, 0, sizeof(d));
d.sin_family = AF_INET;
d.sin_port = htons(PORT);
d.sin_addr.s_addr = htonl(INADDR_BROADCAST);
pollinfo.fd = disc_sock;
pollinfo.events = POLLIN;
do {
LOG_INFO("sending discovery");
memset(&s, 0, sizeof(s));
if (sendto(disc_sock, buf, 1, 0, (struct sockaddr *)&d, sizeof(d)) < 0) {
LOG_INFO("error sending disovery");
}
if (poll(&pollinfo, 1, 5000) == 1) {
char readbuf[10];
socklen_t slen = sizeof(s);
recvfrom(disc_sock, readbuf, 10, 0, (struct sockaddr *)&s, &slen);
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);
return s.sin_addr.s_addr;
}
#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, 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);
loglevel = level;
running = true;
if (server) {
server_addr(server, &slimproto_ip, &slimproto_port);
}
if (!slimproto_ip) {
slimproto_ip = discover_server(server);
}
if (!slimproto_port) {
slimproto_port = PORT;
}
if (name) {
strncpy(player_name, name, PLAYER_NAME_LEN);
player_name[PLAYER_NAME_LEN] = '\0';
}
if (namefile) {
FILE *fp;
name_file = namefile;
fp = fopen(namefile, "r");
if (fp) {
if (!fgets(player_name, PLAYER_NAME_LEN, fp)) {
player_name[PLAYER_NAME_LEN] = '\0';
} else {
// strip any \n from fgets response
int len = strlen(player_name);
if (len > 0 && player_name[len - 1] == '\n') {
player_name[len - 1] = '\0';
}
LOG_INFO("retrieved name %s from %s", player_name, name_file);
}
fclose(fp);
}
}
if (!running) return;
LOCK_O;
snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING,
((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) {
strcat(fixed_cap, ",");
strcat(fixed_cap, codecs[i]->types);
}
}
UNLOCK_O;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = slimproto_ip;
serv_addr.sin_port = htons(slimproto_port);
LOG_INFO("connecting to %s:%d", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
new_server = 0;
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;
reconnect = false;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
set_nonblock(sock);
set_nosigpipe(sock);
if (connect_timeout(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr), 5) != 0) {
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(NULL);
}
} else {
struct sockaddr_in our_addr;
socklen_t len;
LOG_INFO("connected");
var_cap[0] = '\0';
failed_connect = 0;
// check if this is a local player now we are connected & signal to server via 'loc' format
// this requires LocalPlayer server plugin to enable direct file access
len = sizeof(our_addr);
getsockname(sock, (struct sockaddr *) &our_addr, &len);
if (our_addr.sin_addr.s_addr == serv_addr.sin_addr.s_addr) {
LOG_INFO("local player");
strcat(var_cap, ",loc");
}
// add on any capablity to be sent to the new server
if (new_server_cap) {
strcat(var_cap, new_server_cap);
free(new_server_cap);
new_server_cap = NULL;
}
sendHELO(reconnect, fixed_cap, var_cap, mac);
slimproto_run();
if (!reconnect) {
reconnect = true;
}
usleep(100000);
}
previous_server = 0;
closesocket(sock);
}
}
void slimproto_stop(void) {
LOG_INFO("slimproto stop");
running = false;
}

185
slimproto.h Normal file
View File

@@ -0,0 +1,185 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
// packet formats for slimproto
#ifndef SUN
#pragma pack(push, 1)
#else
#pragma pack(1)
#endif
// from S:N:Slimproto _hello_handler
struct HELO_packet {
char opcode[4];
u32_t length;
u8_t deviceid;
u8_t revision;
u8_t mac[6];
u8_t uuid[16];
u16_t wlan_channellist;
u32_t bytes_received_H, bytes_received_L;
char lang[2];
// u8_t capabilities[];
};
// S:N:Slimproto _stat_handler
struct STAT_packet {
char opcode[4];
u32_t length;
u32_t event;
u8_t num_crlf;
u8_t mas_initialized;
u8_t mas_mode;
u32_t stream_buffer_size;
u32_t stream_buffer_fullness;
u32_t bytes_received_H;
u32_t bytes_received_L;
u16_t signal_strength;
u32_t jiffies;
u32_t output_buffer_size;
u32_t output_buffer_fullness;
u32_t elapsed_seconds;
u16_t voltage;
u32_t elapsed_milliseconds;
u32_t server_timestamp;
u16_t error_code;
};
// S:N:Slimproto _disco_handler
struct DSCO_packet {
char opcode[4];
u32_t length;
u8_t reason;
};
// S:N:Slimproto _http_response_handler
struct RESP_header {
char opcode[4];
u32_t length;
// char header[] - added in sendRESP
};
// S:N:Slimproto _http_metadata_handler
struct META_header {
char opcode[4];
u32_t length;
// char metadata[]
};
// S:N:Slimproto _http_setting_handler
struct SETD_header {
char opcode[4];
u32_t length;
u8_t id;
// data
};
#if IR
struct IR_packet {
char opcode[4];
u32_t length;
u32_t jiffies;
u8_t format; // ignored by server
u8_t bits; // ignored by server
u32_t ir_code;
};
#endif
// from S:P:Squeezebox stream_s
struct strm_packet {
char opcode[4];
char command;
u8_t autostart;
u8_t format;
u8_t pcm_sample_size;
u8_t pcm_sample_rate;
u8_t pcm_channels;
u8_t pcm_endianness;
u8_t threshold;
u8_t spdif_enable;
u8_t transition_period;
u8_t transition_type;
u8_t flags;
u8_t output_threshold;
u8_t slaves;
u32_t replay_gain;
u16_t server_port;
u32_t server_ip;
//char request_string[];
};
// S:P:Squeezebox2
struct aude_packet {
char opcode[4];
u8_t enable_spdif;
u8_t enable_dac;
};
// S:P:Squeezebox2
struct audg_packet {
char opcode[4];
u32_t old_gainL; // unused
u32_t old_gainR; // unused
u8_t adjust;
u8_t preamp; // unused
u32_t gainL;
u32_t gainR;
// squence ids - unused
};
// S:P:Squeezebox2
struct cont_packet {
char opcode[4];
u32_t metaint;
u8_t loop;
// guids we don't use
};
// S:C:Commands
struct serv_packet {
char opcode[4];
u32_t server_ip;
// possible sync group
};
// S:P:Squeezebox2
struct setd_packet {
char opcode[4];
u8_t id;
char data[];
};
// codec open - this is an extension to slimproto to allow the server to read the header and then return decode params
struct codc_packet {
char opcode[4];
u8_t format;
u8_t pcm_sample_size;
u8_t pcm_sample_rate;
u8_t pcm_channels;
u8_t pcm_endianness;
};
#ifndef SUN
#pragma pack(pop)
#else
#pragma pack()
#endif

786
squeezelite.h Normal file
View File

@@ -0,0 +1,786 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
* 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, GPIO, IR, DSD, LINKALL to influence build
#define MAJOR_VERSION "1.9"
#define MINOR_VERSION "2"
#define MICRO_VERSION "1145"
#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
#endif
#define QUOTE(name) #name
#define STR(macro) QUOTE(macro)
#define MODEL_NAME_STRING STR(MODEL_NAME)
// build detection
#if defined(linux)
#define LINUX 1
#define OSX 0
#define WIN 0
#define FREEBSD 0
#elif defined (__APPLE__)
#define LINUX 0
#define OSX 1
#define WIN 0
#define FREEBSD 0
#elif defined (_MSC_VER)
#define LINUX 0
#define OSX 0
#define WIN 1
#define FREEBSD 0
#elif defined(__FreeBSD__)
#define LINUX 0
#define OSX 0
#define WIN 0
#define FREEBSD 1
#elif defined (__sun)
#define SUN 1
#define LINUX 1
#define PORTAUDIO 1
#define PA18API 1
#define OSX 0
#define WIN 0
#elif defined (POSIX)
#undef POSIX
#define POSIX 1
#else
#error unknown target
#endif
#if defined(DACAUDIO)
#undef DACAUDIO
#define DACAUDIO 1
#elif LINUX && !defined(PORTAUDIO)
#define ALSA 1
#define PORTAUDIO 0
#else
#define ALSA 0
#define PORTAUDIO 1
#endif
#if !defined(LOOPBACK)
#if SUN
#define EVENTFD 0
#define WINEVENT 0
#define SELFPIPE 1
#elif LINUX && !defined(SELFPIPE)
#define EVENTFD 1
#define SELFPIPE 0
#define WINEVENT 0
#endif
#if (LINUX && !EVENTFD) || OSX || FREEBSD
#define EVENTFD 0
#define SELFPIPE 1
#define WINEVENT 0
#endif
#if WIN
#define EVENTFD 0
#define SELFPIPE 0
#define WINEVENT 1
#endif
#else
#define EVENTFD 0
#define SELFPIPE 0
#define WINEVENT 0
#undef LOOPBACK
#define LOOPBACK 1
#endif
#if defined(RESAMPLE) || defined(RESAMPLE_MP)
#undef RESAMPLE
#define RESAMPLE 1 // resampling
#define PROCESS 1 // any sample processing (only resampling at present)
#else
#define RESAMPLE 0
#define PROCESS 0
#endif
#if defined(RESAMPLE_MP)
#undef RESAMPLE_MP
#define RESAMPLE_MP 1
#else
#define RESAMPLE_MP 0
#endif
#if defined(FFMPEG)
#undef FFMPEG
#define FFMPEG 1
#else
#define FFMPEG 0
#endif
#if (LINUX || OSX) && defined(VISEXPORT)
#undef VISEXPORT
#define VISEXPORT 1 // visulizer export support uses linux shared memory
#else
#define VISEXPORT 0
#endif
#if LINUX && defined(IR)
#undef IR
#define IR 1
#else
#define IR 0
#endif
#if defined(DSD)
#undef DSD
#define DSD 1
#define IF_DSD(x) { x }
#else
#undef DSD
#define DSD 0
#define IF_DSD(x)
#endif
#if defined(LINKALL)
#undef LINKALL
#define LINKALL 1 // link all libraries at build time - requires all to be available at run time
#else
#define LINKALL 0
#endif
#if defined (USE_SSL)
#undef USE_SSL
#define USE_SSL 1
#else
#define USE_SSL 0
#endif
#if !LINKALL
// dynamically loaded libraries at run time
#if LINUX
#define LIBFLAC "libFLAC.so.8"
#define LIBMAD "libmad.so.0"
#define LIBMPG "libmpg123.so.0"
#define LIBVORBIS "libvorbisfile.so.3"
#define LIBTREMOR "libvorbisidec.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"
#define LIBLIRC "liblirc_client.so.0"
#endif
#if OSX
#define LIBFLAC "libFLAC.8.dylib"
#define LIBMAD "libmad.0.dylib"
#define LIBMPG "libmpg123.0.dylib"
#define LIBVORBIS "libvorbisfile.3.dylib"
#define LIBTREMOR "libvorbisidec.1.dylib"
#define LIBFAAD "libfaad.2.dylib"
#define LIBAVUTIL "libavutil.%d.dylib"
#define LIBAVCODEC "libavcodec.%d.dylib"
#define LIBAVFORMAT "libavformat.%d.dylib"
#define LIBSOXR "libsoxr.0.dylib"
#endif
#if WIN
#define LIBFLAC "libFLAC.dll"
#define LIBMAD "libmad-0.dll"
#define LIBMPG "libmpg123-0.dll"
#define LIBVORBIS "libvorbisfile.dll"
#define LIBTREMOR "libvorbisidec.dll"
#define LIBFAAD "libfaad2.dll"
#define LIBAVUTIL "avutil-%d.dll"
#define LIBAVCODEC "avcodec-%d.dll"
#define LIBAVFORMAT "avformat-%d.dll"
#define LIBSOXR "libsoxr.dll"
#endif
#if FREEBSD
#define LIBFLAC "libFLAC.so.11"
#define LIBMAD "libmad.so.2"
#define LIBMPG "libmpg123.so.0"
#define LIBVORBIS "libvorbisfile.so.6"
#define LIBTREMOR "libvorbisidec.so.1"
#define LIBFAAD "libfaad.so.2"
#define LIBAVUTIL "libavutil.so.%d"
#define LIBAVCODEC "libavcodec.so.%d"
#define LIBAVFORMAT "libavformat.so.%d"
#endif
#endif // !LINKALL
// config options
#define STREAMBUF_SIZE (2 * 1024 * 1024)
#define OUTPUTBUF_SIZE (44100 * 8 * 10)
#define OUTPUTBUF_SIZE_CROSSFADE (OUTPUTBUF_SIZE * 12 / 10)
#define MAX_HEADER 4096 // do not reduce as icy-meta max is 4080
#if ALSA
#define ALSA_BUFFER_TIME 40
#define ALSA_PERIOD_COUNT 4
#define OUTPUT_RT_PRIORITY 45
#endif
#define SL_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#if SUN || OSXPPC
#undef SL_LITTLE_ENDIAN
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#if LINUX || OSX || FREEBSD || POSIX
#include <unistd.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <poll.h>
#if !LINKALL
#include <dlfcn.h>
#endif
#include <pthread.h>
#include <signal.h>
#if SUN
#include <sys/types.h>
#endif /* SUN */
#define STREAM_THREAD_STACK_SIZE 8 * 1024
#define DECODE_THREAD_STACK_SIZE 8 * 1024
#define OUTPUT_THREAD_STACK_SIZE 8 * 1024
#define IR_THREAD_STACK_SIZE 8 * 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;
#elif POSIX
typedef unsigned long long 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;
#define mutex_type pthread_mutex_t
#define mutex_create(m) pthread_mutex_init(&m, NULL)
#if POSIX
#define mutex_create_p(m) mutex_create(m)
#else
#define mutex_create_p(m) pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); pthread_mutex_init(&m, &attr); pthread_mutexattr_destroy(&attr)
#endif
#define mutex_lock(m) pthread_mutex_lock(&m)
#define mutex_unlock(m) pthread_mutex_unlock(&m)
#define mutex_destroy(m) pthread_mutex_destroy(&m)
#define thread_type pthread_t
#endif
#if WIN
#include <winsock2.h>
#include <ws2tcpip.h>
#include <io.h>
#define STREAM_THREAD_STACK_SIZE (1024 * 64)
#define DECODE_THREAD_STACK_SIZE (1024 * 128)
#define OUTPUT_THREAD_STACK_SIZE (1024 * 64)
typedef unsigned __int8 u8_t;
typedef unsigned __int16 u16_t;
typedef unsigned __int32 u32_t;
typedef unsigned __int64 u64_t;
typedef __int16 s16_t;
typedef __int32 s32_t;
typedef __int64 s64_t;
typedef BOOL bool;
#define true TRUE
#define false FALSE
#define inline __inline
#define mutex_type HANDLE
#define mutex_create(m) m = CreateMutex(NULL, FALSE, NULL)
#define mutex_create_p mutex_create
#define mutex_lock(m) WaitForSingleObject(m, INFINITE)
#define mutex_unlock(m) ReleaseMutex(m)
#define mutex_destroy(m) CloseHandle(m)
#define thread_type HANDLE
#define usleep(x) Sleep(x/1000)
#define sleep(x) Sleep(x*1000)
#define last_error() WSAGetLastError()
#define ERROR_WOULDBLOCK WSAEWOULDBLOCK
#define open _open
#define read _read
#define snprintf _snprintf
#define in_addr_t u32_t
#define socklen_t int
#define ssize_t int
#define RTLD_NOW 0
#endif
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
#endif
typedef u32_t frames_t;
typedef int sockfd;
#if EVENTFD
#include <sys/eventfd.h>
#define event_event int
#define event_handle struct pollfd
#define wake_create(e) e = eventfd(0, 0)
#define wake_signal(e) eventfd_write(e, 1)
#define wake_clear(e) eventfd_t val; eventfd_read(e, &val)
#define wake_close(e) close(e)
#endif
#if SELFPIPE
#define event_handle struct pollfd
#define event_event struct wake
#define wake_create(e) pipe(e.fds); set_nonblock(e.fds[0]); set_nonblock(e.fds[1])
#define wake_signal(e) write(e.fds[1], ".", 1)
#define wake_clear(e) char c[10]; read(e, &c, 10)
#define wake_close(e) close(e.fds[0]); close(e.fds[1])
struct wake {
int fds[2];
};
#endif
#if LOOPBACK
#define event_handle struct pollfd
#define event_event struct wake
#define wake_create(e) _wake_create(&e)
#define wake_signal(e) send(e.fds[1], ".", 1, 0)
#define wake_clear(e) char c; recv(e, &c, 1, 0)
#define wake_close(e) closesocket(e.mfds); closesocket(e.fds[0]); closesocket(e.fds[1])
struct wake {
int mfds;
int fds[2];
};
void _wake_create(event_event*);
#endif
#if WINEVENT
#define event_event HANDLE
#define event_handle HANDLE
#define wake_create(e) e = CreateEvent(NULL, FALSE, FALSE, NULL)
#define wake_signal(e) SetEvent(e)
#define wake_close(e) CloseHandle(e)
#endif
// printf/scanf formats for u64_t
#if (LINUX && __WORDSIZE == 64) || (FREEBSD && __LP64__)
#define FMT_u64 "%lu"
#define FMT_x64 "%lx"
#elif __GLIBC_HAVE_LONG_LONG || defined __GNUC__ || WIN || SUN
#define FMT_u64 "%llu"
#define FMT_x64 "%llx"
#else
#error can not support u64_t
#endif
#define MAX_SILENCE_FRAMES 2048
#define FIXED_ONE 0x10000
#define BYTES_PER_FRAME 8
#define min(a,b) (((a) < (b)) ? (a) : (b))
// logging
typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
const char *logtime(void);
void logprint(const char *fmt, ...);
#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
// 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);
void get_mac(u8_t *mac);
void set_nonblock(sockfd s);
int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout);
void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr);
void set_readwake_handles(event_handle handles[], sockfd s, event_event e);
event_type wait_readwake(event_handle handles[], int timeout);
void packN(u32_t *dest, u32_t val);
void packn(u16_t *dest, u16_t val);
u32_t unpackN(u32_t *src);
u16_t unpackn(u16_t *src);
#if OSX
void set_nosigpipe(sockfd s);
#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);
void *dlopen(const char *filename, int flag);
void *dlsym(void *handle, const char *symbol);
char *dlerror(void);
int poll(struct pollfd *fds, unsigned long numfds, int timeout);
#endif
#if LINUX || FREEBSD
void touch_memory(u8_t *buf, size_t size);
#endif
// buffer.c
struct buffer {
u8_t *buf;
u8_t *readp;
u8_t *writep;
u8_t *wrap;
size_t size;
size_t base_size;
mutex_type mutex;
};
// _* called with mutex locked
unsigned _buf_used(struct buffer *buf);
unsigned _buf_space(struct buffer *buf);
unsigned _buf_cont_read(struct buffer *buf);
unsigned _buf_cont_write(struct buffer *buf);
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_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, int maxSampleRate);
void slimproto_stop(void);
void wake_controller(void);
// stream.c
typedef enum { STOPPED = 0, DISCONNECT, STREAMING_WAIT,
STREAMING_BUFFERING, STREAMING_FILE, STREAMING_HTTP, SEND_HEADERS, RECV_HEADERS } stream_state;
typedef enum { DISCONNECT_OK = 0, LOCAL_DISCONNECT = 1, REMOTE_DISCONNECT = 2, UNREACHABLE = 3, TIMEOUT = 4 } disconnect_code;
struct streamstate {
stream_state state;
disconnect_code disconnect;
char *header;
size_t header_len;
bool sent_headers;
bool cont_wait;
u64_t bytes;
unsigned threshold;
u32_t meta_interval;
u32_t meta_next;
u32_t meta_left;
bool meta_send;
};
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);
bool stream_disconnect(void);
// decode.c
typedef enum { DECODE_STOPPED = 0, DECODE_READY, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state;
struct decodestate {
decode_state state;
bool new_stream;
mutex_type mutex;
#if PROCESS
bool direct;
bool process;
#endif
};
#if PROCESS
struct processstate {
u8_t *inbuf, *outbuf;
unsigned max_in_frames, max_out_frames;
unsigned in_frames, out_frames;
unsigned in_sample_rate, out_sample_rate;
unsigned long total_in, total_out;
};
#endif
struct codec {
char id;
char *types;
unsigned min_read_bytes;
unsigned min_space;
void (*open)(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
void (*close)(void);
decode_state (*decode)(void);
};
void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs);
void decode_close(void);
void decode_flush(void);
unsigned decode_newstream(unsigned sample_rate, unsigned supported_rates[]);
void codec_open(u8_t format, u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness);
#if PROCESS
// process.c
void process_samples(void);
void process_drain(void);
void process_flush(void);
unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]);
void process_init(char *opt);
#endif
#if RESAMPLE
// resample.c
void resample_samples(struct processstate *process);
bool resample_drain(struct processstate *process);
bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]);
void resample_flush(void);
bool resample_init(char *opt);
#endif
// output.c output_alsa.c output_pa.c output_pack.c
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 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;
output_format format;
const char *device;
#if ALSA
unsigned buffer;
unsigned period;
#endif
bool track_started;
#if PORTAUDIO
bool pa_reopen;
unsigned latency;
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;
unsigned frames_played;
unsigned frames_played_dmp;// frames played at the point delay is measured
unsigned current_sample_rate;
unsigned supported_rates[MAX_SUPPORTED_SAMPLERATES]; // ordered largest first so [0] is max_rate
unsigned default_sample_rate;
bool error_opening;
unsigned device_frames;
u32_t updated;
u32_t track_start_time;
u32_t current_replay_gain;
union {
u32_t pause_frames;
u32_t skip_frames;
u32_t start_at;
};
unsigned next_sample_rate; // set in decode thread
u8_t *track_start; // set in decode thread
u32_t gainL; // set by slimproto
u32_t gainR; // set by slimproto
bool invert; // set by slimproto
u32_t next_replay_gain; // set by slimproto
unsigned threshold; // set by slimproto
fade_state fade;
u8_t *fade_start;
u8_t *fade_end;
fade_dir fade_dir;
fade_mode fade_mode; // set by slimproto
unsigned fade_secs; // set by slimproto
unsigned rate_delay;
bool delay_active;
u32_t stop_time;
u32_t idle_to;
#if DSD
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
};
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
void output_close_common(void);
void output_flush(void);
// _* called with mutex locked
frames_t _output_frames(frames_t avail);
void _checkfade(bool);
// output_alsa.c
#if ALSA
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[], 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
// output_pa.c
#if PORTAUDIO
void list_devices(void);
void set_volume(unsigned left, unsigned right);
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_dac.c
#if DACAUDIO
void set_volume(unsigned left, unsigned right);
void output_init_dac(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle);
void output_close_dac(void);
#endif
// output_stdout.c
void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay);
void output_close_stdout(void);
// output_pack.c
void _scale_and_pack_frames(void *outputptr, s32_t *inputptr, frames_t cnt, s32_t gainL, s32_t gainR, output_format format);
void _apply_cross(struct buffer *outputbuf, frames_t out_frames, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr);
void _apply_gain(struct buffer *outputbuf, frames_t count, s32_t gainL, s32_t gainR);
s32_t gain(s32_t gain, s32_t sample);
s32_t to_gain(float f);
// output_vis.c
#if VISEXPORT
void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence);
void output_vis_init(log_level level, u8_t *mac);
void vis_stop(void);
#else
#define _vis_export(...)
#define vis_stop()
#endif
// dop.c
#if DSD
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 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
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);
struct codec *register_faad(void);
struct codec *register_dsd(void);
struct codec *register_ff(const char *codec);
//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;
#endif
// ir.c
#if IR
struct irstate {
mutex_type mutex;
u32_t code;
u32_t ts;
};
void ir_init(log_level level, char *lircrc);
void ir_close(void);
#endif
// sslsym.c
#if USE_SSL && !LINKALL
bool load_ssl_symbols(void);
void free_ssl_symbols(void);
bool ssl_loaded;
#endif

590
stream.c Normal file
View File

@@ -0,0 +1,590 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
// stream thread
#define _GNU_SOURCE
#include "squeezelite.h"
#include <fcntl.h>
#if USE_SSL
#include "openssl/ssl.h"
#include "openssl/err.h"
#endif
#if SUN
#include <signal.h>
#endif
static log_level loglevel;
static struct buffer buf;
struct buffer *streambuf = &buf;
#define LOCK mutex_lock(streambuf->mutex)
#define UNLOCK mutex_unlock(streambuf->mutex)
static sockfd fd;
struct streamstate stream;
#if USE_SSL
static SSL_CTX *SSLctx;
SSL *ssl;
#endif
#if !USE_SSL
#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()
#else
#define _last_error() ERROR_WOULDBLOCK
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);
}
#endif
static bool send_header(void) {
char *ptr = stream.header;
int len = stream.header_len;
unsigned try = 0;
ssize_t n;
while (len) {
n = _send(ssl, fd, ptr, len, MSG_NOSIGNAL);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK && try < 10) {
LOG_SDEBUG("retrying (%d) writing to socket", ++try);
usleep(1000);
continue;
}
LOG_INFO("failed writing to socket: %s", strerror(last_error()));
stream.disconnect = LOCAL_DISCONNECT;
stream.state = DISCONNECT;
wake_controller();
return false;
}
LOG_SDEBUG("wrote %d bytes to socket", n);
ptr += n;
len -= n;
}
LOG_SDEBUG("wrote header");
return true;
}
static bool running = true;
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 void *stream_thread() {
while (running) {
struct pollfd pollinfo;
size_t space;
LOCK;
space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
if (fd < 0 || !space || stream.state <= STREAMING_WAIT) {
UNLOCK;
usleep(100000);
continue;
}
if (stream.state == STREAMING_FILE) {
int n = read(fd, streambuf->writep, space);
if (n == 0) {
LOG_INFO("end of stream");
_disconnect(DISCONNECT, DISCONNECT_OK);
}
if (n > 0) {
_buf_inc_writep(streambuf, n);
stream.bytes += n;
LOG_SDEBUG("streambuf read %d bytes", n);
}
if (n < 0) {
LOG_WARN("error reading: %s", strerror(last_error()));
_disconnect(DISCONNECT, REMOTE_DISCONNECT);
}
UNLOCK;
continue;
} else {
pollinfo.fd = fd;
pollinfo.events = POLLIN;
if (stream.state == SEND_HEADERS) {
pollinfo.events |= POLLOUT;
}
}
UNLOCK;
if (_poll(ssl, &pollinfo, 100)) {
LOCK;
// check socket has not been closed while in poll
if (fd < 0) {
UNLOCK;
continue;
}
if ((pollinfo.revents & POLLOUT) && stream.state == SEND_HEADERS) {
if (send_header()) stream.state = RECV_HEADERS;
stream.header_len = 0;
UNLOCK;
continue;
}
if (pollinfo.revents & (POLLIN | POLLHUP)) {
// get response headers
if (stream.state == RECV_HEADERS) {
// read one byte at a time to catch end of header
char c;
static int endtok;
int n = _recv(ssl, fd, &c, 1, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading headers: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
*(stream.header + stream.header_len) = c;
stream.header_len++;
if (stream.header_len > MAX_HEADER - 1) {
LOG_ERROR("received headers too long: %u", stream.header_len);
_disconnect(DISCONNECT, LOCAL_DISCONNECT);
}
if (stream.header_len > 1 && (c == '\r' || c == '\n')) {
endtok++;
if (endtok == 4) {
*(stream.header + stream.header_len) = '\0';
LOG_INFO("headers: len: %d\n%s", stream.header_len, stream.header);
stream.state = stream.cont_wait ? STREAMING_WAIT : STREAMING_BUFFERING;
wake_controller();
}
} else {
endtok = 0;
}
UNLOCK;
continue;
}
// receive icy meta data
if (stream.meta_interval && stream.meta_next == 0) {
if (stream.meta_left == 0) {
// read meta length
u8_t c;
int n = _recv(ssl, fd, &c, 1, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
stream.meta_left = 16 * c;
stream.header_len = 0; // amount of received meta data
// MAX_HEADER must be more than meta max of 16 * 255
}
if (stream.meta_left) {
int n = _recv(ssl, fd, stream.header + stream.header_len, stream.meta_left, 0);
if (n <= 0) {
if (n < 0 && _last_error() == ERROR_WOULDBLOCK) {
UNLOCK;
continue;
}
LOG_INFO("error reading icy meta: %s", n ? strerror(last_error()) : "closed");
_disconnect(STOPPED, LOCAL_DISCONNECT);
UNLOCK;
continue;
}
stream.meta_left -= n;
stream.header_len += n;
}
if (stream.meta_left == 0) {
if (stream.header_len) {
*(stream.header + stream.header_len) = '\0';
LOG_INFO("icy meta: len: %u\n%s", stream.header_len, stream.header);
stream.meta_send = true;
wake_controller();
}
stream.meta_next = stream.meta_interval;
UNLOCK;
continue;
}
// stream body into streambuf
} else {
int n;
space = min(_buf_space(streambuf), _buf_cont_write(streambuf));
if (stream.meta_interval) {
space = min(space, stream.meta_next);
}
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) {
LOG_INFO("error reading: %s", strerror(last_error()));
_disconnect(DISCONNECT, REMOTE_DISCONNECT);
}
if (n > 0) {
_buf_inc_writep(streambuf, n);
stream.bytes += n;
if (stream.meta_interval) {
stream.meta_next -= n;
}
} else {
UNLOCK;
continue;
}
if (stream.state == STREAMING_BUFFERING && stream.bytes > stream.threshold) {
stream.state = STREAMING_HTTP;
wake_controller();
}
LOG_SDEBUG("streambuf read %d bytes", n);
}
}
UNLOCK;
} else {
LOG_SDEBUG("poll timeout");
}
}
#if USE_SSL
if (SSLctx) {
SSL_CTX_free(SSLctx);
}
#endif
return 0;
}
static thread_type thread;
void stream_init(log_level level, unsigned stream_buf_size) {
loglevel = level;
LOG_INFO("init stream");
LOG_DEBUG("streambuf size: %u", stream_buf_size);
buf_init(streambuf, stream_buf_size);
if (streambuf->buf == NULL) {
LOG_ERROR("unable to malloc buffer");
exit(0);
}
#if USE_SSL
#if !LINKALL
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
}
#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';
fd = -1;
#if LINUX || FREEBSD
touch_memory(streambuf->buf, streambuf->size);
#endif
#if LINUX || OSX || FREEBSD || POSIX
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
#if WIN
thread = CreateThread(NULL, STREAM_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&stream_thread, NULL, 0, NULL);
#endif
}
void stream_close(void) {
LOG_INFO("close stream");
LOCK;
running = false;
UNLOCK;
#if LINUX || OSX || FREEBSD || POSIX
pthread_join(thread, NULL);
#endif
free(stream.header);
buf_destroy(streambuf);
}
void stream_file(const char *header, size_t header_len, unsigned threshold) {
buf_flush(streambuf);
LOCK;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
LOG_INFO("opening local file: %s", stream.header);
#if WIN
fd = open(stream.header, O_RDONLY | O_BINARY);
#else
fd = open(stream.header, O_RDONLY);
#endif
stream.state = STREAMING_FILE;
if (fd < 0) {
LOG_INFO("can't open file: %s", stream.header);
stream.state = DISCONNECT;
}
wake_controller();
stream.cont_wait = false;
stream.meta_interval = 0;
stream.meta_next = 0;
stream.meta_left = 0;
stream.meta_send = false;
stream.sent_headers = false;
stream.bytes = 0;
stream.threshold = threshold;
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;
}
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");
LOCK;
stream.state = DISCONNECT;
stream.disconnect = UNREACHABLE;
UNLOCK;
return;
}
#if USE_SSL
if (ntohs(port) == 443) {
char *server = strcasestr(header, "Host:");
ssl = SSL_new(SSLctx);
SSL_set_fd(ssl, sock);
// add SNI
if (server) {
char *p, *servername = malloc(1024);
sscanf(server, "Host:%255[^:]s", servername);
for (p = servername; *p == ' '; p++);
SSL_set_tlsext_host_name(ssl, p);
free(servername);
}
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) continue;
}
LOG_WARN("unable to open SSL socket %d (%d)", status, err);
closesocket(sock);
SSL_free(ssl);
ssl = NULL;
LOCK;
stream.state = DISCONNECT;
stream.disconnect = UNREACHABLE;
UNLOCK;
return;
}
} else {
ssl = NULL;
}
#endif
buf_flush(streambuf);
LOCK;
fd = sock;
stream.state = SEND_HEADERS;
stream.cont_wait = cont_wait;
stream.meta_interval = 0;
stream.meta_next = 0;
stream.meta_left = 0;
stream.meta_send = false;
stream.header_len = header_len;
memcpy(stream.header, header, header_len);
*(stream.header+header_len) = '\0';
LOG_INFO("header: %s", stream.header);
stream.sent_headers = false;
stream.bytes = 0;
stream.threshold = threshold;
UNLOCK;
}
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;
disc = true;
}
stream.state = STOPPED;
UNLOCK;
return disc;
}

563
utils.c Normal file
View File

@@ -0,0 +1,563 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
#if LINUX || OSX || FREEBSD || POSIX
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#if FREEBSD
#include <ifaddrs.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#endif
#endif
#if SUN
#include <sys/socket.h>
#include <sys/sockio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#endif
#if WIN
#include <iphlpapi.h>
#if USE_SSL
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#endif
#endif
#if OSX
#include <net/if_dl.h>
#include <net/if_types.h>
#include <ifaddrs.h>
#include <netdb.h>
#endif
#include <fcntl.h>
// logging functions
const char *logtime(void) {
static char buf[100];
#if WIN
SYSTEMTIME lt;
GetLocalTime(&lt);
sprintf(buf, "[%02d:%02d:%02d.%03d]", lt.wHour, lt.wMinute, lt.wSecond, lt.wMilliseconds);
#else
struct timeval tv;
gettimeofday(&tv, NULL);
strftime(buf, sizeof(buf), "[%T.", localtime(&tv.tv_sec));
sprintf(buf+strlen(buf), "%06ld]", (long)tv.tv_usec);
#endif
return buf;
}
void logprint(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
fflush(stderr);
}
// cmdline parsing
char *next_param(char *src, char c) {
static char *str = NULL;
char *ptr, *ret;
if (src) str = src;
if (str && (ptr = strchr(str, c))) {
ret = str;
*ptr = '\0';
str = ptr + 1;
} else {
ret = str;
str = NULL;
}
return ret && ret[0] ? ret : NULL;
}
// clock
u32_t gettime_ms(void) {
#if WIN
return GetTickCount();
#else
#if LINUX || FREEBSD || POSIX
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
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}
// mac address
#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);
ifc.ifc_len = sizeof(ifs);
ifc.ifc_req = ifs;
if (ioctl(s, SIOCGIFCONF, &ifc) == 0) {
ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq));
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));
if (ioctl (s, SIOCGIFHWADDR, &ifreq) == 0) {
memcpy(mac, ifreq.ifr_hwaddr.sa_data, 6);
if (mac[0]+mac[1]+mac[2] != 0) {
break;
}
}
}
}
}
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
#if OSX || FREEBSD
void get_mac(u8_t mac[]) {
struct ifaddrs *addrs, *ptr;
const struct sockaddr_dl *dlAddr;
const unsigned char *base;
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
if (getifaddrs(&addrs) == 0) {
ptr = addrs;
while (ptr) {
if (ptr->ifa_addr->sa_family == AF_LINK && ((const struct sockaddr_dl *) ptr->ifa_addr)->sdl_type == IFT_ETHER) {
dlAddr = (const struct sockaddr_dl *)ptr->ifa_addr;
base = (const unsigned char*) &dlAddr->sdl_data[dlAddr->sdl_nlen];
memcpy(mac, base, min(dlAddr->sdl_alen, 6));
break;
}
ptr = ptr->ifa_next;
}
freeifaddrs(addrs);
}
}
#endif
#if WIN
#pragma comment(lib, "IPHLPAPI.lib")
void get_mac(u8_t mac[]) {
IP_ADAPTER_INFO AdapterInfo[16];
DWORD dwBufLen = sizeof(AdapterInfo);
DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen);
mac[0] = mac[1] = mac[2] = mac[3] = mac[4] = mac[5] = 0;
if (GetAdaptersInfo(AdapterInfo, &dwBufLen) == ERROR_SUCCESS) {
memcpy(mac, AdapterInfo[0].Address, 6);
}
}
#endif
void set_nonblock(sockfd s) {
#if WIN
u_long iMode = 1;
ioctlsocket(s, FIONBIO, &iMode);
#else
int flags = fcntl(s, F_GETFL,0);
fcntl(s, F_SETFL, flags | O_NONBLOCK);
#endif
}
// connect for socket already set to non blocking with timeout in seconds
int connect_timeout(sockfd sock, const struct sockaddr *addr, socklen_t addrlen, int timeout) {
fd_set w, e;
struct timeval tval;
if (connect(sock, addr, addrlen) < 0) {
#if !WIN
if (last_error() != EINPROGRESS) {
#else
if (last_error() != WSAEWOULDBLOCK) {
#endif
return -1;
}
}
FD_ZERO(&w);
FD_SET(sock, &w);
e = w;
tval.tv_sec = timeout;
tval.tv_usec = 0;
// only return 0 if w set and sock error is zero, otherwise return error code
if (select(sock + 1, NULL, &w, &e, timeout ? &tval : NULL) == 1 && FD_ISSET(sock, &w)) {
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *)&error, &len);
return error;
}
return -1;
}
void server_addr(char *server, in_addr_t *ip_ptr, unsigned *port_ptr) {
struct addrinfo *res = NULL;
struct addrinfo hints;
const char *port = NULL;
if (strtok(server, ":")) {
port = strtok(NULL, ":");
if (port) {
*port_ptr = atoi(port);
}
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
getaddrinfo(server, NULL, &hints, &res);
if (res && res->ai_addr) {
*ip_ptr = ((struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr;
}
if (res) {
freeaddrinfo(res);
}
}
void set_readwake_handles(event_handle handles[], sockfd s, event_event e) {
#if WINEVENT
handles[0] = WSACreateEvent();
handles[1] = e;
WSAEventSelect(s, handles[0], FD_READ | FD_CLOSE);
#elif SELFPIPE || LOOPBACK
handles[0].fd = s;
handles[1].fd = e.fds[0];
handles[0].events = POLLIN;
handles[1].events = POLLIN;
#else
handles[0].fd = s;
handles[1].fd = e;
handles[0].events = POLLIN;
handles[1].events = POLLIN;
#endif
}
event_type wait_readwake(event_handle handles[], int timeout) {
#if WINEVENT
int wait = WSAWaitForMultipleEvents(2, handles, FALSE, timeout, FALSE);
if (wait == WSA_WAIT_EVENT_0) {
WSAResetEvent(handles[0]);
return EVENT_READ;
} else if (wait == WSA_WAIT_EVENT_0 + 1) {
return EVENT_WAKE;
} else {
return EVENT_TIMEOUT;
}
#else
if (poll(handles, 2, timeout) > 0) {
if (handles[0].revents) {
return EVENT_READ;
}
if (handles[1].revents) {
wake_clear(handles[1].fd);
return EVENT_WAKE;
}
}
return EVENT_TIMEOUT;
#endif
}
#if LOOPBACK
void _wake_create(event_event* e) {
struct sockaddr_in addr;
short port;
socklen_t len;
e->mfds = e->fds[0] = e->fds[1] = -1;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// create sending socket - will wait for connections
addr.sin_port = 0;
e->mfds = socket(AF_INET, SOCK_STREAM, 0);
bind(e->mfds, (struct sockaddr*) &addr, sizeof(addr));
len = sizeof(struct sockaddr);
// get assigned port & listen
getsockname(e->mfds, (struct sockaddr *) &addr, &len);
port = addr.sin_port;
listen(e->mfds, 1);
// create receiving socket
addr.sin_port = 0;
e->fds[0] = socket(AF_INET, SOCK_STREAM, 0);
bind(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
// connect to sender (we listen so it can be blocking)
addr.sin_port = port;
connect(e->fds[0], (struct sockaddr*) &addr, sizeof(addr));
// this one will work or fail, but not block
len = sizeof(struct sockaddr);
e->fds[1] = accept(e->mfds, (struct sockaddr*) &addr, &len);
}
#endif
// pack/unpack to network byte order
void packN(u32_t *dest, u32_t val) {
u8_t *ptr = (u8_t *)dest;
*(ptr) = (val >> 24) & 0xFF; *(ptr+1) = (val >> 16) & 0xFF; *(ptr+2) = (val >> 8) & 0xFF; *(ptr+3) = val & 0xFF;
}
void packn(u16_t *dest, u16_t val) {
u8_t *ptr = (u8_t *)dest;
*(ptr) = (val >> 8) & 0xFF; *(ptr+1) = val & 0xFF;
}
u32_t unpackN(u32_t *src) {
u8_t *ptr = (u8_t *)src;
return *(ptr) << 24 | *(ptr+1) << 16 | *(ptr+2) << 8 | *(ptr+3);
}
u16_t unpackn(u16_t *src) {
u8_t *ptr = (u8_t *)src;
return *(ptr) << 8 | *(ptr+1);
}
#if OSX
void set_nosigpipe(sockfd s) {
int set = 1;
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
}
#endif
#if WIN
void winsock_init(void) {
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
int WSerr = WSAStartup(wVersionRequested, &wsaData);
if (WSerr != 0) {
LOG_ERROR("Bad winsock version");
exit(1);
}
}
void winsock_close(void) {
WSACleanup();
}
void *dlopen(const char *filename, int flag) {
SetLastError(0);
return LoadLibrary((LPCTSTR)filename);
}
void *dlsym(void *handle, const char *symbol) {
SetLastError(0);
return (void *)GetProcAddress(handle, symbol);
}
char *dlerror(void) {
static char ret[32];
int last = GetLastError();
if (last) {
sprintf(ret, "code: %i", last);
SetLastError(0);
return ret;
}
return NULL;
}
int poll(struct pollfd *fds, unsigned long numfds, int timeout) {
fd_set r, w;
struct timeval tv;
int ret, i, max_fds = fds[0].fd;
FD_ZERO(&r);
FD_ZERO(&w);
for (i = 0; i < numfds; i++) {
if (fds[i].events & POLLIN) FD_SET(fds[i].fd, &r);
if (fds[i].events & POLLOUT) FD_SET(fds[i].fd, &w);
if (max_fds < fds[i].fd) max_fds = fds[i].fd;
}
tv.tv_sec = timeout / 1000;
tv.tv_usec = 1000 * (timeout % 1000);
ret = select(max_fds + 1, &r, &w, NULL, &tv);
if (ret < 0) return ret;
for (i = 0; i < numfds; i++) {
fds[i].revents = 0;
if (FD_ISSET(fds[i].fd, &r)) fds[i].revents |= POLLIN;
if (FD_ISSET(fds[i].fd, &w)) fds[i].revents |= POLLOUT;
}
return ret;
}
#endif
#if LINUX || FREEBSD
void touch_memory(u8_t *buf, size_t size) {
u8_t *ptr;
for (ptr = buf; ptr < buf + size; ptr += sysconf(_SC_PAGESIZE)) {
*ptr = 0;
}
}
#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