mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2026-01-27 12:50:49 +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