diff --git a/ChangeLog.txt b/ChangeLog.txt index d30adf4..7b6571b 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -103,3 +103,35 @@ Minor changes - improve synchronisation feedback accuracy + +Version 1.6.5 21/11/14 +====================== + +Minor changes +- fix problem opening ALSA device if 44100 is not supported +- trap setting of hw player mac address + +Version 1.7 1/1/15 +================== + +Minor changes +- allow player modelname to be set at compile or run time +- workaround alsa drivers reporting very large number of available frames +- fix clicks on localfile playback of AIFF files +- add -P option to store process id in a file +- improve error messages for command line parsing + +Version 1.7.1 10/1/15 +===================== + +Minor changes +- fix crash which could occur when resampling + +Version 1.8 1/2/15 +================== + +Features +- support for closing output device when idle with -C option +- support for basic IR input using LIRC on Linux +- support for volume adjustment or unmuting of alsa mixer +- support for inverting output polarity via LMS setting (requires recent 7.9 server) diff --git a/LICENSE.txt b/LICENSE.txt index 8bb3cc5..94af310 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ Squeezelite - lightweight headless squeezebox emulator -(c) Adrian Smith 2012-2014, triode1@btinternet.com +(c) Adrian Smith 2012-2015, triode1@btinternet.com Released under GPLv3 license: @@ -17,3 +17,33 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . + +--------------------------------------------------------------------- + +If built with DSD support, this software also includes code subject to the following license: + +Copyright 2009, 2011 Sebastian Gesemann. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY SEBASTIAN GESEMANN ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEBASTIAN GESEMANN OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Sebastian Gesemann. diff --git a/Makefile b/Makefile index 65e224b..dca2abd 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ OPT_LINKALL = -DLINKALL OPT_RESAMPLE= -DRESAMPLE OPT_VIS = -DVISEXPORT +OPT_IR = -DIR SOURCES = \ main.c slimproto.c buffer.c stream.c utils.c \ @@ -19,12 +20,14 @@ SOURCES_FF = ffmpeg.c SOURCES_RESAMPLE = process.c resample.c SOURCES_VIS = output_vis.c +SOURCES_IR = ir.c LINK_LINUX = -ldl LINKALL = -lFLAC -lmad -lvorbisfile -lfaad -lmpg123 LINKALL_FF = -lavcodec -lavformat -lavutil LINKALL_RESAMPLE = -lsoxr +LINKALL_IR = -llirc_client DEPS = squeezelite.h slimproto.h @@ -43,6 +46,9 @@ ifneq (,$(findstring $(OPT_VIS), $(CFLAGS))) SOURCES += $(SOURCES_VIS) endif +ifneq (,$(findstring $(OPT_IR), $(CFLAGS))) + SOURCES += $(SOURCES_IR) +endif # add optional link options ifneq (,$(findstring $(OPT_LINKALL), $(CFLAGS))) @@ -52,6 +58,9 @@ endif ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS))) LDFLAGS += $(LINKALL_RESAMPLE) +endif +ifneq (,$(findstring $(OPT_IR), $(CFLAGS))) + LDFLAGS += $(LINKALL_IR) endif else # if not LINKALL and linux add LINK_LINUX diff --git a/buffer.c b/buffer.c index 814b012..fd83a76 100644 --- a/buffer.c +++ b/buffer.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/decode.c b/decode.c index 53bae8e..add5fcb 100644 --- a/decode.c +++ b/decode.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -240,6 +240,8 @@ codec->open(sample_size, sample_rate, channels, endianness); + decode.state = DECODE_READY; + UNLOCK_D; return; } diff --git a/dop.c b/dop.c index 580dd38..b498202 100644 --- a/dop.c +++ b/dop.c @@ -37,7 +37,7 @@ u32_t next = 0; while (frames--) { - if (((*lptr & 0x00FF0000) == 0x00050000 && (*rptr & 0x00FF0000) == 0x00050000) || + if (((*lptr & 0x00FF0000) == 0x00050000 && (*rptr & 0x00FF0000) == 0x00050000) || ((*lptr & 0x00FF0000) == 0x00FA0000 && (*rptr & 0x00FF0000) == 0x00FA0000)) { if (*lptr >> 24 == next) { matched++; @@ -58,17 +58,28 @@ return false; } -// update the dop marker for frames in the output buffer +// update the dop marker and potentially invert polarity for frames in the output buffer // performaned on all output including silence to maintain marker phase consitency -void update_dop_marker(u32_t *ptr, frames_t frames) { +void update_dop(u32_t *ptr, frames_t frames, bool invert) { static u32_t marker = 0x05; - while (frames--) { - u32_t scaled_marker = marker << 24; - *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; - ++ptr; - *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; - ++ptr; - marker = ( 0x05 + 0xFA ) - marker; + if (!invert) { + while (frames--) { + u32_t scaled_marker = marker << 24; + *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; + ++ptr; + *ptr = (*ptr & 0x00FFFFFF) | scaled_marker; + ++ptr; + marker = ( 0x05 + 0xFA ) - marker; + } + } else { + while (frames--) { + u32_t scaled_marker = marker << 24; + *ptr = ((~(*ptr)) & 0x00FFFFFF) | scaled_marker; + ++ptr; + *ptr = ((~(*ptr)) & 0x00FFFFFF) | scaled_marker; + ++ptr; + marker = ( 0x05 + 0xFA ) - marker; + } } } diff --git a/dsd.c b/dsd.c index 4b1662c..32d4795 100644 --- a/dsd.c +++ b/dsd.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/faad.c b/faad.c index aa297dc..96d33ca 100644 --- a/faad.c +++ b/faad.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -228,7 +228,7 @@ // found media data, advance to start of first chunk and return if (!strcmp(type, "mdat")) { - _buf_inc_readp(streambuf, 8); + _buf_inc_readp(streambuf, 8); a->pos += 8; bytes -= 8; if (play) { diff --git a/ffmpeg.c b/ffmpeg.c index b96e169..279f31d 100644 --- a/ffmpeg.c +++ b/ffmpeg.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -610,8 +610,8 @@ ff->avcodec_alloc_frame = dlsym(handle_codec, "avcodec_alloc_frame"); ff->avcodec_free_frame = dlsym(handle_codec, "avcodec_free_frame"); ff->avcodec_decode_audio4 = dlsym(handle_codec, "avcodec_decode_audio4"); - ff->av_init_packet = dlsym(handle_codec, "av_init_packet"); - ff->av_free_packet = dlsym(handle_codec, "av_free_packet"); + ff->av_init_packet = dlsym(handle_codec, "av_init_packet"); + ff->av_free_packet = dlsym(handle_codec, "av_free_packet"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); @@ -620,14 +620,14 @@ LOG_INFO("loaded "LIBAVCODEC" (%u.%u.%u)", LIBAVCODEC_VERSION_MAJOR, ff->avcodec_version() >> 16, (ff->avcodec_version() >> 8) & 0xff, ff->avcodec_version() & 0xff); - ff->avformat_version = dlsym(handle_format, "avformat_version"); - ff->avformat_alloc_context = dlsym(handle_format, "avformat_alloc_context"); - ff->avformat_free_context = dlsym(handle_format, "avformat_free_context"); - ff->avformat_open_input = dlsym(handle_format, "avformat_open_input"); - ff->avformat_find_stream_info = dlsym(handle_format, "avformat_find_stream_info"); - ff->avio_alloc_context = dlsym(handle_format, "avio_alloc_context"); - ff->av_read_frame = dlsym(handle_format, "av_read_frame"); - ff->av_find_input_format= dlsym(handle_format, "av_find_input_format"); + ff->avformat_version = dlsym(handle_format, "avformat_version"); + ff->avformat_alloc_context = dlsym(handle_format, "avformat_alloc_context"); + ff->avformat_free_context = dlsym(handle_format, "avformat_free_context"); + ff->avformat_open_input = dlsym(handle_format, "avformat_open_input"); + ff->avformat_find_stream_info = dlsym(handle_format, "avformat_find_stream_info"); + ff->avio_alloc_context = dlsym(handle_format, "avio_alloc_context"); + ff->av_read_frame = dlsym(handle_format, "av_read_frame"); + ff->av_find_input_format= dlsym(handle_format, "av_find_input_format"); ff->av_register_all = dlsym(handle_format, "av_register_all"); if ((err = dlerror()) != NULL) { diff --git a/ir.c b/ir.c new file mode 100644 index 0000000..2355ee4 --- /dev/null +++ b/ir.c @@ -0,0 +1,248 @@ +/* + * Squeezelite - lightweight headless squeezebox emulator + * + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 . + * + */ + +// ir thread - linux only + +#include "squeezelite.h" + +#if IR + +#include + +#define LIRC_CLIENT_ID "squeezelite" + +static log_level loglevel; + +struct irstate ir; + +static struct lirc_config *config = NULL; +static sockfd fd = -1; + +static thread_type thread; + +#define LOCK_I mutex_lock(ir.mutex) +#define UNLOCK_I mutex_unlock(ir.mutex) + +#if !LINKALL +struct lirc { + // LIRC symbols to be dynamically loaded + int (* lirc_init)(char *prog, int verbose); + int (* lirc_deinit)(void); + int (* lirc_readconfig)(char *file, struct lirc_config **config, int (check) (char *s)); + void (* lirc_freeconfig)(struct lirc_config *config); + int (* lirc_nextcode)(char **code); + int (* lirc_code2char)(struct lirc_config *config, char *code, char **string); +}; + +static struct lirc *i; +#endif + +#if LINKALL +#define LIRC(h, fn, ...) (lirc_ ## fn)(__VA_ARGS__) +#else +#define LIRC(h, fn, ...) (h)->lirc_##fn(__VA_ARGS__) +#endif + +// cmds based on entires in Slim_Device_Remote.ir +// these may appear as config entries in .lircrc files +static struct { + char *cmd; + u32_t code; +} cmdmap[] = { + { "voldown", 0x768900ff }, + { "volup", 0x7689807f }, + { "rew", 0x7689c03f }, + { "fwd", 0x7689a05f }, + { "pause", 0x768920df }, + { "play", 0x768910ef }, + { "power", 0x768940bf }, + { "muting", 0x7689c43b }, + { "power_on", 0x76898f70 }, + { "power_off",0x76898778 }, + { NULL, 0 }, +}; + +// selected lirc namespace button names as defaults - some support repeat +static struct { + char *lirc; + u32_t code; + bool repeat; +} keymap[] = { + { "KEY_VOLUMEDOWN", 0x768900ff, true }, + { "KEY_VOLUMEUP", 0x7689807f, true }, + { "KEY_PREVIOUS", 0x7689c03f, false }, + { "KEY_REWIND", 0x7689c03f, false }, + { "KEY_NEXT", 0x7689a05f, false }, + { "KEY_FORWARD", 0x7689a05f, false }, + { "KEY_PAUSE", 0x768920df, true }, + { "KEY_PLAY", 0x768910ef, false }, + { "KEY_POWER", 0x768940bf, false }, + { "KEY_MUTE", 0x7689c43b, false }, + { NULL, 0 , false }, +}; + +static u32_t ir_cmd_map(const char *c) { + int i; + for (i = 0; cmdmap[i].cmd; i++) { + if (!strcmp(c, cmdmap[i].cmd)) { + return cmdmap[i].code; + } + } + return 0; +} + +static u32_t ir_key_map(const char *c, const char *r) { + int i; + for (i = 0; keymap[i].lirc; i++) { + if (!strcmp(c, keymap[i].lirc)) { + if (keymap[i].repeat || !strcmp(r, "00")) { + return keymap[i].code; + } + LOG_DEBUG("repeat suppressed"); + break; + } + } + return 0; +} + +static void *ir_thread() { + char *code; + + while (fd > 0 && LIRC(i, nextcode, &code) == 0) { + + u32_t now = gettime_ms(); + u32_t ir_code = 0; + + if (code == NULL) continue; + + if (config) { + // allow lirc_client to decode then lookup cmd in our table + // we can only send one IR event to slimproto so break after first one + char *c; + while (LIRC(i, code2char, config, code, &c) == 0 && c != NULL) { + ir_code = ir_cmd_map(c); + if (ir_code) { + LOG_DEBUG("ir cmd: %s -> %x", c, ir_code); + } + } + } + + if (!ir_code) { + // try to match on lirc button name if it is from the standard namespace + // this allows use of non slim remotes without a specific entry in .lircrc + char *b, *r; + strtok(code, " \n"); // discard + r = strtok(NULL, " \n"); // repeat count + b = strtok(NULL, " \n"); // key name + if (r && b) { + ir_code = ir_key_map(b, r); + LOG_DEBUG("ir lirc: %s [%s] -> %x", b, r, ir_code); + } + } + + if (ir_code) { + LOCK_I; + if (ir.code) { + LOG_DEBUG("code dropped"); + } + ir.code = ir_code; + ir.ts = now; + UNLOCK_I; + wake_controller(); + } + + free(code); + } + + return 0; +} + +#if !LINKALL +static bool load_lirc() { + void *handle = dlopen(LIBLIRC, RTLD_NOW); + char *err; + + if (!handle) { + LOG_INFO("dlerror: %s", dlerror()); + return false; + } + + i->lirc_init = dlsym(handle, "lirc_init"); + i->lirc_deinit = dlsym(handle, "lirc_deinit"); + i->lirc_readconfig = dlsym(handle, "lirc_readconfig"); + i->lirc_freeconfig = dlsym(handle, "lirc_freeconfig"); + i->lirc_nextcode = dlsym(handle, "lirc_nextcode"); + i->lirc_code2char = dlsym(handle, "lirc_code2char"); + + if ((err = dlerror()) != NULL) { + LOG_INFO("dlerror: %s", err); + return false; + } + + LOG_INFO("loaded "LIBLIRC); + return true; +} +#endif + +void ir_init(log_level level, char *lircrc) { + loglevel = level; + +#if !LINKALL + i = malloc(sizeof(struct lirc)); + if (!i || !load_lirc()) { + return; + } +#endif + + fd = LIRC(i, init, LIRC_CLIENT_ID, 0); + + if (fd > 0) { + if (LIRC(i, readconfig,lircrc, &config, NULL) != 0) { + LOG_WARN("error reading config: %s", lircrc); + } + + mutex_create(ir.mutex); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + IR_THREAD_STACK_SIZE); + pthread_create(&thread, &attr, ir_thread, NULL); + pthread_attr_destroy(&attr); + + } else { + LOG_WARN("failed to connect to lircd - ir processing disabled"); + } +} + +void ir_close(void) { + if (fd > 0) { + fd = -1; + if (config) { + LIRC(i, freeconfig, config); + } + LIRC(i, deinit); + + pthread_cancel(thread); + pthread_join(thread, NULL); + mutex_destroy(ir.mutex); + } +} + +#endif //#if IR diff --git a/mad.c b/mad.c index 1605bf9..30e2498 100644 --- a/mad.c +++ b/mad.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/main.c b/main.c index 96c9154..5f3d48c 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -22,7 +22,7 @@ #include -#define TITLE "Squeezelite " VERSION ", Copyright 2012-2014 Adrian Smith." +#define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith." #define CODECS_BASE "flac,pcm,mp3,ogg,aac" #if FFMPEG @@ -58,14 +58,26 @@ " -a \t\tSpecify sample format (16|24|32) of output file when using -o - to output samples to stdout (interleaved little endian only)\n" " -b :\tSpecify internal Stream and Output buffer sizes in Kbytes\n" " -c ,\tRestrict codecs to those specified, otherwise load all available codecs; known codecs: " CODECS "\n" + " -C \t\tClose output device when idle after timeout seconds, default is to keep it open while player is 'on'\n" +#if !IR " -d =\tSet logging level, logs: all|slimproto|stream|decode|output, level: info|debug|sdebug\n" +#else + " -d =\tSet logging level, logs: all|slimproto|stream|decode|output|ir, level: info|debug|sdebug\n" +#endif " -e ,\tExplicitly exclude native support of one or more codecs; known codecs: " CODECS "\n" " -f \t\tWrite debug to logfile\n" +#if IR + " -i []\tEnable lirc remote control support (lirc config file ~/.lircrc used if filename not specified)\n" +#endif " -m \t\tSet mac address, format: ab:cd:ef:12:34:56\n" + " -M \tSet the squeezelite player model name sent to the server (default: " MODEL_NAME_STRING ")\n" " -n \t\tSet the player name\n" " -N \t\tStore player name in filename to allow server defined name changes to be shared between servers (not supported with -n)\n" #if ALSA " -p \t\tSet real time priority of output thread (1-99)\n" +#endif +#if LINUX || FREEBSD + " -P \t\tStore the process id (PID) in filename\n" #endif " -r [:]\tSample rates supported, allows output to be off when squeezelite is started; rates = |-|,,; delay = optional delay switching rates in ms\n" #if RESAMPLE @@ -84,10 +96,16 @@ #if VISEXPORT " -v \t\t\tVisualiser support\n" #endif +# if ALSA + " -L \t\t\tList volume controls for output device\n" + " -U \t\tUnmute ALSA control and set to full volume (not supported with -V)\n" + " -V \t\tUse ALSA control for volume adjustment, otherwise use software volume adjustment\n" +#endif #if LINUX || FREEBSD " -z \t\t\tDaemonize\n" #endif " -t \t\t\tLicense terms\n" + " -? \t\t\tDisplay this help text\n" "\n" "Build options:" #if LINUX @@ -129,6 +147,9 @@ #endif #if VISEXPORT " VISEXPORT" +#endif +#if IR + " IR" #endif #if DSD " DSD" @@ -173,6 +194,7 @@ char *exclude_codecs = ""; char *name = NULL; char *namefile = NULL; + char *modelname = NULL; char *logfile = NULL; u8_t mac[6]; unsigned stream_buf_size = STREAMBUF_SIZE; @@ -181,11 +203,16 @@ unsigned rate_delay = 0; char *resample = NULL; char *output_params = NULL; + unsigned idle = 0; #if LINUX || FREEBSD bool daemonize = false; + char *pidfile = NULL; + FILE *pidfp = NULL; #endif #if ALSA unsigned rt_priority = OUTPUT_RT_PRIORITY; + char *output_mixer = NULL; + bool output_mixer_unmute = false; #endif #if DSD bool dop = false; @@ -193,18 +220,24 @@ #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 char *optarg = NULL; int optind = 1; int i; -#define MAXCMDLINE 256 +#define MAXCMDLINE 512 char cmdline[MAXCMDLINE] = ""; get_mac(mac); @@ -216,10 +249,17 @@ while (optind < argc && strlen(argv[optind]) >= 2 && argv[optind][0] == '-') { char *opt = argv[optind] + 1; - if (strstr("oabcdefmnNprs", opt) && optind < argc - 1) { + if (strstr("oabcCdefmMnNpPrs" +#if ALSA + "UV" +#endif + , opt) && optind < argc - 1) { optarg = argv[optind + 1]; optind += 2; - } else if (strstr("ltz" + } else if (strstr("ltz?" +#if ALSA + "L" +#endif #if RESAMPLE "uR" #endif @@ -229,18 +269,23 @@ #if VISEXPORT "v" #endif +#if IR + "i" +#endif + , opt)) { optarg = NULL; optind += 1; } else { + fprintf(stderr, "\nOption error: -%s\n\n", opt); usage(argv[0]); - exit(0); + exit(1); } switch (opt[0]) { - case 'o': - output_device = optarg; - break; + case 'o': + output_device = optarg; + break; case 'a': output_params = optarg; break; @@ -255,10 +300,15 @@ 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': + case 'd': { char *l = strtok(optarg, "="); char *v = strtok(NULL, "="); @@ -271,12 +321,16 @@ 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(0); + exit(1); } } - break; + break; case 'f': logfile = optarg; break; @@ -284,12 +338,19 @@ { int byte = 0; char *tmp; - char *t = strtok(optarg, ":"); - while (t && byte < 6) { - mac[byte++] = (u8_t)strtoul(t, &tmp, 16); - t = strtok(NULL, ":"); + 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': { @@ -349,15 +410,27 @@ 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(0); - } + exit(1); + } + break; +#endif +#if LINUX || FREEBSD + case 'P': + pidfile = optarg; break; #endif case 'l': list_devices(); exit(0); break; +#if ALSA + case 'L': + list_mixers(output_device); + exit(0); + break; +#endif #if RESAMPLE case 'u': case 'R': @@ -381,6 +454,26 @@ visexport = true; break; #endif +#if ALSA + 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 LINUX || FREEBSD case 'z': daemonize = true; @@ -389,15 +482,20 @@ case 't': license(); exit(0); - default: - break; - } - } + 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(0); + exit(1); } signal(SIGINT, sighandler); @@ -434,11 +532,24 @@ } #if LINUX || FREEBSD + 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", getpid()); + fclose(pidfp); + } #endif #if WIN @@ -451,10 +562,11 @@ 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); + output_init_alsa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, rt_priority, idle, output_mixer, + output_mixer_unmute); #endif #if PORTAUDIO - output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay); + output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); #endif } @@ -476,13 +588,19 @@ } #endif +#if IR + if (lircrc) { + ir_init(log_ir, lircrc); + } +#endif + if (name && namefile) { - printf("-n and -N option should not be used at same time\n"); - exit(0); - } - - slimproto(log_slimproto, server, mac, 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); + decode_close(); stream_close(); @@ -497,9 +615,20 @@ #endif } +#if IR + ir_close(); +#endif + #if WIN winsock_close(); #endif +#if LINUX || FREEBSD + if (pidfile) { + unlink(pidfile); + free(pidfile); + } +#endif + exit(0); } diff --git a/mpg.c b/mpg.c index 0657b92..f3074f2 100644 --- a/mpg.c +++ b/mpg.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/output.c b/output.c index ee54c8c..6176c8b 100644 --- a/output.c +++ b/output.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,7 +49,9 @@ 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; @@ -220,6 +222,7 @@ 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 @@ -235,6 +238,7 @@ } 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"); @@ -333,7 +337,7 @@ } } -void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]) { +void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle) { unsigned i; loglevel = level; @@ -363,10 +367,14 @@ dop_silence_frames((u32_t *)silencebuf_dop, MAX_SILENCE_FRAMES); ) - output.state = OUTPUT_STOPPED; + 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; if (!rates[0]) { if (!test_open(output.device, output.supported_rates)) { @@ -387,7 +395,7 @@ } } if (!output.default_sample_rate) { - output.default_sample_rate = rates[0]; + output.default_sample_rate = output.supported_rates[0]; } output.current_sample_rate = output.default_sample_rate; diff --git a/output_alsa.c b/output_alsa.c index 3b0139a..aa01560 100644 --- a/output_alsa.c +++ b/output_alsa.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -27,6 +27,7 @@ #include #include #include +#include #define MAX_DEVICE_LEN 128 @@ -49,6 +50,8 @@ bool mmap; bool reopen; u8_t *write_buf; + const char *volume_mixer_name; + int volume_mixer_index; } alsa; static snd_pcm_t *pcmp = NULL; @@ -93,6 +96,173 @@ printf("\n"); } +void list_mixers(const char *output_device) { + int err; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *elem; + snd_mixer_selem_id_alloca(&sid); + + LOG_INFO("listing mixers for: %s", output_device); + + if ((err = snd_mixer_open(&handle, 0)) < 0) { + LOG_ERROR("open error: %s", snd_strerror(err)); + return; + } + if ((err = snd_mixer_attach(handle, output_device)) < 0) { + LOG_ERROR("attach error: %s", snd_strerror(err)); + snd_mixer_close(handle); + return; + } + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + LOG_ERROR("register error: %s", snd_strerror(err)); + snd_mixer_close(handle); + return; + } + if ((err = snd_mixer_load(handle)) < 0) { + LOG_ERROR("load error: %s", snd_strerror(err)); + snd_mixer_close(handle); + return; + } + + printf("Volume controls for %s\n", output_device); + for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) { + if (snd_mixer_selem_has_playback_volume(elem)) { + snd_mixer_selem_get_id(elem, sid); + printf(" %s", snd_mixer_selem_id_get_name(sid)); + if (snd_mixer_selem_id_get_index(sid)) { + printf(",%d", snd_mixer_selem_id_get_index(sid)); + } + printf("\n"); + } + } + printf("\n"); + + snd_mixer_close(handle); +} + +#define MINVOL_DB 72 // LMS volume map for SqueezePlay sends values in range ~ -72..0 dB + +static void set_mixer(const char *device, const char *mixer, int mixer_index, bool setmax, float ldB, float rdB) { + int err; + long nleft, nright; + long min, max; + snd_mixer_t *handle; + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t* elem; + + if ((err = snd_mixer_open(&handle, 0)) < 0) { + LOG_ERROR("open error: %s", snd_strerror(err)); + return; + } + if ((err = snd_mixer_attach(handle, device)) < 0) { + LOG_ERROR("attach error: %s", snd_strerror(err)); + snd_mixer_close(handle); + return; + } + if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) { + LOG_ERROR("register error: %s", snd_strerror(err)); + snd_mixer_close(handle); + return; + } + if ((err = snd_mixer_load(handle)) < 0) { + LOG_ERROR("load error: %s", snd_strerror(err)); + snd_mixer_close(handle); + return; + } + + snd_mixer_selem_id_alloca(&sid); + + snd_mixer_selem_id_set_index(sid, mixer_index); + snd_mixer_selem_id_set_name(sid, mixer); + + if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) { + LOG_ERROR("error find selem %s", mixer); + snd_mixer_close(handle); + return; + } + + if (snd_mixer_selem_has_playback_switch(elem)) { + snd_mixer_selem_set_playback_switch_all(elem, 1); // unmute + } + + err = snd_mixer_selem_get_playback_dB_range(elem, &min, &max); + + if (err < 0 || max - min < 1000) { + // unable to get db range or range is less than 10dB - ignore and set using raw values + if ((err = snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) < 0) { + LOG_ERROR("unable to get volume raw range"); + } else { + long lraw, rraw; + if (setmax) { + lraw = rraw = max; + } else { + lraw = ((ldB > -MINVOL_DB ? MINVOL_DB + floor(ldB) : 0) / MINVOL_DB * (max-min)) + min; + rraw = ((rdB > -MINVOL_DB ? MINVOL_DB + floor(rdB) : 0) / MINVOL_DB * (max-min)) + min; + } + LOG_DEBUG("setting vol raw [%ld..%ld]", min, max); + if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, lraw)) < 0) { + LOG_ERROR("error setting left volume: %s", snd_strerror(err)); + } + if ((err = snd_mixer_selem_set_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, rraw)) < 0) { + LOG_ERROR("error setting right volume: %s", snd_strerror(err)); + } + } + } else { + // set db directly + LOG_DEBUG("setting vol dB [%ld..%ld]", min, max); + if (setmax) { + // set to 0dB if available as this should be max volume for music recored at max pcm values + if (max >= 0 && min <= 0) { + ldB = rdB = 0; + } else { + ldB = rdB = max; + } + } + if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_LEFT, 100 * ldB, 1)) < 0) { + LOG_ERROR("error setting left volume: %s", snd_strerror(err)); + } + if ((err = snd_mixer_selem_set_playback_dB(elem, SND_MIXER_SCHN_FRONT_RIGHT, 100 * rdB, 1)) < 0) { + LOG_ERROR("error setting right volume: %s", snd_strerror(err)); + } + } + + if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &nleft)) < 0) { + LOG_ERROR("error getting left vol: %s", snd_strerror(err)); + } + if ((err = snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_RIGHT, &nright)) < 0) { + LOG_ERROR("error getting right vol: %s", snd_strerror(err)); + } + + LOG_DEBUG("%s left: %3.1fdB -> %ld right: %3.1fdB -> %ld", mixer, ldB, nleft, rdB, nright); + + snd_mixer_close(handle); +} + +void set_volume(unsigned left, unsigned right) { + float ldB, rdB; + + if (!alsa.volume_mixer_name) { + LOG_DEBUG("setting internal gain left: %u right: %u", left, right); + LOCK; + output.gainL = left; + output.gainR = right; + UNLOCK; + return; + } else { + LOCK; + output.gainL = FIXED_ONE; + output.gainR = FIXED_ONE; + UNLOCK; + } + + // convert 16.16 fixed point to dB + ldB = 20 * log10( left / 65536.0F ); + rdB = 20 * log10( right / 65536.0F ); + + set_mixer(output.device, alsa.volume_mixer_name, alsa.volume_mixer_index, false, ldB, rdB); +} + static void *alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { va_list args; if ((loglevel >= lINFO && err == 0) || loglevel >= lDEBUG) { @@ -181,6 +351,8 @@ LOG_ERROR("device name too long: %s", device); return -1; } + + LOG_INFO("opening device at: %u", sample_rate); bool retry; do { @@ -380,7 +552,7 @@ if (silence) { inputptr = (s32_t *) silencebuf_dop; } - update_dop_marker((u32_t *) inputptr, out_frames); + update_dop((u32_t *) inputptr, out_frames, output.invert && !silence); } ) @@ -438,7 +610,8 @@ static void *output_thread(void *arg) { bool start = true; - bool output_off = false, probe_device = (arg != NULL); + bool output_off = (output.state == OUTPUT_OFF); + bool probe_device = (arg != NULL); int err; while (running) { @@ -548,8 +721,11 @@ continue; } - // restrict avail in writei mode as write_buf is restricted to period_size - if (!alsa.mmap) { + // restrict avail to within sensible limits as alsa drivers can return erroneous large values + // in writei mode restrict to period_size due to size of write_buf + if (alsa.mmap) { + avail = min(avail, alsa.buffer_size); + } else { avail = min(avail, alsa.period_size); } @@ -613,7 +789,7 @@ static pthread_t thread; void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], - unsigned rate_delay, unsigned rt_priority) { + unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute) { unsigned alsa_buffer = ALSA_BUFFER_TIME; unsigned alsa_period = ALSA_PERIOD_COUNT; @@ -621,6 +797,9 @@ bool alsa_mmap = true; bool alsa_reopen = false; + char *volume_mixer_name = next_param(volume_mixer, ','); + char *volume_mixer_index = next_param(NULL, ','); + char *t = next_param(params, ':'); char *c = next_param(NULL, ':'); char *s = next_param(NULL, ':'); @@ -643,6 +822,12 @@ alsa.write_buf = NULL; alsa.format = 0; alsa.reopen = alsa_reopen; + + if (!mixer_unmute) { + alsa.volume_mixer_name = volume_mixer_name; + alsa.volume_mixer_index = volume_mixer_index ? atoi(volume_mixer_index) : 0; + } + output.format = 0; output.buffer = alsa_buffer; output.period = alsa_period; @@ -662,7 +847,11 @@ snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); - output_init_common(level, device, output_buf_size, rates); + output_init_common(level, device, output_buf_size, rates, idle); + + if (mixer_unmute && volume_mixer_name) { + set_mixer(output.device, volume_mixer_name, volume_mixer_index ? atoi(volume_mixer_index) : 0, true, 0, 0); + } #if LINUX // RT linux - aim to avoid pagefaults by locking memory: @@ -673,8 +862,8 @@ LOG_INFO("memory locked"); } - mallopt(M_TRIM_THRESHOLD, -1); - mallopt(M_MMAP_MAX, 0); + mallopt(M_TRIM_THRESHOLD, -1); + mallopt(M_MMAP_MAX, 0); touch_memory(silencebuf, MAX_SILENCE_FRAMES * BYTES_PER_FRAME); touch_memory(outputbuf->buf, outputbuf->size); diff --git a/output_pa.c b/output_pa.c index 2bfce07..025904b 100644 --- a/output_pa.c +++ b/output_pa.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -51,7 +51,7 @@ #endif void list_devices(void) { - PaError err; + PaError err; int i; if ((err = Pa_Initialize()) != paNoError) { @@ -67,9 +67,17 @@ } printf("\n"); - if ((err = Pa_Terminate()) != paNoError) { + if ((err = Pa_Terminate()) != paNoError) { LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); } +} + +void set_volume(unsigned left, unsigned right) { + LOG_DEBUG("setting internal gain left: %u right: %u", left, right); + LOCK; + output.gainL = left; + output.gainR = right; + UNLOCK; } static int pa_device_id(const char *device) { @@ -306,7 +314,7 @@ IF_DSD( if (output.dop) { - update_dop_marker((u32_t *) outputbuf->readp, out_frames); + update_dop((u32_t *) outputbuf->readp, out_frames, output.invert); } ) @@ -319,7 +327,7 @@ IF_DSD( if (output.dop) { buf = silencebuf_dop; - update_dop_marker((u32_t *) buf, out_frames); + update_dop((u32_t *) buf, out_frames, false); // don't invert silence } ) @@ -377,7 +385,8 @@ return ret; } -void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { +void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, + unsigned idle) { PaError err; unsigned latency = 0; int osx_playnice = -1; @@ -404,12 +413,12 @@ LOG_INFO("requested latency: %u", output.latency); - if ((err = Pa_Initialize()) != paNoError) { + if ((err = Pa_Initialize()) != paNoError) { LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); exit(0); } - output_init_common(level, device, output_buf_size, rates); + output_init_common(level, device, output_buf_size, rates, idle); LOCK; @@ -434,7 +443,7 @@ } } - if ((err = Pa_Terminate()) != paNoError) { + if ((err = Pa_Terminate()) != paNoError) { LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); } diff --git a/output_pack.c b/output_pack.c index ed7845c..16e7ab7 100644 --- a/output_pack.c +++ b/output_pack.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/output_stdout.c b/output_stdout.c index 568d009..37544ab 100644 --- a/output_stdout.c +++ b/output_stdout.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -67,7 +67,7 @@ if (silence) { obuf = silencebuf_dop; } - update_dop_marker((u32_t *)obuf, out_frames); + update_dop((u32_t *)obuf, out_frames, output.invert && !silence); } ) @@ -150,7 +150,7 @@ rates[0] = 44100; } - output_init_common(level, "-", output_buf_size, rates); + output_init_common(level, "-", output_buf_size, rates, 0); #if LINUX || OSX || FREEBSD pthread_attr_t attr; diff --git a/output_vis.c b/output_vis.c index 2c616e8..087836e 100644 --- a/output_vis.c +++ b/output_vis.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/pcm.c b/pcm.c index 4b6a638..c867ef5 100644 --- a/pcm.c +++ b/pcm.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -104,6 +104,7 @@ ptr += 8; _buf_inc_readp(streambuf, ptr - streambuf->readp); audio_left = len; + LOG_INFO("audio size: %u", audio_left); limit = true; return; } @@ -113,7 +114,8 @@ // following 4 bytes is blocksize - ignored ptr += 8 + 8; _buf_inc_readp(streambuf, ptr + offset - streambuf->readp); - audio_left = len; + audio_left = len - 8 - offset; + LOG_INFO("audio size: %u", audio_left); limit = true; return; } @@ -189,7 +191,7 @@ if (decode.new_stream) { LOG_INFO("setting track_start"); LOCK_O_not_direct; - output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); + output.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); output.track_start = outputbuf->writep; IF_DSD( output.next_dop = false; ) if (output.fade_mode) _checkfade(true); diff --git a/process.c b/process.c index 67ca3c2..6a107c6 100644 --- a/process.c +++ b/process.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/resample.c b/resample.c index e0c7c89..6157638 100644 --- a/resample.c +++ b/resample.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -74,6 +74,7 @@ SOXR(r, process, r->resampler, process->inbuf, process->in_frames, &idone, process->outbuf, process->max_out_frames, &odone); if (error) { LOG_INFO("soxr_process error: %s", soxr_strerror(error)); + return; } if (idone != process->in_frames) { @@ -100,6 +101,7 @@ soxr_error_t error = SOXR(r, process, r->resampler, NULL, 0, NULL, process->outbuf, process->max_out_frames, &odone); if (error) { LOG_INFO("soxr_process error: %s", soxr_strerror(error)); + return true; } process->out_frames = odone; diff --git a/slimproto.c b/slimproto.c index 0a310e1..81d2f30 100644 --- a/slimproto.c +++ b/slimproto.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -46,6 +46,9 @@ extern struct decodestate decode; extern struct codec *codecs[]; +#if IR +extern struct irstate ir; +#endif event_event wake_e; @@ -55,6 +58,10 @@ #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; @@ -101,7 +108,7 @@ } static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap, u8_t mac[6]) { - const char *base_cap = "Model=squeezelite,ModelName=SqueezeLite,AccuratePlayPoints=1,HasDigitalOut=1"; + const char *base_cap = "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION; struct HELO_packet pkt; memset(&pkt, 0, sizeof(pkt)); @@ -226,6 +233,23 @@ 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; @@ -257,7 +281,12 @@ unsigned interval = unpackN(&strm->replay_gain); LOCK_O; output.pause_frames = interval * status.current_sample_rate / 1000; - output.state = interval ? OUTPUT_PAUSE_FRAMES : OUTPUT_STOPPED; + 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); @@ -280,9 +309,6 @@ output.state = jiffies ? OUTPUT_START_AT : OUTPUT_RUNNING; output.start_at = jiffies; UNLOCK_O; - LOCK_D; - decode.state = DECODE_RUNNING; - UNLOCK_D; LOG_DEBUG("unpause at: %u now: %u", jiffies, gettime_ms()); sendSTAT("STMr", 0); } @@ -327,6 +353,7 @@ 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; } @@ -371,8 +398,9 @@ if (!aude->enable_spdif && output.state != OUTPUT_OFF) { output.state = OUTPUT_OFF; } - if (aude->enable_spdif && 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; } @@ -384,10 +412,7 @@ LOG_DEBUG("audg gainL: %u gainR: %u adjust: %u", audg->gainL, audg->gainR, audg->adjust); - LOCK_O; - output.gainL = audg->adjust ? audg->gainL : FIXED_ONE; - output.gainR = audg->adjust ? audg->gainR : FIXED_ONE; - UNLOCK_O; + set_volume(audg->adjust ? audg->gainL : FIXED_ONE, audg->adjust ? audg->gainR : FIXED_ONE); } static void process_setd(u8_t *pkt, int len) { @@ -564,9 +589,15 @@ 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; @@ -594,6 +625,30 @@ stream.meta_send = false; } UNLOCK_S; + + LOCK_D; + if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !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); @@ -614,46 +669,42 @@ output.pa_reopen = false; } #endif - if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT) { + if (_start_output && (output.state == OUTPUT_STOPPED || 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; } - UNLOCK_O; - - LOCK_D; - if (decode.state == DECODE_RUNNING && now - status.last > 1000) { + 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; } - if ((status.stream_state == STREAMING_HTTP || status.stream_state == STREAMING_FILE) && !sentSTMl - && decode.state == DECODE_STOPPED) { - if (autostart == 0) { - _sendSTMl = true; - sentSTMl = true; - } else if (autostart == 1) { - decode.state = DECODE_RUNNING; - LOCK_O; - if (output.state == OUTPUT_STOPPED) { - output.state = OUTPUT_BUFFER; - } - UNLOCK_O; - } - // 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; - } - } - UNLOCK_D; - + 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 @@ -667,6 +718,9 @@ 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 } } } @@ -677,8 +731,8 @@ } in_addr_t discover_server(void) { - struct sockaddr_in d; - struct sockaddr_in s; + struct sockaddr_in d; + struct sockaddr_in s; char *buf; struct pollfd pollinfo; @@ -690,9 +744,9 @@ buf = "e"; memset(&d, 0, sizeof(d)); - d.sin_family = AF_INET; + d.sin_family = AF_INET; d.sin_port = htons(PORT); - d.sin_addr.s_addr = htonl(INADDR_BROADCAST); + d.sin_addr.s_addr = htonl(INADDR_BROADCAST); pollinfo.fd = disc_sock; pollinfo.events = POLLIN; @@ -720,9 +774,12 @@ return s.sin_addr.s_addr; } -void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile) { - struct sockaddr_in serv_addr; - static char fixed_cap[128], var_cap[128] = ""; +#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) { + 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; @@ -772,10 +829,11 @@ if (!running) return; LOCK_O; - sprintf(fixed_cap, ",MaxSampleRate=%u", output.supported_rates[0]); + snprintf(fixed_cap, FIXED_CAP_LEN, ",ModelName=%s,MaxSampleRate=%u", modelname ? modelname : MODEL_NAME_STRING, + output.supported_rates[0]); for (i = 0; i < MAX_CODECS; i++) { - if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < 128 - 10) { + if (codecs[i] && codecs[i]->id && strlen(fixed_cap) < FIXED_CAP_LEN - 10) { strcat(fixed_cap, ","); strcat(fixed_cap, codecs[i]->types); } diff --git a/slimproto.h b/slimproto.h index 77f197d..319ee8d 100644 --- a/slimproto.h +++ b/slimproto.h @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -88,6 +88,17 @@ // 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]; @@ -155,7 +166,7 @@ // 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 format; u8_t pcm_sample_size; u8_t pcm_sample_rate; u8_t pcm_channels; diff --git a/squeezelite.h b/squeezelite.h index c331415..455fdfd 100644 --- a/squeezelite.h +++ b/squeezelite.h @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,9 +18,17 @@ * */ -// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, DSD, LINKALL to influence build - -#define VERSION "v1.6.4" +// make may define: PORTAUDIO, SELFPIPE, RESAMPLE, RESAMPLE_MP, VISEXPORT, IR, DSD, LINKALL to influence build + +#define VERSION "v1.8" + +#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) @@ -100,6 +108,13 @@ #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 @@ -133,6 +148,7 @@ #define LIBAVCODEC "libavcodec.so.%d" #define LIBAVFORMAT "libavformat.so.%d" #define LIBSOXR "libsoxr.so.0" +#define LIBLIRC "liblirc_client.so.0" #endif #if OSX @@ -213,6 +229,7 @@ #define STREAM_THREAD_STACK_SIZE 64 * 1024 #define DECODE_THREAD_STACK_SIZE 128 * 1024 #define OUTPUT_THREAD_STACK_SIZE 64 * 1024 +#define IR_THREAD_STACK_SIZE 64 * 1024 #define thread_t pthread_t; #define closesocket(s) close(s) #define last_error() errno @@ -409,7 +426,7 @@ 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); +void slimproto(log_level level, char *server, u8_t mac[6], const char *name, const char *namefile, const char *modelname); void slimproto_stop(void); void wake_controller(void); @@ -440,7 +457,7 @@ bool stream_disconnect(void); // decode.c -typedef enum { DECODE_STOPPED = 0, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state; +typedef enum { DECODE_STOPPED = 0, DECODE_READY, DECODE_RUNNING, DECODE_COMPLETE, DECODE_ERROR } decode_state; struct decodestate { decode_state state; @@ -544,6 +561,7 @@ 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; @@ -554,6 +572,8 @@ unsigned fade_secs; // set by slimproto unsigned rate_delay; bool delay_active; + u32_t stop_time; + u32_t idle_to; #if DSD bool next_dop; // set in decode thread bool dop; @@ -562,7 +582,7 @@ #endif }; -void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[]); +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 @@ -572,17 +592,20 @@ // 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[]); 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 rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute); 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[]); -void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay); +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 @@ -611,7 +634,7 @@ // dop.c #if DSD bool is_flac_dop(u32_t *lptr, u32_t *rptr, frames_t frames); -void update_dop_marker(u32_t *ptr, frames_t frames); +void update_dop(u32_t *ptr, frames_t frames, bool invert); void dop_silence_frames(u32_t *ptr, frames_t frames); void dop_init(bool enable, unsigned delay); #endif @@ -627,3 +650,15 @@ struct codec *register_faad(void); struct codec *register_dsd(void); struct codec *register_ff(const char *codec); + +// 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 diff --git a/stream.c b/stream.c index cfded7f..3fdb28b 100644 --- a/stream.c +++ b/stream.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -367,7 +367,7 @@ } 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; + struct sockaddr_in addr; int sock = socket(AF_INET, SOCK_STREAM, 0); @@ -386,7 +386,7 @@ set_nonblock(sock); set_nosigpipe(sock); - if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) { + if (connect_timeout(sock, (struct sockaddr *) &addr, sizeof(addr), 10) < 0) { LOG_INFO("unable to connect to server"); LOCK; stream.state = DISCONNECT; diff --git a/utils.c b/utils.c index 7a5fde7..ba67d77 100644 --- a/utils.c +++ b/utils.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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 @@ -70,7 +70,7 @@ static char *str = NULL; char *ptr, *ret; if (src) str = src; - if (str && (ptr = strchr(str, c))) { + if (str && (ptr = strchr(str, c))) { ret = str; *ptr = '\0'; str = ptr + 1; @@ -103,19 +103,19 @@ #if LINUX // search first 4 interfaces returned by IFCONF void get_mac(u8_t mac[]) { - struct ifconf ifc; - struct ifreq *ifr, *ifend; - struct ifreq ifreq; - struct ifreq ifs[4]; + struct ifconf ifc; + struct ifreq *ifr, *ifend; + struct ifreq ifreq; + struct ifreq ifs[4]; 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) { + 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++) { @@ -163,9 +163,9 @@ #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); + 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; @@ -316,13 +316,13 @@ #if WIN void winsock_init(void) { - WSADATA wsaData; + WSADATA wsaData; WORD wVersionRequested = MAKEWORD(2, 2); - int WSerr = WSAStartup(wVersionRequested, &wsaData); - if (WSerr != 0) { - LOG_ERROR("Bad winsock version"); - exit(1); - } + int WSerr = WSAStartup(wVersionRequested, &wsaData); + if (WSerr != 0) { + LOG_ERROR("Bad winsock version"); + exit(1); + } } void winsock_close(void) { @@ -366,7 +366,7 @@ tv.tv_usec = 1000 * (timeout % 1000); ret = select(fds[0].fd + 1, &r, &w, NULL, &tv); - + if (ret < 0) return ret; fds[0].revents = 0; @@ -383,6 +383,6 @@ u8_t *ptr; for (ptr = buf; ptr < buf + size; ptr += sysconf(_SC_PAGESIZE)) { *ptr = 0; - } -} -#endif + } +} +#endif diff --git a/vorbis.c b/vorbis.c index 15becf1..0809bee 100644 --- a/vorbis.c +++ b/vorbis.c @@ -1,7 +1,7 @@ /* * Squeezelite - lightweight headless squeezebox emulator * - * (c) Adrian Smith 2012-2014, triode1@btinternet.com + * (c) Adrian Smith 2012-2015, triode1@btinternet.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