mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-06 11:36:59 +03:00
initial commit
This commit is contained in:
5
CMakeLists.txt
Normal file
5
CMakeLists.txt
Normal 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
68
Kconfig.projbuild
Normal 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
115
buffer.c
Normal 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
8
component.mk
Normal 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
296
decode.c
Normal 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
12
esp32.c
Normal 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
837
main.c
Normal 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
450
output.c
Normal 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
154
output_dac.c
Normal 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
365
output_pack.c
Normal 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
438
pcm.c
Normal 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
148
scan.c
Normal 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
976
slimproto.c
Normal 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
185
slimproto.h
Normal 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
786
squeezelite.h
Normal 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
590
stream.c
Normal 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
563
utils.c
Normal 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(<);
|
||||
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
|
||||
Reference in New Issue
Block a user