pax_global_header 0000666 0000000 0000000 00000000064 12463437225 0014522 g ustar 00root root 0000000 0000000 52 comment=8b8dfe6918ebe45ade5f3d9b68d453d7b8128d99 squeezelite-1.8/ 0000775 0000000 0000000 00000000000 12463437225 0013731 5 ustar 00root root 0000000 0000000 squeezelite-1.8/ChangeLog.txt 0000664 0000000 0000000 00000010360 12463437225 0016321 0 ustar 00root root 0000000 0000000 Version 1.0 - 15/2/13 ===================== - initial release Version 1.1 - 12/4/13 ===================== Minor changes - add timeout on slimproto connection to detect dead server - fix issue with clipping on windows by disabling portaudio dither - silence alsa error messages on linux alsa builds unless debugging is enabled - hide some additional error messages unless debuging is enabled so usb dacs produce less error messages when turned off and on Version 1.2 - 6/7/13 ==================== Features - support of upsampling via libsoxr Minor changes - command line option for setting the service address now requires "-s" before the server address - fixes a bug where the channels could become swapped when using S16_LE ALSA output - falls back to polling for a new server if one is not found for more than 30 seconds - fixes play of wav/aiff local files when the LocalPlayer plugin is active Version 1.3 - 6/10/13 ===================== Features - support for wma/alac decode via ffmpeg library (requires compilation with -DFFMPEG) - support for export of audio data to jivelite to enable visulizations on linux (requires compilation with -DVISEXPORT) Minor changes - support async as well as sync resampling rates - support on/off of audio device with portaudio - improved gapless support for aac/mad when skipping to mid track (based on patches from Wouter Ellenbroek) - various bug fixes Version 1.3.1 - 25/11/13 ======================== Minor changes - support of compile time linking for distro packaging, uses -DLINKALL option Version 1.4 28/12/13 ==================== Features - native support of dsd playback to dop capable dac or via conversion to pcm and resampling - support dop in flac playback to dop dacs - support of output to stdout Minor changes - support of resampling only when sample rate is not natively supported - fix problem with libmpg123 playback not playing to end of track - add ablity for player name change to be stored locally in a file (to emulate hardware where name is stored on player) Version 1.5 12/1/14 =================== Minor changes - add configurable delay for switch between pcm and dop - allow visexport to work with jivelite running as any user - bug fixes for dsf playback, for status progress on windows using wdm-ks output, and to avoid 100% cpu - change some logging levels for slimproto to aid readability Version 1.6 23/3/14 =================== Minor changes - add support for direct file playback on windows - add configurable delay for switch between pcm sample rates - support build on freebsd - fix gapless playback on portaudio builds - fix gapless playback for mp3 localfile case with tags at start of file Version 1.6.1 22/4/14 ===================== Minor changes - fix bug with PA version changing sample rate between tracks - fix crash when skipping in ogg while resampling - fix typo Version 1.6.2 26/5/14 ===================== Minor changes - fix XRUN on track change when resampling on low power cpus - log command line to logfile when debugging enabled - option to exclude codecs (-e) - support parallel execution of libsoxr Version 1.6.3 14/6/14 ===================== Minor changes - reduce time to start track when playing local files - disable use of OPENMP when RESAMPLE build option defined, add new option RESAMPLE_MP to enable it Version 1.6.4 7/7/14 ==================== Minor changes - improve synchronisation feedback accuracy Version 1.6.5 21/11/14 ====================== Minor changes - fix problem opening ALSA device if 44100 is not supported - trap setting of hw player mac address Version 1.7 1/1/15 ================== Minor changes - allow player modelname to be set at compile or run time - workaround alsa drivers reporting very large number of available frames - fix clicks on localfile playback of AIFF files - add -P option to store process id in a file - improve error messages for command line parsing Version 1.7.1 10/1/15 ===================== Minor changes - fix crash which could occur when resampling Version 1.8 1/2/15 ================== Features - support for closing output device when idle with -C option - support for basic IR input using LIRC on Linux - support for volume adjustment or unmuting of alsa mixer - support for inverting output polarity via LMS setting (requires recent 7.9 server) squeezelite-1.8/LICENSE.txt 0000664 0000000 0000000 00000004601 12463437225 0015555 0 ustar 00root root 0000000 0000000 Squeezelite - lightweight headless squeezebox emulator (c) Adrian Smith 2012-2015, triode1@btinternet.com Released under GPLv3 license: 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 . --------------------------------------------------------------------- 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. squeezelite-1.8/Makefile 0000664 0000000 0000000 00000004147 12463437225 0015377 0 ustar 00root root 0000000 0000000 # Cross compile support - create a Makefile which defines these three variables and then includes this Makefile... CFLAGS ?= -Wall -fPIC -O2 $(OPTS) LDFLAGS ?= -lasound -lpthread -lm -lrt EXECUTABLE ?= squeezelite # passing one or more of these in $(OPTS) enables optional feature inclusion OPT_DSD = -DDSD OPT_FF = -DFFMPEG OPT_LINKALL = -DLINKALL OPT_RESAMPLE= -DRESAMPLE OPT_VIS = -DVISEXPORT OPT_IR = -DIR SOURCES = \ main.c slimproto.c buffer.c stream.c utils.c \ output.c output_alsa.c output_pa.c output_stdout.c output_pack.c decode.c \ flac.c pcm.c mad.c vorbis.c faad.c mpg.c SOURCES_DSD = dsd.c dop.c dsd2pcm/dsd2pcm.c 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 UNAME = $(shell uname -s) # add optional sources ifneq (,$(findstring $(OPT_DSD), $(CFLAGS))) SOURCES += $(SOURCES_DSD) endif ifneq (,$(findstring $(OPT_FF), $(CFLAGS))) SOURCES += $(SOURCES_FF) endif ifneq (,$(findstring $(OPT_RESAMPLE), $(CFLAGS))) SOURCES += $(SOURCES_RESAMPLE) endif 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))) LDFLAGS += $(LINKALL) ifneq (,$(findstring $(OPT_FF), $(CFLAGS))) LDFLAGS += $(LINKALL_FF) 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 ifeq ($(UNAME), Linux) LDFLAGS += $(LINK_LINUX) endif endif OBJECTS = $(SOURCES:.c=.o) all: $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) $(CC) $(OBJECTS) $(LDFLAGS) -o $@ $(OBJECTS): $(DEPS) .c.o: $(CC) $(CFLAGS) $(CPPFLAGS) $< -c -o $@ clean: rm -f $(OBJECTS) $(EXECUTABLE) squeezelite-1.8/Makefile.freebsd 0000664 0000000 0000000 00000000243 12463437225 0017001 0 ustar 00root root 0000000 0000000 CPPFLAGS = -I/usr/local/include -I/usr/local/include/portaudio2 LDFLAGS = -L/usr/local/lib -L/usr/local/lib/portaudio2 -lportaudio -lpthread -lm include Makefile squeezelite-1.8/Makefile.osx 0000664 0000000 0000000 00000000520 12463437225 0016176 0 ustar 00root root 0000000 0000000 # OSX build - adjust -I to point to header files for codecs and portaudio CFLAGS = -arch x86_64 -arch i386 -Wall -fPIC -O2 -I./include $(OPTS) LDFLAGS = -arch x86_64 -arch i386 -lpthread libportaudio.a -framework CoreAudio -framework AudioToolbox -framework AudioUnit -framework Carbon EXECUTABLE ?= squeezelite-osx include Makefile squeezelite-1.8/Makefile.pa 0000664 0000000 0000000 00000000232 12463437225 0015765 0 ustar 00root root 0000000 0000000 # Make with portaudio rather than direct alsa OPTS += -DPORTAUDIO LDFLAGS = -lportaudio -lpthread -ldl -lrt EXECUTABLE = squeezelite-pa include Makefile squeezelite-1.8/Makefile.resample 0000664 0000000 0000000 00000000044 12463437225 0017176 0 ustar 00root root 0000000 0000000 OPTS = -DRESAMPLE include Makefile squeezelite-1.8/buffer.c 0000664 0000000 0000000 00000005771 12463437225 0015360 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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); } } squeezelite-1.8/decode.c 0000664 0000000 0000000 00000014676 12463437225 0015336 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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 thread_type thread; void decode_init(log_level level, const char *include_codecs, const char *exclude_codecs) { int i; loglevel = level; LOG_INFO("init decode, include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs); // register codecs // dsf,dff,alc,wma,wmap,wmal,aac,spt,ogg,ogf,flc,aif,pcm,mp3 i = 0; #if DSD if (!strstr(exclude_codecs, "dsd") && (!include_codecs || strstr(include_codecs, "dsd"))) codecs[i++] = register_dsd(); #endif #if FFMPEG if (!strstr(exclude_codecs, "alac") && (!include_codecs || strstr(include_codecs, "alac"))) codecs[i++] = register_ff("alc"); if (!strstr(exclude_codecs, "wma") && (!include_codecs || strstr(include_codecs, "wma"))) codecs[i++] = register_ff("wma"); #endif if (!strstr(exclude_codecs, "aac") && (!include_codecs || strstr(include_codecs, "aac"))) codecs[i++] = register_faad(); if (!strstr(exclude_codecs, "ogg") && (!include_codecs || strstr(include_codecs, "ogg"))) codecs[i++] = register_vorbis(); if (!strstr(exclude_codecs, "flac") && (!include_codecs || strstr(include_codecs, "flac"))) codecs[i++] = register_flac(); if (!strstr(exclude_codecs, "pcm") && (!include_codecs || strstr(include_codecs, "pcm"))) codecs[i++] = register_pcm(); // try mad then mpg for mp3 unless command line option passed if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mad")) && (!include_codecs || strstr(include_codecs, "mp3") || strstr(include_codecs, "mad"))) codecs[i] = register_mad(); if (!(strstr(exclude_codecs, "mp3") || strstr(exclude_codecs, "mpg")) && !codecs[i] && (!include_codecs || strstr(include_codecs, "mp3") || strstr(include_codecs, "mpg"))) codecs[i] = register_mpg(); mutex_create(decode.mutex); #if LINUX || OSX || FREEBSD pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + DECODE_THREAD_STACK_SIZE); 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"); } squeezelite-1.8/dop.c 0000664 0000000 0000000 00000005415 12463437225 0014664 0 ustar 00root root 0000000 0000000 /* * Squeezelite - lightweight headless squeezebox emulator * * (c) Adrian Smith 2012, 2013, 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 . * */ // DSP over PCM (DOP) specific functions #include "squeezelite.h" #if DSD extern struct buffer *outputbuf; extern struct outputstate output; #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) // check for 32 dop marker frames to see if this is dop in flac // dop is always encoded in 24 bit samples with marker 0x0005xxxx or 0x00FAxxxx bool is_flac_dop(u32_t *lptr, u32_t *rptr, frames_t frames) { int matched = 0; u32_t next = 0; while (frames--) { if (((*lptr & 0x00FF0000) == 0x00050000 && (*rptr & 0x00FF0000) == 0x00050000) || ((*lptr & 0x00FF0000) == 0x00FA0000 && (*rptr & 0x00FF0000) == 0x00FA0000)) { if (*lptr >> 24 == next) { matched++; next = ( 0x05 + 0xFA ) - next; } else { next = *lptr >> 24; matched = 1; } } else { return false; } if (matched == 32) { return true; } ++lptr; ++rptr; } return false; } // 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(u32_t *ptr, frames_t frames, bool invert) { static u32_t marker = 0x05; 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; } } } // fill silence buffer with 10101100 which represents dop silence // leave marker zero it will be updated at output, leave lsb zero void dop_silence_frames(u32_t *ptr, frames_t frames) { while (frames--) { *ptr++ = 0x00ACAC00; *ptr++ = 0x00ACAC00; } } void dop_init(bool enable, unsigned delay) { LOCK_O; output.has_dop = enable; output.dop_delay = delay; UNLOCK_O; } #endif // DSD squeezelite-1.8/dsd.c 0000664 0000000 0000000 00000040634 12463437225 0014656 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // dsd support #include "squeezelite.h" #if DSD // use dsd2pcm from Sebastian Gesemann for conversion to pcm: #include "./dsd2pcm/dsd2pcm.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; #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 BLOCK 4096 // expected size of dsd block #define BLOCK_FRAMES BLOCK * BYTES_PER_FRAME #define WRAP_BUF_SIZE 16 typedef enum { UNKNOWN=0, DSF, DSDIFF } dsd_type; static bool dop = false; // local copy of output.has_dop to avoid holding output lock struct dsd { dsd_type type; u32_t consume; u32_t sample_rate; u32_t channels; u64_t sample_bytes; u32_t block_size; bool lsb_first; dsd2pcm_ctx *dsd2pcm_ctx[2]; float *transfer[2]; }; static struct dsd *d; static u64_t unpack64be(const u8_t *p) { return (u64_t)p[0] << 56 | (u64_t)p[1] << 48 | (u64_t)p[2] << 40 | (u64_t)p[3] << 32 | (u64_t)p[4] << 24 | (u64_t)p[5] << 16 | (u64_t)p[6] << 8 | (u64_t)p[7]; } static u64_t unpack64le(const u8_t *p) { return (u64_t)p[7] << 56 | (u64_t)p[6] << 48 | (u64_t)p[5] << 40 | (u64_t)p[4] << 32 | (u64_t)p[3] << 24 | (u64_t)p[2] << 16 | (u64_t)p[1] << 8 | (u64_t)p[0]; } static u32_t unpack32le(const u8_t *p) { return (u32_t)p[3] << 24 | (u32_t)p[2] << 16 | (u32_t)p[1] << 8 | (u32_t)p[0]; } static int _read_header(void) { unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); s32_t consume; if (!d->type && bytes >= 4) { if (!memcmp(streambuf->readp, "FRM8", 4)) { d->type = DSDIFF; } else if (!memcmp(streambuf->readp, "DSD ", 4)) { d->type = DSF; } else { LOG_WARN("bad type"); return -1; } } while (bytes >= 16) { char id[5]; u64_t len = d->type == DSDIFF ? unpack64be(streambuf->readp + 4) : unpack64le(streambuf->readp + 4); memcpy(id, streambuf->readp, 4); id[4] = '\0'; consume = 0; if (d->type == DSDIFF) { if (!strcmp(id, "FRM8")) { if (!memcmp(streambuf->readp + 12, "DSD ", 4)) { consume = 16; // read into } else { LOG_WARN("bad dsdiff FRM8"); return -1; } } if (!strcmp(id, "PROP") && !memcmp(streambuf->readp + 12, "SND ", 4)) { consume = 16; // read into } if (!strcmp(id, "FVER")) { LOG_INFO("DSDIFF version: %u.%u.%u.%u", *(streambuf->readp + 12), *(streambuf->readp + 13), *(streambuf->readp + 14), *(streambuf->readp + 15)); } if (!strcmp(id, "FS ")) { d->sample_rate = unpackN((void *)(streambuf->readp + 12)); LOG_INFO("sample rate: %u", d->sample_rate); } if (!strcmp(id, "CHNL")) { d->channels = unpackn((void *)(streambuf->readp + 12)); LOG_INFO("channels: %u", d->channels); } if (!strcmp(id, "DSD ")) { LOG_INFO("found dsd len: " FMT_u64, len); d->sample_bytes = len; _buf_inc_readp(streambuf, 12); bytes -= 12; return 1; // got to the audio } } if (d->type == DSF) { if (!strcmp(id, "fmt ")) { if (bytes >= len && bytes >= 52) { u32_t version = unpack32le((void *)(streambuf->readp + 12)); u32_t format = unpack32le((void *)(streambuf->readp + 16)); LOG_INFO("DSF version: %u format: %u", version, format); if (format != 0) { LOG_WARN("only support DSD raw format"); return -1; } d->channels = unpack32le((void *)(streambuf->readp + 24)); d->sample_rate = unpack32le((void *)(streambuf->readp + 28)); d->lsb_first = (unpack32le((void *)(streambuf->readp + 32)) == 1); d->sample_bytes = unpack64le((void *)(streambuf->readp + 36)) / 8; d->block_size = unpack32le((void *)(streambuf->readp + 44)); LOG_INFO("channels: %u", d->channels); LOG_INFO("sample rate: %u", d->sample_rate); LOG_INFO("lsb first: %u", d->lsb_first); LOG_INFO("sample bytes: " FMT_u64, d->sample_bytes); LOG_INFO("block size: %u", d->block_size); } else { consume = -1; // come back later } } if (!strcmp(id, "data")) { LOG_INFO("found dsd len: " FMT_u64, len); _buf_inc_readp(streambuf, 12); bytes -= 12; return 1; // got to the audio } } // default to consuming whole chunk if (!consume) { consume = (s32_t)((d->type == DSDIFF) ? len + 12 : len); } if (bytes >= consume) { LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d", id, len, consume); _buf_inc_readp(streambuf, consume); bytes -= consume; } else if (consume > 0) { LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d - partial consume: %u", id, len, consume, bytes); _buf_inc_readp(streambuf, bytes); d->consume = consume - bytes; break; } else { break; } } return 0; } static decode_state _decode_dsf(void) { // samples in streambuf are interleaved on block basis // we transfer whole blocks for all channels in one call and so itterate the while loop below to handle wraps unsigned bytes = _buf_used(streambuf); unsigned block_left = d->block_size; unsigned bytes_per_frame = dop ? 2 : 1; if (bytes < d->block_size * d->channels) { LOG_INFO("stream too short"); // this can occur when scanning the track return DECODE_COMPLETE; } IF_PROCESS( process.in_frames = 0; ); while (block_left) { frames_t frames, out, count; unsigned bytes_read; u8_t *iptrl = (u8_t *)streambuf->readp; u8_t *iptrr = (u8_t *)streambuf->readp + d->block_size; u32_t *optr; if (iptrr >= streambuf->wrap) { iptrr -= streambuf->size; } bytes = min(block_left, min(streambuf->wrap - iptrl, streambuf->wrap - iptrr)); IF_DIRECT( out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; optr = (u32_t *)outputbuf->writep; ); IF_PROCESS( out = process.max_in_frames - process.in_frames; optr = (u32_t *)(process.inbuf + process.in_frames * BYTES_PER_FRAME); ); frames = min(bytes, d->sample_bytes) / bytes_per_frame; if (frames == 0) { if (dop && d->sample_bytes == 1 && bytes >= 2) { // 1 byte left add a byte of silence and play *(iptrl + 1) = *(iptrr + 1) = 0x69; frames = 1; } else { // should not get here due to wrapping m/2 for dop should never result in 0 as header len is always even LOG_INFO("frames got to zero"); return DECODE_COMPLETE; } } frames = min(frames, out); frames = min(frames, BLOCK); bytes_read = frames * bytes_per_frame; count = frames; if (dop) { if (d->channels == 1) { if (d->lsb_first) { while (count--) { *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8; *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8; iptrl += 2; } } else { while (count--) { *(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8; *(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8; iptrl += 2; } } } else { if (d->lsb_first) { while (count--) { *(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8; *(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 16 | dsd2pcm_bitreverse[*(iptrr+1)] << 8; iptrl += 2; iptrr += 2; } } else { while (count--) { *(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8; *(optr++) = *(iptrr) << 16 | *(iptrr+1) << 8; iptrl += 2; iptrr += 2; } } } } else { if (d->channels == 1) { float *iptrf = d->transfer[0]; dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptrl, 1, d->lsb_first, iptrf, 1); while (count--) { double scaled = *iptrf++ * 0x7fffffff; if (scaled > 2147483647.0) scaled = 2147483647.0; if (scaled < -2147483648.0) scaled = -2147483648.0; *optr++ = (s32_t)scaled; *optr++ = (s32_t)scaled; } } else { float *iptrfl = d->transfer[0]; float *iptrfr = d->transfer[1]; dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptrl, 1, d->lsb_first, iptrfl, 1); dsd2pcm_translate(d->dsd2pcm_ctx[1], frames, iptrr, 1, d->lsb_first, iptrfr, 1); while (count--) { double scaledl = *iptrfl++ * 0x7fffffff; double scaledr = *iptrfr++ * 0x7fffffff; if (scaledl > 2147483647.0) scaledl = 2147483647.0; if (scaledl < -2147483648.0) scaledl = -2147483648.0; if (scaledr > 2147483647.0) scaledr = 2147483647.0; if (scaledr < -2147483648.0) scaledr = -2147483648.0; *optr++ = (s32_t)scaledl; *optr++ = (s32_t)scaledr; } } } _buf_inc_readp(streambuf, bytes_read); block_left -= bytes_read; if (d->sample_bytes > bytes_read) { d->sample_bytes -= bytes_read; } else { LOG_INFO("end of track samples"); block_left = 0; d->sample_bytes = 0; } IF_DIRECT( _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames += frames; ); LOG_SDEBUG("write %u frames", frames); } // skip the other channel blocks // the right channel has already been read and is guarenteed to be in streambuf so can be skipped immediately if (d->channels > 1) { _buf_inc_readp(streambuf, d->block_size); } if (d->channels > 2) { d->consume = d->block_size * (d->channels - 2); } return DECODE_RUNNING; } static decode_state _decode_dsdiff(void) { // samples in streambuf are interleaved on byte per channel // we process as little as necessary per call and only need to handle frames wrapping round streambuf unsigned bytes_per_frame, bytes_read; frames_t out, frames, count; u8_t *iptr; u32_t *optr; u8_t tmp[WRAP_BUF_SIZE]; unsigned 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 (dop) { bytes_per_frame = d->channels * 2; } else { bytes_per_frame = d->channels; out = min(out, BLOCK); } frames = min(min(bytes, d->sample_bytes) / bytes_per_frame, out); bytes_read = frames * bytes_per_frame; iptr = (u8_t *)streambuf->readp; IF_DIRECT( optr = (u32_t *)outputbuf->writep; ); IF_PROCESS( optr = (u32_t *)process.inbuf; ); // handle wrap around end of streambuf and partial dop frame at end of stream if (!frames && bytes < bytes_per_frame) { memset(tmp, 0x69, WRAP_BUF_SIZE); // 0x69 = dsd silence memcpy(tmp, streambuf->readp, bytes); if (_buf_used(streambuf) > bytes_per_frame) { memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes); bytes_read = bytes_per_frame; } else { bytes_read = bytes; } iptr = tmp; frames = 1; } count = frames; if (dop) { if (d->channels == 1) { while (count--) { *(optr++) = *(iptr) << 16 | *(iptr+1) << 8; *(optr++) = *(iptr) << 16 | *(iptr+1) << 8; iptr += bytes_per_frame; } } else { while (count--) { *(optr++) = *(iptr ) << 16 | *(iptr + d->channels) << 8; *(optr++) = *(iptr+1) << 16 | *(iptr + d->channels + 1) << 8; iptr += bytes_per_frame; } } } else { if (d->channels == 1) { float *iptrf = d->transfer[0]; dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptr, 1, 0, iptrf, 1); while (count--) { double scaled = *iptrf++ * 0x7fffffff; if (scaled > 2147483647.0) scaled = 2147483647.0; if (scaled < -2147483648.0) scaled = -2147483648.0; *optr++ = (s32_t)scaled; *optr++ = (s32_t)scaled; } } else { float *iptrfl = d->transfer[0]; float *iptrfr = d->transfer[1]; dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptr, d->channels, 0, iptrfl, 1); dsd2pcm_translate(d->dsd2pcm_ctx[1], frames, iptr + 1, d->channels, 0, iptrfr, 1); while (count--) { double scaledl = *iptrfl++ * 0x7fffffff; double scaledr = *iptrfr++ * 0x7fffffff; if (scaledl > 2147483647.0) scaledl = 2147483647.0; if (scaledl < -2147483648.0) scaledl = -2147483648.0; if (scaledr > 2147483647.0) scaledr = 2147483647.0; if (scaledr < -2147483648.0) scaledr = -2147483648.0; *optr++ = (s32_t)scaledl; *optr++ = (s32_t)scaledr; } } } _buf_inc_readp(streambuf, bytes_read); if (d->sample_bytes > bytes_read) { d->sample_bytes -= bytes_read; } else { LOG_INFO("end of track samples"); d->sample_bytes = 0; } IF_DIRECT( _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames = frames; ); LOG_SDEBUG("write %u frames", frames); return DECODE_RUNNING; } static decode_state dsd_decode(void) { decode_state ret; LOCK_S; if ((stream.state <= DISCONNECT && !_buf_used(streambuf)) || (!decode.new_stream && d->sample_bytes == 0)) { UNLOCK_S; return DECODE_COMPLETE; } if (d->consume) { unsigned consume = min(d->consume, min(_buf_used(streambuf), _buf_cont_read(streambuf))); LOG_DEBUG("consume: %u of %u", consume, d->consume); _buf_inc_readp(streambuf, consume); d->consume -= consume; if (d->consume) { UNLOCK_S; return DECODE_RUNNING; } } if (decode.new_stream) { int r = _read_header(); if (r < 1) { UNLOCK_S; return DECODE_ERROR; } if (r == 0) { UNLOCK_S; return DECODE_RUNNING; } // otherwise got to start of audio LOCK_O; LOG_INFO("setting track_start"); output.track_start = outputbuf->writep; dop = output.has_dop; if (dop && d->sample_rate / 16 > output.supported_rates[0]) { LOG_INFO("DOP sample rate too high for device - converting to PCM"); dop = false; } if (dop) { LOG_INFO("DOP output"); output.next_dop = true; output.next_sample_rate = d->sample_rate / 16; output.fade = FADE_INACTIVE; } else { LOG_INFO("DSD to PCM output"); output.next_dop = false; output.next_sample_rate = decode_newstream(d->sample_rate / 8, output.supported_rates); if (output.fade_mode) _checkfade(true); } decode.new_stream = false; UNLOCK_O; } LOCK_O_direct; switch (d->type) { case DSF: ret = _decode_dsf(); break; case DSDIFF: ret = _decode_dsdiff(); break; default: ret = DECODE_ERROR; } UNLOCK_O_direct; UNLOCK_S; return ret; } static void dsd_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { d->type = UNKNOWN; if (!d->dsd2pcm_ctx[0]) { d->dsd2pcm_ctx[0] = dsd2pcm_init(); d->dsd2pcm_ctx[1] = dsd2pcm_init(); } else { dsd2pcm_reset(d->dsd2pcm_ctx[0]); dsd2pcm_reset(d->dsd2pcm_ctx[1]); } if (!d->transfer[1]) { d->transfer[0] = malloc(sizeof(float) * BLOCK); d->transfer[1] = malloc(sizeof(float) * BLOCK); } } static void dsd_close(void) { if (d->dsd2pcm_ctx[0]) { dsd2pcm_destroy(d->dsd2pcm_ctx[0]); dsd2pcm_destroy(d->dsd2pcm_ctx[1]); d->dsd2pcm_ctx[0] = NULL; d->dsd2pcm_ctx[1] = NULL; } if (d->transfer[0]) { free(d->transfer[0]); free(d->transfer[1]); d->transfer[0] = NULL; d->transfer[1] = NULL; } } struct codec *register_dsd(void) { static struct codec ret = { 'd', // id "dsf,dff", // types BLOCK * 2, // min read BLOCK_FRAMES,// min space dsd_open, // open dsd_close, // close dsd_decode, // decode }; d = malloc(sizeof(struct dsd)); if (!d) { return NULL; } memset(d, 0, sizeof(struct dsd)); dsd2pcm_precalc(); LOG_INFO("using dsd to decode dsf,dff"); return &ret; } #endif // DSD squeezelite-1.8/dsd2pcm/ 0000775 0000000 0000000 00000000000 12463437225 0015265 5 ustar 00root root 0000000 0000000 squeezelite-1.8/dsd2pcm/LICENSE.txt 0000664 0000000 0000000 00000002754 12463437225 0017120 0 ustar 00root root 0000000 0000000 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. squeezelite-1.8/dsd2pcm/dsd2pcm.c 0000664 0000000 0000000 00000013743 12463437225 0016775 0 ustar 00root root 0000000 0000000 /* 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. ---- Additions (c) Adrian Smith, 2013 under same licence terms: - expose bitreverse array as dsd2pcm_bitreverse - expose precalc function as dsd2pcm_precalc to allow it to be initalised */ #include #include #include "dsd2pcm.h" #define HTAPS 48 /* number of FIR constants */ #define FIFOSIZE 16 /* must be a power of two */ #define FIFOMASK (FIFOSIZE-1) /* bit mask for FIFO offsets */ #define CTABLES ((HTAPS+7)/8) /* number of "8 MACs" lookup tables */ #if FIFOSIZE*8 < HTAPS*2 #error "FIFOSIZE too small" #endif /* * Properties of this 96-tap lowpass filter when applied on a signal * with sampling rate of 44100*64 Hz: * * () has a delay of 17 microseconds. * * () flat response up to 48 kHz * * () if you downsample afterwards by a factor of 8, the * spectrum below 70 kHz is practically alias-free. * * () stopband rejection is about 160 dB * * The coefficient tables ("ctables") take only 6 Kibi Bytes and * should fit into a modern processor's fast cache. */ /* * The 2nd half (48 coeffs) of a 96-tap symmetric lowpass filter */ static const double htaps[HTAPS] = { 0.09950731974056658, 0.09562845727714668, 0.08819647126516944, 0.07782552527068175, 0.06534876523171299, 0.05172629311427257, 0.0379429484910187, 0.02490921351762261, 0.0133774746265897, 0.003883043418804416, -0.003284703416210726, -0.008080250212687497, -0.01067241812471033, -0.01139427235000863, -0.0106813877974587, -0.009007905078766049, -0.006828859761015335, -0.004535184322001496, -0.002425035959059578, -0.0006922187080790708, 0.0005700762133516592, 0.001353838005269448, 0.001713709169690937, 0.001742046839472948, 0.001545601648013235, 0.001226696225277855, 0.0008704322683580222, 0.0005381636200535649, 0.000266446345425276, 7.002968738383528e-05, -5.279407053811266e-05, -0.0001140625650874684, -0.0001304796361231895, -0.0001189970287491285, -9.396247155265073e-05, -6.577634378272832e-05, -4.07492895872535e-05, -2.17407957554587e-05, -9.163058931391722e-06, -2.017460145032201e-06, 1.249721855219005e-06, 2.166655190537392e-06, 1.930520892991082e-06, 1.319400334374195e-06, 7.410039764949091e-07, 3.423230509967409e-07, 1.244182214744588e-07, 3.130441005359396e-08 }; static float ctables[CTABLES][256]; unsigned char dsd2pcm_bitreverse[256]; static int precalculated = 0; void dsd2pcm_precalc(void) { int t, e, m, k; double acc; if (precalculated) return; for (t=0, e=0; t<256; ++t) { dsd2pcm_bitreverse[t] = e; for (m=128; m && !((e^=m)&m); m>>=1) ; } for (t=0; t8) k=8; for (e=0; e<256; ++e) { acc = 0.0; for (m=0; m> (7-m)) & 1)*2-1) * htaps[t*8+m]; } ctables[CTABLES-1-t][e] = (float)acc; } } precalculated = 1; } struct dsd2pcm_ctx_s { unsigned char fifo[FIFOSIZE]; unsigned fifopos; }; extern dsd2pcm_ctx* dsd2pcm_init() { dsd2pcm_ctx* ptr; if (!precalculated) dsd2pcm_precalc(); ptr = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx)); if (ptr) dsd2pcm_reset(ptr); return ptr; } extern void dsd2pcm_destroy(dsd2pcm_ctx* ptr) { free(ptr); } extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx* ptr) { dsd2pcm_ctx* p2; p2 = (dsd2pcm_ctx*) malloc(sizeof(dsd2pcm_ctx)); if (p2) { memcpy(p2,ptr,sizeof(dsd2pcm_ctx)); } return p2; } extern void dsd2pcm_reset(dsd2pcm_ctx* ptr) { int i; for (i=0; ififo[i] = 0x69; /* my favorite silence pattern */ ptr->fifopos = 0; /* 0x69 = 01101001 * This pattern "on repeat" makes a low energy 352.8 kHz tone * and a high energy 1.0584 MHz tone which should be filtered * out completely by any playback system --> silence */ } extern void dsd2pcm_translate( dsd2pcm_ctx* ptr, size_t samples, const unsigned char *src, ptrdiff_t src_stride, int lsbf, float *dst, ptrdiff_t dst_stride) { unsigned ffp; unsigned i; unsigned bite1, bite2; unsigned char* p; double acc; ffp = ptr->fifopos; lsbf = lsbf ? 1 : 0; while (samples-- > 0) { bite1 = *src & 0xFFu; if (lsbf) bite1 = dsd2pcm_bitreverse[bite1]; ptr->fifo[ffp] = bite1; src += src_stride; p = ptr->fifo + ((ffp-CTABLES) & FIFOMASK); *p = dsd2pcm_bitreverse[*p & 0xFF]; acc = 0; for (i=0; ififo[(ffp -i) & FIFOMASK] & 0xFF; bite2 = ptr->fifo[(ffp-(CTABLES*2-1)+i) & FIFOMASK] & 0xFF; acc += ctables[i][bite1] + ctables[i][bite2]; } *dst = (float)acc; dst += dst_stride; ffp = (ffp + 1) & FIFOMASK; } ptr->fifopos = ffp; } squeezelite-1.8/dsd2pcm/dsd2pcm.h 0000664 0000000 0000000 00000006472 12463437225 0017003 0 ustar 00root root 0000000 0000000 /* 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. ---- Marked additions (c) Adrian Smith, 2013 under same licence terms */ #ifndef DSD2PCM_H_INCLUDED #define DSD2PCM_H_INCLUDED #include #include #ifdef __cplusplus extern "C" { #endif struct dsd2pcm_ctx_s; typedef struct dsd2pcm_ctx_s dsd2pcm_ctx; /** * initializes a "dsd2pcm engine" for one channel * (precomputes tables and allocates memory) * * This is the only function that is not thread-safe in terms of the * POSIX thread-safety definition because it modifies global state * (lookup tables are computed during the first call) */ extern dsd2pcm_ctx* dsd2pcm_init(void); /** * deinitializes a "dsd2pcm engine" * (releases memory, don't forget!) */ extern void dsd2pcm_destroy(dsd2pcm_ctx *ctx); /** * clones the context and returns a pointer to the * newly allocated copy */ extern dsd2pcm_ctx* dsd2pcm_clone(dsd2pcm_ctx *ctx); /** * resets the internal state for a fresh new stream */ extern void dsd2pcm_reset(dsd2pcm_ctx *ctx); /** * "translates" a stream of octets to a stream of floats * (8:1 decimation) * @param ctx -- pointer to abstract context (buffers) * @param samples -- number of octets/samples to "translate" * @param src -- pointer to first octet (input) * @param src_stride -- src pointer increment * @param lsbitfirst -- bitorder, 0=msb first, 1=lsbfirst * @param dst -- pointer to first float (output) * @param dst_stride -- dst pointer increment */ extern void dsd2pcm_translate(dsd2pcm_ctx *ctx, size_t samples, const unsigned char *src, ptrdiff_t src_stride, int lsbitfirst, float *dst, ptrdiff_t dst_stride); /** * Additions by Adrian Smith (c) 2013 for Squeezelite */ extern unsigned char dsd2pcm_bitreverse[]; extern void dsd2pcm_precalc(void); /** * End of addition */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* include guard DSD2PCM_H_INCLUDED */ squeezelite-1.8/faad.c 0000664 0000000 0000000 00000040304 12463437225 0014771 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #include #define WRAPBUF_LEN 2048 struct chunk_table { u32_t sample, offset; }; struct faad { NeAACDecHandle hAac; u8_t type; // following used for mp4 only u32_t consume; u32_t pos; u32_t sample; u32_t nextchunk; void *stsc; u32_t skip; u64_t samples; u64_t sttssamples; bool empty; struct chunk_table *chunkinfo; // faad symbols to be dynamically loaded #if !LINKALL NeAACDecConfigurationPtr (* NeAACDecGetCurrentConfiguration)(NeAACDecHandle); unsigned char (* NeAACDecSetConfiguration)(NeAACDecHandle, NeAACDecConfigurationPtr); NeAACDecHandle (* NeAACDecOpen)(void); void (* NeAACDecClose)(NeAACDecHandle); long (* NeAACDecInit)(NeAACDecHandle, unsigned char *, unsigned long, unsigned long *, unsigned char *); char (* NeAACDecInit2)(NeAACDecHandle, unsigned char *pBuffer, unsigned long, unsigned long *, unsigned char *); void *(* NeAACDecDecode)(NeAACDecHandle, NeAACDecFrameInfo *, unsigned char *, unsigned long); char *(* NeAACDecGetErrorMessage)(unsigned char); #endif }; static struct faad *a; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define 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 IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define NEAAC(h, fn, ...) (NeAACDec ## fn)(__VA_ARGS__) #else #define NEAAC(h, fn, ...) (h)->NeAACDec##fn(__VA_ARGS__) #endif // minimal code for mp4 file parsing to extract audio config and find media data // adapted from faad2/common/mp4ff u32_t mp4_desc_length(u8_t **buf) { u8_t b; u8_t num_bytes = 0; u32_t length = 0; do { b = **buf; *buf += 1; num_bytes++; length = (length << 7) | (b & 0x7f); } while ((b & 0x80) && num_bytes < 4); return length; } // read mp4 header to extract config data static int read_mp4_header(unsigned long *samplerate_p, unsigned char *channels_p) { size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); char type[5]; u32_t len; while (bytes >= 8) { // count trak to find the first playable one static unsigned trak, play; u32_t consume; len = unpackN((u32_t *)streambuf->readp); memcpy(type, streambuf->readp + 4, 4); type[4] = '\0'; if (!strcmp(type, "moov")) { trak = 0; play = 0; } if (!strcmp(type, "trak")) { trak++; } // extract audio config from within esds and pass to DecInit2 if (!strcmp(type, "esds") && bytes > len) { unsigned config_len; u8_t *ptr = streambuf->readp + 12; if (*ptr++ == 0x03) { mp4_desc_length(&ptr); ptr += 4; } else { ptr += 3; } mp4_desc_length(&ptr); ptr += 13; if (*ptr++ != 0x05) { LOG_WARN("error parsing esds"); return -1; } config_len = mp4_desc_length(&ptr); if (NEAAC(a, Init2, a->hAac, ptr, config_len, samplerate_p, channels_p) == 0) { LOG_DEBUG("playable aac track: %u", trak); play = trak; } } // extract the total number of samples from stts if (!strcmp(type, "stts") && bytes > len) { u32_t i; u8_t *ptr = streambuf->readp + 12; u32_t entries = unpackN((u32_t *)ptr); ptr += 4; for (i = 0; i < entries; ++i) { u32_t count = unpackN((u32_t *)ptr); u32_t size = unpackN((u32_t *)(ptr + 4)); a->sttssamples += count * size; ptr += 8; } LOG_DEBUG("total number of samples contained in stts: " FMT_u64, a->sttssamples); } // stash sample to chunk info, assume it comes before stco if (!strcmp(type, "stsc") && bytes > len && !a->chunkinfo) { a->stsc = malloc(len - 12); if (a->stsc == NULL) { LOG_WARN("malloc fail"); return -1; } memcpy(a->stsc, streambuf->readp + 12, len - 12); } // build offsets table from stco and stored stsc if (!strcmp(type, "stco") && bytes > len && play == trak) { u32_t i; // extract chunk offsets u8_t *ptr = streambuf->readp + 12; u32_t entries = unpackN((u32_t *)ptr); ptr += 4; a->chunkinfo = malloc(sizeof(struct chunk_table) * (entries + 1)); if (a->chunkinfo == NULL) { LOG_WARN("malloc fail"); return -1; } for (i = 0; i < entries; ++i) { a->chunkinfo[i].offset = unpackN((u32_t *)ptr); a->chunkinfo[i].sample = 0; ptr += 4; } a->chunkinfo[i].sample = 0; a->chunkinfo[i].offset = 0; // fill in first sample id for each chunk from stored stsc if (a->stsc) { u32_t stsc_entries = unpackN((u32_t *)a->stsc); u32_t sample = 0; u32_t last = 0, last_samples = 0; u8_t *ptr = (u8_t *)a->stsc + 4; while (stsc_entries--) { u32_t first = unpackN((u32_t *)ptr); u32_t samples = unpackN((u32_t *)(ptr + 4)); if (last) { for (i = last - 1; i < first - 1; ++i) { a->chunkinfo[i].sample = sample; sample += last_samples; } } if (stsc_entries == 0) { for (i = first - 1; i < entries; ++i) { a->chunkinfo[i].sample = sample; sample += samples; } } last = first; last_samples = samples; ptr += 12; } free(a->stsc); a->stsc = NULL; } } // found media data, advance to start of first chunk and return if (!strcmp(type, "mdat")) { _buf_inc_readp(streambuf, 8); a->pos += 8; bytes -= 8; if (play) { LOG_DEBUG("type: mdat len: %u pos: %u", len, a->pos); if (a->chunkinfo && a->chunkinfo[0].offset > a->pos) { u32_t skip = a->chunkinfo[0].offset - a->pos; LOG_DEBUG("skipping: %u", skip); if (skip <= bytes) { _buf_inc_readp(streambuf, skip); a->pos += skip; } else { a->consume = skip; } } a->sample = a->nextchunk = 1; return 1; } else { LOG_DEBUG("type: mdat len: %u, no playable track found", len); return -1; } } // parse key-value atoms within ilst ---- entries to get encoder padding within iTunSMPB entry for gapless if (!strcmp(type, "----") && bytes > len) { u8_t *ptr = streambuf->readp + 8; u32_t remain = len - 8, size; if (!memcmp(ptr + 4, "mean", 4) && (size = unpackN((u32_t *)ptr)) < remain) { ptr += size; remain -= size; } if (!memcmp(ptr + 4, "name", 4) && (size = unpackN((u32_t *)ptr)) < remain && !memcmp(ptr + 12, "iTunSMPB", 8)) { ptr += size; remain -= size; } if (!memcmp(ptr + 4, "data", 4) && remain > 16 + 48) { // data is stored as hex strings: 0 start end samples u32_t b, c; u64_t d; if (sscanf((const char *)(ptr + 16), "%x %x %x " FMT_x64, &b, &b, &c, &d) == 4) { LOG_DEBUG("iTunSMPB start: %u end: %u samples: " FMT_u64, b, c, d); if (a->sttssamples && a->sttssamples < b + c + d) { LOG_DEBUG("reducing samples as stts count is less"); d = a->sttssamples - (b + c); } a->skip = b; a->samples = d; } } } // default to consuming entire box consume = len; // read into these boxes so reduce consume if (!strcmp(type, "moov") || !strcmp(type, "trak") || !strcmp(type, "mdia") || !strcmp(type, "minf") || !strcmp(type, "stbl") || !strcmp(type, "udta") || !strcmp(type, "ilst")) { consume = 8; } // special cases which mix mix data in the enclosing box which we want to read into if (!strcmp(type, "stsd")) consume = 16; if (!strcmp(type, "mp4a")) consume = 36; if (!strcmp(type, "meta")) consume = 12; // consume rest of box if it has been parsed (all in the buffer) or is not one we want to parse if (bytes >= consume) { LOG_DEBUG("type: %s len: %u consume: %u", type, len, consume); _buf_inc_readp(streambuf, consume); a->pos += consume; bytes -= consume; } else if ( !(!strcmp(type, "esds") || !strcmp(type, "stts") || !strcmp(type, "stsc") || !strcmp(type, "stco") || !strcmp(type, "----")) ) { LOG_DEBUG("type: %s len: %u consume: %u - partial consume: %u", type, len, consume, bytes); _buf_inc_readp(streambuf, bytes); a->pos += bytes; a->consume = consume - bytes; break; } else { break; } } return 0; } static decode_state faad_decode(void) { size_t bytes_total; size_t bytes_wrap; NeAACDecFrameInfo info; s32_t *iptr; bool endstream; frames_t frames; LOCK_S; bytes_total = _buf_used(streambuf); bytes_wrap = min(bytes_total, _buf_cont_read(streambuf)); if (stream.state <= DISCONNECT && !bytes_total) { UNLOCK_S; return DECODE_COMPLETE; } if (a->consume) { u32_t consume = min(a->consume, bytes_wrap); LOG_DEBUG("consume: %u of %u", consume, a->consume); _buf_inc_readp(streambuf, consume); a->pos += consume; a->consume -= consume; UNLOCK_S; return DECODE_RUNNING; } if (decode.new_stream) { int found = 0; static unsigned char channels; static unsigned long samplerate; if (a->type == '2') { // adts stream - seek for header while (bytes_wrap >= 2 && (*(streambuf->readp) != 0xFF || (*(streambuf->readp + 1) & 0xF6) != 0xF0)) { _buf_inc_readp(streambuf, 1); bytes_total--; bytes_wrap--; } if (bytes_wrap >= 2) { long n = NEAAC(a, Init, a->hAac, streambuf->readp, bytes_wrap, &samplerate, &channels); if (n < 0) { found = -1; } else { _buf_inc_readp(streambuf, n); found = 1; } } } else { // mp4 - read header found = read_mp4_header(&samplerate, &channels); } if (found == 1) { LOG_INFO("samplerate: %u channels: %u", samplerate, channels); bytes_total = _buf_used(streambuf); bytes_wrap = min(bytes_total, _buf_cont_read(streambuf)); LOCK_O; LOG_INFO("setting track_start"); output.next_sample_rate = decode_newstream(samplerate, output.supported_rates); IF_DSD( output.next_dop = false; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O; } else if (found == -1) { LOG_WARN("error reading stream header"); UNLOCK_S; return DECODE_ERROR; } else { // not finished header parsing come back next time UNLOCK_S; return DECODE_RUNNING; } } if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) { // make a local copy of frames which may have wrapped round the end of streambuf u8_t buf[WRAPBUF_LEN]; memcpy(buf, streambuf->readp, bytes_wrap); memcpy(buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap); iptr = NEAAC(a, Decode, a->hAac, &info, buf, WRAPBUF_LEN); } else { iptr = NEAAC(a, Decode, a->hAac, &info, streambuf->readp, bytes_wrap); } if (info.error) { LOG_WARN("error: %u %s", info.error, NEAAC(a, GetErrorMessage, info.error)); } endstream = false; // mp4 end of chunk - skip to next offset if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) { if (a->chunkinfo[a->nextchunk].offset > a->pos) { u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos; if (skip != info.bytesconsumed) { LOG_DEBUG("skipping to next chunk pos: %u consumed: %u != skip: %u", a->pos, info.bytesconsumed, skip); } if (bytes_total >= skip) { _buf_inc_readp(streambuf, skip); a->pos += skip; } else { a->consume = skip; } a->nextchunk++; } else { LOG_ERROR("error: need to skip backwards!"); endstream = true; } // adts and mp4 when not at end of chunk } else if (info.bytesconsumed != 0) { _buf_inc_readp(streambuf, info.bytesconsumed); a->pos += info.bytesconsumed; // error which doesn't advance streambuf - end } else { endstream = true; } UNLOCK_S; if (endstream) { LOG_WARN("unable to decode further"); return DECODE_ERROR; } if (!info.samples) { a->empty = true; return DECODE_RUNNING; } frames = info.samples / info.channels; if (a->skip) { u32_t skip; if (a->empty) { a->empty = false; a->skip -= frames; LOG_DEBUG("gapless: first frame empty, skipped %u frames at start", frames); } skip = min(frames, a->skip); LOG_DEBUG("gapless: skipping %u frames at start", skip); frames -= skip; a->skip -= skip; iptr += skip * info.channels; } if (a->samples) { if (a->samples < frames) { LOG_DEBUG("gapless: trimming %u frames from end", frames - a->samples); frames = (frames_t)a->samples; } a->samples -= frames; } LOG_SDEBUG("write %u frames", frames); LOCK_O_direct; while (frames > 0) { frames_t f; frames_t count; s32_t *optr; IF_DIRECT( f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME; optr = (s32_t *)outputbuf->writep; ); IF_PROCESS( f = process.max_in_frames; optr = (s32_t *)process.inbuf; ); f = min(f, frames); count = f; if (info.channels == 2) { while (count--) { *optr++ = *iptr++ << 8; *optr++ = *iptr++ << 8; } } else if (info.channels == 1) { while (count--) { *optr++ = *iptr << 8; *optr++ = *iptr++ << 8; } } else { LOG_WARN("unsupported number of channels"); } frames -= f; IF_DIRECT( _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames = f; if (frames) LOG_ERROR("unhandled case"); ); } UNLOCK_O_direct; return DECODE_RUNNING; } static void faad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { NeAACDecConfigurationPtr conf; LOG_INFO("opening %s stream", size == '2' ? "adts" : "mp4"); a->type = size; a->pos = a->consume = a->sample = a->nextchunk = 0; if (a->chunkinfo) { free(a->chunkinfo); } if (a->stsc) { free(a->stsc); } a->chunkinfo = NULL; a->stsc = NULL; a->skip = 0; a->samples = 0; a->sttssamples = 0; a->empty = false; if (a->hAac) { NEAAC(a, Close, a->hAac); } a->hAac = NEAAC(a, Open); conf = NEAAC(a, GetCurrentConfiguration, a->hAac); conf->outputFormat = FAAD_FMT_24BIT; conf->downMatrix = 1; if (!NEAAC(a, SetConfiguration, a->hAac, conf)) { LOG_WARN("error setting config"); }; } static void faad_close(void) { NEAAC(a, Close, a->hAac); a->hAac = NULL; if (a->chunkinfo) { free(a->chunkinfo); a->chunkinfo = NULL; } if (a->stsc) { free(a->stsc); a->stsc = NULL; } } static bool load_faad() { #if !LINKALL void *handle = dlopen(LIBFAAD, RTLD_NOW); char *err; if (!handle) { LOG_INFO("dlerror: %s", dlerror()); return false; } a->NeAACDecGetCurrentConfiguration = dlsym(handle, "NeAACDecGetCurrentConfiguration"); a->NeAACDecSetConfiguration = dlsym(handle, "NeAACDecSetConfiguration"); a->NeAACDecOpen = dlsym(handle, "NeAACDecOpen"); a->NeAACDecClose = dlsym(handle, "NeAACDecClose"); a->NeAACDecInit = dlsym(handle, "NeAACDecInit"); a->NeAACDecInit2 = dlsym(handle, "NeAACDecInit2"); a->NeAACDecDecode = dlsym(handle, "NeAACDecDecode"); a->NeAACDecGetErrorMessage = dlsym(handle, "NeAACDecGetErrorMessage"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBFAAD""); #endif return true; } struct codec *register_faad(void) { static struct codec ret = { 'a', // id "aac", // types WRAPBUF_LEN, // min read 20480, // min space faad_open, // open faad_close, // close faad_decode, // decode }; a = malloc(sizeof(struct faad)); if (!a) { return NULL; } a->hAac = NULL; a->chunkinfo = NULL; a->stsc = NULL; if (!load_faad()) { return NULL; } LOG_INFO("using faad to decode aac"); return &ret; } squeezelite-1.8/ffmpeg.c 0000664 0000000 0000000 00000052312 12463437225 0015344 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #if FFMPEG #include #include #define READ_SIZE 4096 * 4 // this is large enough to ensure ffmpeg always gets new data when decode is called #define WRITE_SIZE 256 * 1024 // FIXME - make smaller, but still to absorb max wma output // FIXME - do we need to align these params as per ffmpeg on i386? #define attribute_align_arg struct ff_s { // state for ffmpeg decoder bool wma; u8_t wma_mmsh; u8_t wma_playstream; u8_t wma_metadatastream; u8_t *readbuf; bool end_of_stream; AVInputFormat *input_format; AVFormatContext *formatC; AVCodecContext *codecC; AVFrame *frame; AVPacket *avpkt; unsigned mmsh_bytes_left; unsigned mmsh_bytes_pad; unsigned mmsh_packet_len; #if !LINKALL // ffmpeg symbols to be dynamically loaded from libavcodec unsigned (* avcodec_version)(void); AVCodec * (* avcodec_find_decoder)(int); int attribute_align_arg (* avcodec_open2)(AVCodecContext *, const AVCodec *, AVDictionary **); AVFrame * (* avcodec_alloc_frame)(void); void (* avcodec_free_frame)(AVFrame **); int attribute_align_arg (* avcodec_decode_audio4)(AVCodecContext *, AVFrame *, int *, const AVPacket *); // ffmpeg symbols to be dynamically loaded from libavformat unsigned (* avformat_version)(void); AVFormatContext * (* avformat_alloc_context)(void); void (* avformat_free_context)(AVFormatContext *); int (* avformat_open_input)(AVFormatContext **, const char *, AVInputFormat *, AVDictionary **); int (* avformat_find_stream_info)(AVFormatContext *, AVDictionary **); AVIOContext * (* avio_alloc_context)(unsigned char *, int, int, void *, int (*read_packet)(void *, uint8_t *, int), int (*write_packet)(void *, uint8_t *, int), int64_t (*seek)(void *, int64_t, int)); void (* av_init_packet)(AVPacket *); void (* av_free_packet)(AVPacket *); int (* av_read_frame)(AVFormatContext *, AVPacket *); AVInputFormat * (* av_find_input_format)(const char *); void (* av_register_all)(void); // ffmpeg symbols to be dynamically loaded from libavutil unsigned (* avutil_version)(void); void (* av_log_set_callback)(void (*)(void*, int, const char*, va_list)); void (* av_log_set_level)(int); int (* av_strerror)(int, char *, size_t); void * (* av_malloc)(size_t); void (* av_freep)(void *); #endif }; static struct ff_s *ff; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define 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 IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define AV(h, fn, ...) (av_ ## fn)(__VA_ARGS__) #define AVIO(h, fn, ...) (avio_ ## fn)(__VA_ARGS__) #define AVCODEC(h, fn, ...) (avcodec_ ## fn)(__VA_ARGS__) #define AVFORMAT(h, fn, ...) (avformat_ ## fn)(__VA_ARGS__) #else #define AV(h, fn, ...) (h)->av_##fn(__VA_ARGS__) #define AVIO(h, fn, ...) (h)->avio_##fn(__VA_ARGS__) #define AVCODEC(h, fn, ...) (h)->avcodec_##fn(__VA_ARGS__) #define AVFORMAT(h, fn, ...) (h)->avformat_##fn(__VA_ARGS__) #endif // our own version of useful error function not included in earlier ffmpeg versions static char *av__err2str(errnum) { static char buf[64]; AV(ff, strerror, errnum, buf, 64); return buf; } // parser to extract asf data packet length from asf header const u8_t header_guid[16] = { 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }; const u8_t file_props_guid[16] = { 0xA1, 0xDC, 0xAB, 0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 }; static int _parse_packlen(void) { int bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); u8_t *ptr = streambuf->readp; int remain = 1; while (bytes >= 24 && remain > 0) { u32_t len = *(ptr+16) | *(ptr+17) << 8 | *(ptr+18) << 16 | *(ptr+19) << 24; // assume msb 32 bits are 0 if (!memcmp(ptr, header_guid, 16) && bytes >= 30) { ptr += 30; bytes -= 30; remain = len - 30; continue; } if (!memcmp(ptr, file_props_guid, 16) && len == 104) { u32_t packlen = *(ptr+92) | *(ptr+93) << 8 | *(ptr+94) << 16 | *(ptr+95) << 24; LOG_INFO("asf packet len: %u", packlen); return packlen; } ptr += len; bytes -= len; remain -= len; } LOG_WARN("could not parse packet length"); return 0; } static int _read_data(void *opaque, u8_t *buffer, int buf_size) { size_t bytes; LOCK_S; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); ff->end_of_stream = (stream.state <= DISCONNECT && bytes == 0); bytes = min(bytes, buf_size); // for chunked wma extract asf header and data frames from framing structure // pad asf data frames to size of packet extracted from asf header if (ff->wma_mmsh) { unsigned chunk_type = 0, chunk_len = 0; if (ff->mmsh_bytes_left) { // bytes remaining from previous frame if (bytes >= ff->mmsh_bytes_left) { bytes = ff->mmsh_bytes_left; ff->mmsh_bytes_left = 0; } else { ff->mmsh_bytes_left -= bytes; } } else if (ff->mmsh_bytes_pad) { // add padding for previous frame bytes = min(ff->mmsh_bytes_pad, buf_size); memset(buffer, 0, bytes); ff->mmsh_bytes_pad -= bytes; UNLOCK_S; return bytes; } else if (bytes >= 12) { // new chunk header chunk_type = (*(streambuf->readp) & 0x7f) | *(streambuf->readp + 1) << 8; chunk_len = *(streambuf->readp + 2) | *(streambuf->readp + 3) << 8; _buf_inc_readp(streambuf, 12); bytes -= 12; } else if (_buf_used(streambuf) >= 12) { // new chunk header split over end of streambuf, read in two u8_t header[12]; memcpy(header, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); memcpy(header + bytes, streambuf->readp, 12 - bytes); _buf_inc_readp(streambuf, 12 - bytes); chunk_type = (header[0] & 0x7f) | header[1] << 8; chunk_len = header[2] | header[3] << 8; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, buf_size); } else { // should not get here... LOG_ERROR("chunk parser stalled bytes: %u %u", bytes, _buf_used(streambuf)); UNLOCK_S; return 0; } if (chunk_type && chunk_len) { if (chunk_type == 0x4824) { // asf header - parse packet length ff->mmsh_packet_len = _parse_packlen(); ff->mmsh_bytes_pad = 0; } else if (chunk_type == 0x4424 && ff->mmsh_packet_len) { // asf data packet - add padding ff->mmsh_bytes_pad = ff->mmsh_packet_len - chunk_len + 8; } else { LOG_INFO("unknown chunk: %04x", chunk_type); // other packet - no padding ff->mmsh_bytes_pad = 0; } if (chunk_len - 8 <= bytes) { bytes = chunk_len - 8; ff->mmsh_bytes_left = 0; } else { ff->mmsh_bytes_left = chunk_len - 8 - bytes; } } } memcpy(buffer, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); if (ff->mmsh_bytes_pad && bytes + ff->mmsh_bytes_pad < buf_size) { memset(buffer + bytes, 0, ff->mmsh_bytes_pad); bytes += ff->mmsh_bytes_pad; ff->mmsh_bytes_pad = 0; } UNLOCK_S; return bytes; } static decode_state ff_decode(void) { int r, len, got_frame; AVPacket pkt_c; s32_t *optr = NULL; if (decode.new_stream) { AVIOContext *avio; AVStream *av_stream; AVCodec *codec; int o; int audio_stream = -1; ff->mmsh_bytes_left = ff->mmsh_bytes_pad = ff->mmsh_packet_len = 0; if (!ff->readbuf) { ff->readbuf = AV(ff, malloc, READ_SIZE + FF_INPUT_BUFFER_PADDING_SIZE); } avio = AVIO(ff, alloc_context, ff->readbuf, READ_SIZE, 0, NULL, _read_data, NULL, NULL); avio->seekable = 0; ff->formatC = AVFORMAT(ff, alloc_context); if (ff->formatC == NULL) { LOG_ERROR("null context"); return DECODE_ERROR; } ff->formatC->pb = avio; ff->formatC->flags |= AVFMT_FLAG_CUSTOM_IO | AVFMT_FLAG_NOPARSE; o = AVFORMAT(ff, open_input, &ff->formatC, "", ff->input_format, NULL); if (o < 0) { LOG_WARN("avformat_open_input: %d %s", o, av__err2str(o)); return DECODE_ERROR; } LOG_INFO("format: name:%s lname:%s", ff->formatC->iformat->name, ff->formatC->iformat->long_name); o = AVFORMAT(ff, find_stream_info, ff->formatC, NULL); if (o < 0) { LOG_WARN("avformat_find_stream_info: %d %s", o, av__err2str(o)); return DECODE_ERROR; } if (ff->wma && ff->wma_playstream < ff->formatC->nb_streams) { if (ff->formatC->streams[ff->wma_playstream]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { LOG_INFO("using wma stream sent from server: %i", ff->wma_playstream); audio_stream = ff->wma_playstream; } } if (audio_stream == -1) { int i; for (i = 0; i < ff->formatC->nb_streams; ++i) { if (ff->formatC->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { audio_stream = i; LOG_INFO("found stream: %i", i); break; } } } if (audio_stream == -1) { LOG_WARN("no audio stream found"); return DECODE_ERROR; } av_stream = ff->formatC->streams[audio_stream]; ff->codecC = av_stream->codec; codec = AVCODEC(ff, find_decoder, ff->codecC->codec_id); AVCODEC(ff, open2, ff->codecC, codec, NULL); ff->frame = AVCODEC(ff, alloc_frame); ff->avpkt = AV(ff, malloc, sizeof(AVPacket)); if (ff->avpkt == NULL) { LOG_ERROR("can't allocate avpkt"); return DECODE_ERROR; } AV(ff, init_packet, ff->avpkt); ff->avpkt->data = NULL; ff->avpkt->size = 0; LOCK_O; LOG_INFO("setting track_start"); output.next_sample_rate = decode_newstream(ff->codecC->sample_rate, output.supported_rates); IF_DSD( output.next_dop = false; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O; } got_frame = 0; if ((r = AV(ff, read_frame, ff->formatC, ff->avpkt)) < 0) { if (r == AVERROR_EOF) { if (ff->end_of_stream) { LOG_INFO("decode complete"); return DECODE_COMPLETE; } else { LOG_INFO("codec end of file"); } } else { LOG_ERROR("av_read_frame error: %i %s", r, av__err2str(r)); } return DECODE_RUNNING; } // clone packet as we are adjusting it pkt_c = *ff->avpkt; IF_PROCESS( optr = (s32_t *)process.inbuf; process.in_frames = 0; ); while (pkt_c.size > 0 || got_frame) { len = AVCODEC(ff, decode_audio4, ff->codecC, ff->frame, &got_frame, &pkt_c); if (len < 0) { LOG_ERROR("avcodec_decode_audio4 error: %i %s", len, av__err2str(len)); return DECODE_RUNNING; } pkt_c.data += len; pkt_c.size -= len; if (got_frame) { s16_t *iptr16 = (s16_t *)ff->frame->data[0]; s32_t *iptr32 = (s32_t *)ff->frame->data[0]; s16_t *iptr16l = (s16_t *)ff->frame->data[0]; s16_t *iptr16r = (s16_t *)ff->frame->data[1]; s32_t *iptr32l = (s32_t *)ff->frame->data[0]; s32_t *iptr32r = (s32_t *)ff->frame->data[1]; float *iptrfl = (float *)ff->frame->data[0]; float *iptrfr = (float *)ff->frame->data[1]; frames_t frames = ff->frame->nb_samples; LOG_SDEBUG("got audio channels: %u samples: %u format: %u", ff->codecC->channels, ff->frame->nb_samples, ff->codecC->sample_fmt); LOCK_O_direct; while (frames > 0) { frames_t count; frames_t f; IF_DIRECT( optr = (s32_t *)outputbuf->writep; f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; f = min(f, frames); ); IF_PROCESS( if (process.in_frames + frames > process.max_in_frames) { LOG_WARN("exceeded process buffer size - dropping frames"); break; } f = frames; ); count = f; if (ff->codecC->channels == 2) { if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S16) { while (count--) { *optr++ = *iptr16++ << 16; *optr++ = *iptr16++ << 16; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S32) { while (count--) { *optr++ = *iptr32++; *optr++ = *iptr32++; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S16P) { while (count--) { *optr++ = *iptr16l++ << 16; *optr++ = *iptr16r++ << 16; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S32P) { while (count--) { *optr++ = *iptr32l++; *optr++ = *iptr32r++; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_FLTP) { while (count--) { double scaledl = *iptrfl++ * 0x7fffffff; double scaledr = *iptrfr++ * 0x7fffffff; if (scaledl > 2147483647.0) scaledl = 2147483647.0; if (scaledl < -2147483648.0) scaledl = -2147483648.0; if (scaledr > 2147483647.0) scaledr = 2147483647.0; if (scaledr < -2147483648.0) scaledr = -2147483648.0; *optr++ = (s32_t)scaledl; *optr++ = (s32_t)scaledr; } } else { LOG_WARN("unsupported sample format: %u", ff->codecC->sample_fmt); } } else if (ff->codecC->channels == 1) { if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S16) { while (count--) { *optr++ = *iptr16 << 16; *optr++ = *iptr16++ << 16; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S32) { while (count--) { *optr++ = *iptr32; *optr++ = *iptr32++; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S16P) { while (count--) { *optr++ = *iptr16l << 16; *optr++ = *iptr16l++ << 16; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_S32P) { while (count--) { *optr++ = *iptr32l; *optr++ = *iptr32l++; } } else if (ff->codecC->sample_fmt == AV_SAMPLE_FMT_FLTP) { while (count--) { double scaled = *iptrfl++ * 0x7fffffff; if (scaled > 2147483647.0) scaled = 2147483647.0; if (scaled < -2147483648.0) scaled = -2147483648.0; *optr++ = (s32_t)scaled; *optr++ = (s32_t)scaled; } } else { LOG_WARN("unsupported sample format: %u", ff->codecC->sample_fmt); } } else { LOG_WARN("unsupported number of channels"); } frames -= f; IF_DIRECT( _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames += f; ); } UNLOCK_O_direct; } } AV(ff, free_packet, ff->avpkt); return DECODE_RUNNING; } static void _free_ff_data(void) { if (ff->formatC) { if (ff->formatC->pb) AV(ff, freep, &ff->formatC->pb); AVFORMAT(ff, free_context, ff->formatC); ff->formatC = NULL; } if (ff->frame) { // ffmpeg version dependant free function #if !LINKALL ff->avcodec_free_frame ? AVCODEC(ff, free_frame, &ff->frame) : AV(ff, freep, &ff->frame); #elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54,28,0) AVCODEC(ff, free_frame, &ff->frame); #else AV(ff, freep, &ff->frame); #endif ff->frame = NULL; } if (ff->avpkt) { AV(ff, free_packet, ff->avpkt); AV(ff, freep, &ff->avpkt); ff->avpkt = NULL; } } static void ff_open_wma(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { _free_ff_data(); ff->input_format = AV(ff, find_input_format, "asf"); if (ff->input_format == NULL) { LOG_ERROR("asf format not supported by ffmpeg library"); } ff->wma = true; ff->wma_mmsh = size - '0'; ff->wma_playstream = rate - 1; ff->wma_metadatastream = chan != '?' ? chan : 0; LOG_INFO("open wma chunking: %u playstream: %u metadatastream: %u", ff->wma_mmsh, ff->wma_playstream, ff->wma_metadatastream); } static void ff_open_alac(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { _free_ff_data(); ff->input_format = AV(ff, find_input_format, "mp4"); if (ff->input_format == NULL) { LOG_ERROR("mp4 format not supported by ffmpeg library"); } ff->wma = false; ff->wma_mmsh = 0; LOG_INFO("open alac"); } static void ff_close(void) { _free_ff_data(); if (ff->readbuf) { AV(ff, freep, &ff->readbuf); ff->readbuf = NULL; } } static bool load_ff() { #if !LINKALL void *handle_codec = NULL, *handle_format = NULL, *handle_util = NULL; char name[30]; char *err; // we try to load the ffmpeg library version which matches the header file we are compiled with as structs differ between versions sprintf(name, LIBAVCODEC, LIBAVCODEC_VERSION_MAJOR); handle_codec = dlopen(name, RTLD_NOW); if (!handle_codec) { LOG_INFO("dlerror: %s", dlerror()); return false; } sprintf(name, LIBAVFORMAT, LIBAVFORMAT_VERSION_MAJOR); handle_format = dlopen(name, RTLD_NOW); if (!handle_format) { LOG_INFO("dlerror: %s", dlerror()); return false; } sprintf(name, LIBAVUTIL, LIBAVUTIL_VERSION_MAJOR); handle_util = dlopen(name, RTLD_NOW); if (!handle_util) { LOG_INFO("dlerror: %s", dlerror()); return false; } ff->avcodec_version = dlsym(handle_codec, "avcodec_version"); ff->avcodec_find_decoder = dlsym(handle_codec, "avcodec_find_decoder"); ff->avcodec_open2 = dlsym(handle_codec, "avcodec_open2"); 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"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } 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->av_register_all = dlsym(handle_format, "av_register_all"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBAVFORMAT" (%u.%u.%u)", LIBAVFORMAT_VERSION_MAJOR, ff->avformat_version() >> 16, (ff->avformat_version() >> 8) & 0xff, ff->avformat_version() & 0xff); ff->avutil_version = dlsym(handle_util, "avutil_version"); ff->av_log_set_callback = dlsym(handle_util, "av_log_set_callback"); ff->av_log_set_level = dlsym(handle_util, "av_log_set_level"); ff->av_strerror = dlsym(handle_util, "av_strerror"); ff->av_malloc = dlsym(handle_util, "av_malloc"); ff->av_freep = dlsym(handle_util, "av_freep"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBAVUTIL" (%u.%u.%u)", LIBAVUTIL_VERSION_MAJOR, ff->avutil_version() >> 16, (ff->avutil_version() >> 8) & 0xff, ff->avutil_version() & 0xff); #endif return true; } static int ff_log_level = 0; void av_err_callback(void *avcl, int level, const char *fmt, va_list vl) { if (level > ff_log_level) return; fprintf(stderr, "%s ffmpeg: ", logtime()); vfprintf(stderr, fmt, vl); fflush(stderr); } static bool registered = false; struct codec *register_ff(const char *codec) { if (!registered) { ff = malloc(sizeof(struct ff_s)); if (!ff) { return NULL; } memset(ff, 0, sizeof(struct ff_s)); if (!load_ff()) { return NULL; } switch (loglevel) { case lERROR: ff_log_level = AV_LOG_ERROR; break; case lWARN: ff_log_level = AV_LOG_WARNING; break; case lINFO: ff_log_level = AV_LOG_INFO; break; case lDEBUG: ff_log_level = AV_LOG_VERBOSE; break; default: break; } AV(ff, log_set_callback, av_err_callback); AV(ff, register_all); registered = true; } if (!strcmp(codec, "wma")) { static struct codec ret = { 'w', // id "wma,wmap,wmal", // types READ_SIZE, // min read WRITE_SIZE, // min space ff_open_wma, // open ff_close, // close ff_decode, // decode }; LOG_INFO("using ffmpeg to decode wma,wmap,wmal"); return &ret; } if (!strcmp(codec, "alc")) { static struct codec ret = { 'l', // id "alc", // types READ_SIZE, // min read WRITE_SIZE, // min space ff_open_alac,// open ff_close, // close ff_decode, // decode }; LOG_INFO("using ffmpeg to decode alc"); return &ret; } return NULL; } #endif squeezelite-1.8/flac.c 0000664 0000000 0000000 00000020513 12463437225 0015003 0 ustar 00root root 0000000 0000000 /* * Squeezelite - lightweight headless squeezeplay emulator for linux * * (c) Adrian Smith 2012, 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 . * */ #include "squeezelite.h" #include struct flac { FLAC__StreamDecoder *decoder; #if !LINKALL // FLAC symbols to be dynamically loaded const char **FLAC__StreamDecoderErrorStatusString; const char **FLAC__StreamDecoderStateString; FLAC__StreamDecoder * (* FLAC__stream_decoder_new)(void); FLAC__bool (* FLAC__stream_decoder_reset)(FLAC__StreamDecoder *decoder); void (* FLAC__stream_decoder_delete)(FLAC__StreamDecoder *decoder); FLAC__StreamDecoderInitStatus (* FLAC__stream_decoder_init_stream)( FLAC__StreamDecoder *decoder, FLAC__StreamDecoderReadCallback read_callback, FLAC__StreamDecoderSeekCallback seek_callback, FLAC__StreamDecoderTellCallback tell_callback, FLAC__StreamDecoderLengthCallback length_callback, FLAC__StreamDecoderEofCallback eof_callback, FLAC__StreamDecoderWriteCallback write_callback, FLAC__StreamDecoderMetadataCallback metadata_callback, FLAC__StreamDecoderErrorCallback error_callback, void *client_data ); FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder); FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder); #endif }; static struct flac *f; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define 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 IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define FLAC(h, fn, ...) (FLAC__ ## fn)(__VA_ARGS__) #define FLAC_A(h, a) (FLAC__ ## a) #else #define FLAC(h, fn, ...) (h)->FLAC__##fn(__VA_ARGS__) #define FLAC_A(h, a) (h)->FLAC__ ## a #endif static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *want, void *client_data) { size_t bytes; bool end; LOCK_S; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, *want); end = (stream.state <= DISCONNECT && bytes == 0); memcpy(buffer, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); UNLOCK_S; *want = bytes; return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) { size_t frames = frame->header.blocksize; unsigned bits_per_sample = frame->header.bits_per_sample; unsigned channels = frame->header.channels; FLAC__int32 *lptr = (FLAC__int32 *)buffer[0]; FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0]; if (decode.new_stream) { LOCK_O; LOG_INFO("setting track_start"); output.track_start = outputbuf->writep; decode.new_stream = false; #if DSD if (output.has_dop && bits_per_sample == 24 && is_flac_dop((u32_t *)lptr, (u32_t *)rptr, frames)) { LOG_INFO("file contains DOP"); output.next_dop = true; output.next_sample_rate = frame->header.sample_rate; output.fade = FADE_INACTIVE; } else { output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates); output.next_dop = false; if (output.fade_mode) _checkfade(true); } #else output.next_sample_rate = decode_newstream(frame->header.sample_rate, output.supported_rates); if (output.fade_mode) _checkfade(true); #endif UNLOCK_O; } LOCK_O_direct; while (frames > 0) { frames_t f; frames_t count; s32_t *optr; IF_DIRECT( optr = (s32_t *)outputbuf->writep; f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; ); IF_PROCESS( optr = (s32_t *)process.inbuf; f = process.max_in_frames; ); f = min(f, frames); count = f; if (bits_per_sample == 8) { while (count--) { *optr++ = *lptr++ << 24; *optr++ = *rptr++ << 24; } } else if (bits_per_sample == 16) { while (count--) { *optr++ = *lptr++ << 16; *optr++ = *rptr++ << 16; } } else if (bits_per_sample == 24) { while (count--) { *optr++ = *lptr++ << 8; *optr++ = *rptr++ << 8; } } else if (bits_per_sample == 32) { while (count--) { *optr++ = *lptr++; *optr++ = *rptr++; } } else { LOG_ERROR("unsupported bits per sample: %u", bits_per_sample); } frames -= f; IF_DIRECT( _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames = f; if (frames) LOG_ERROR("unhandled case"); ); } UNLOCK_O_direct; return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } static void error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) { LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderErrorStatusString)[status]); } static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t endianness) { if (f->decoder) { FLAC(f, stream_decoder_reset, f->decoder); } else { f->decoder = FLAC(f, stream_decoder_new); } FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL); } static void flac_close(void) { FLAC(f, stream_decoder_delete, f->decoder); f->decoder = NULL; } static decode_state flac_decode(void) { bool ok = FLAC(f, stream_decoder_process_single, f->decoder); FLAC__StreamDecoderState state = FLAC(f, stream_decoder_get_state, f->decoder); if (!ok && state != FLAC__STREAM_DECODER_END_OF_STREAM) { LOG_INFO("flac error: %s", FLAC_A(f, StreamDecoderStateString)[state]); }; if (state == FLAC__STREAM_DECODER_END_OF_STREAM) { return DECODE_COMPLETE; } else if (state > FLAC__STREAM_DECODER_END_OF_STREAM) { return DECODE_ERROR; } else { return DECODE_RUNNING; } } static bool load_flac() { #if !LINKALL void *handle = dlopen(LIBFLAC, RTLD_NOW); char *err; if (!handle) { LOG_INFO("dlerror: %s", dlerror()); return false; } f->FLAC__StreamDecoderErrorStatusString = dlsym(handle, "FLAC__StreamDecoderErrorStatusString"); f->FLAC__StreamDecoderStateString = dlsym(handle, "FLAC__StreamDecoderStateString"); f->FLAC__stream_decoder_new = dlsym(handle, "FLAC__stream_decoder_new"); f->FLAC__stream_decoder_reset = dlsym(handle, "FLAC__stream_decoder_reset"); f->FLAC__stream_decoder_delete = dlsym(handle, "FLAC__stream_decoder_delete"); f->FLAC__stream_decoder_init_stream = dlsym(handle, "FLAC__stream_decoder_init_stream"); f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single"); f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBFLAC); #endif return true; } struct codec *register_flac(void) { static struct codec ret = { 'f', // id "flc", // types 8192, // min read 102400, // min space flac_open, // open flac_close, // close flac_decode, // decode }; f = malloc(sizeof(struct flac)); if (!f) { return NULL; } f->decoder = NULL; if (!load_flac()) { return NULL; } LOG_INFO("using flac to decode flc"); return &ret; } squeezelite-1.8/ir.c 0000664 0000000 0000000 00000013377 12463437225 0014522 0 ustar 00root root 0000000 0000000 /* * 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 squeezelite-1.8/mad.c 0000664 0000000 0000000 00000026120 12463437225 0014637 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #include #define MAD_DELAY 529 #define READBUF_SIZE 2048 // local buffer used by decoder: FIXME merge with any other decoders needing one? struct mad { u8_t *readbuf; unsigned readbuf_len; struct mad_stream stream; struct mad_frame frame; struct mad_synth synth; enum mad_error last_error; // for lame gapless processing int checktags; u32_t consume; u32_t skip; u64_t samples; u32_t padding; #if !LINKALL // mad symbols to be dynamically loaded void (* mad_stream_init)(struct mad_stream *); void (* mad_frame_init)(struct mad_frame *); void (* mad_synth_init)(struct mad_synth *); void (* mad_frame_finish)(struct mad_frame *); void (* mad_stream_finish)(struct mad_stream *); void (* mad_stream_buffer)(struct mad_stream *, unsigned char const *, unsigned long); int (* mad_frame_decode)(struct mad_frame *, struct mad_stream *); void (* mad_synth_frame)(struct mad_synth *, struct mad_frame const *); char const *(* mad_stream_errorstr)(struct mad_stream const *); #endif }; static struct mad *m; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define 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 IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define MAD(h, fn, ...) (mad_ ## fn)(__VA_ARGS__) #else #define MAD(h, fn, ...) (h)->mad_##fn(__VA_ARGS__) #endif // based on libmad minimad.c scale static inline u32_t scale(mad_fixed_t sample) { sample += (1L << (MAD_F_FRACBITS - 24)); if (sample >= MAD_F_ONE) sample = MAD_F_ONE - 1; else if (sample < -MAD_F_ONE) sample = -MAD_F_ONE; return (s32_t)(sample >> (MAD_F_FRACBITS + 1 - 24)) << 8; } // check for id3.2 tag at start of file - http://id3.org/id3v2.4.0-structure, return length static unsigned _check_id3_tag(size_t bytes) { u8_t *ptr = streambuf->readp; u32_t size = 0; if (bytes > 10 && *ptr == 'I' && *(ptr+1) == 'D' && *(ptr+2) == '3') { // size is encoded as syncsafe integer, add 10 if footer present if (*(ptr+6) < 0x80 && *(ptr+7) < 0x80 && *(ptr+8) < 0x80 && *(ptr+9) < 0x80) { size = 10 + (*(ptr+6) << 21) + (*(ptr+7) << 14) + (*(ptr+8) << 7) + *(ptr+9) + ((*(ptr+5) & 0x10) ? 10 : 0); LOG_DEBUG("id3.2 tag len: %u", size); } } return size; } // check for lame gapless params, don't advance streambuf static void _check_lame_header(size_t bytes) { u8_t *ptr = streambuf->readp; if (*ptr == 0xff && (*(ptr+1) & 0xf0) == 0xf0 && bytes > 180) { u32_t frame_count = 0, enc_delay = 0, enc_padding = 0; u8_t flags; // 2 channels if (!memcmp(ptr + 36, "Xing", 4) || !memcmp(ptr + 36, "Info", 4)) { ptr += 36 + 7; // mono } else if (!memcmp(ptr + 21, "Xing", 4) || !memcmp(ptr + 21, "Info", 4)) { ptr += 21 + 7; } flags = *ptr; if (flags & 0x01) { frame_count = unpackN((u32_t *)(ptr + 1)); ptr += 4; } if (flags & 0x02) ptr += 4; if (flags & 0x04) ptr += 100; if (flags & 0x08) ptr += 4; if (!!memcmp(ptr+1, "LAME", 4)) { return; } ptr += 22; enc_delay = (*ptr << 4 | *(ptr + 1) >> 4) + MAD_DELAY; enc_padding = (*(ptr + 1) & 0xF) << 8 | *(ptr + 2); enc_padding = enc_padding > MAD_DELAY ? enc_padding - MAD_DELAY : 0; // add one frame to initial skip for this (empty) frame m->skip = enc_delay + 1152; m->samples = frame_count * 1152 - enc_delay - enc_padding; m->padding = enc_padding; LOG_INFO("gapless: skip: %u samples: " FMT_u64 " delay: %u padding: %u", m->skip, m->samples, enc_delay, enc_padding); } } static decode_state mad_decode(void) { size_t bytes; bool eos = false; LOCK_S; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); if (m->checktags) { if (m->checktags == 1) { m->consume = _check_id3_tag(bytes); m->checktags = 2; } if (m->consume) { u32_t consume = min(m->consume, bytes); LOG_DEBUG("consume: %u of %u", consume, m->consume); _buf_inc_readp(streambuf, consume); m->consume -= consume; UNLOCK_S; return DECODE_RUNNING; } if (m->checktags == 2) { if (!stream.meta_interval) { _check_lame_header(bytes); } m->checktags = 0; } } if (m->stream.next_frame && m->readbuf_len) { m->readbuf_len -= m->stream.next_frame - m->readbuf; memmove(m->readbuf, m->stream.next_frame, m->readbuf_len); } bytes = min(bytes, READBUF_SIZE - m->readbuf_len); memcpy(m->readbuf + m->readbuf_len, streambuf->readp, bytes); m->readbuf_len += bytes; _buf_inc_readp(streambuf, bytes); if (stream.state <= DISCONNECT && _buf_used(streambuf) == 0) { eos = true; LOG_DEBUG("end of stream"); memset(m->readbuf + m->readbuf_len, 0, MAD_BUFFER_GUARD); m->readbuf_len += MAD_BUFFER_GUARD; } UNLOCK_S; MAD(m, stream_buffer, &m->stream, m->readbuf, m->readbuf_len); while (true) { size_t frames; s32_t *iptrl; s32_t *iptrr; unsigned max_frames; if (MAD(m, frame_decode, &m->frame, &m->stream) == -1) { decode_state ret; if (!eos && m->stream.error == MAD_ERROR_BUFLEN) { ret = DECODE_RUNNING; } else if (eos && (m->stream.error == MAD_ERROR_BUFLEN || m->stream.error == MAD_ERROR_LOSTSYNC)) { ret = DECODE_COMPLETE; } else if (!MAD_RECOVERABLE(m->stream.error)) { LOG_INFO("mad_frame_decode error: %s - stopping decoder", MAD(m, stream_errorstr, &m->stream)); ret = DECODE_COMPLETE; } else { if (m->stream.error != m->last_error) { // suppress repeat error messages LOG_DEBUG("mad_frame_decode error: %s", MAD(m, stream_errorstr, &m->stream)); } ret = DECODE_RUNNING; } m->last_error = m->stream.error; return ret; }; MAD(m, synth_frame, &m->synth, &m->frame); if (decode.new_stream) { LOCK_O; LOG_INFO("setting track_start"); output.next_sample_rate = decode_newstream(m->synth.pcm.samplerate, output.supported_rates); IF_DSD( output.next_dop = false; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O; } LOCK_O_direct; IF_DIRECT( max_frames = _buf_space(outputbuf) / BYTES_PER_FRAME; ); IF_PROCESS( max_frames = process.max_in_frames - process.in_frames; ); if (m->synth.pcm.length > max_frames) { LOG_WARN("too many samples - dropping samples"); m->synth.pcm.length = max_frames; } frames = m->synth.pcm.length; iptrl = m->synth.pcm.samples[0]; iptrr = m->synth.pcm.samples[ m->synth.pcm.channels - 1 ]; if (m->skip) { u32_t skip = min(m->skip, frames); LOG_DEBUG("gapless: skipping %u frames at start", skip); frames -= skip; m->skip -= skip; iptrl += skip; iptrr += skip; } if (m->samples) { if (m->samples < frames) { LOG_DEBUG("gapless: trimming %u frames from end", frames - m->samples); frames = (size_t)m->samples; } m->samples -= frames; if (m->samples > 0 && eos && !(m->stream.next_frame[0] == 0xff && (m->stream.next_frame[1] & 0xf0) == 0xf0)) { // this is the last frame to be decoded, but more samples expected so we must have skipped, remove padding // note this only works if the padding is less than one frame of 1152 bytes otherswise some gap will remain LOG_DEBUG("gapless: early end - trimming padding from end"); if (frames >= m->padding) { frames -= m->padding; } else { frames = 0; } m->samples = 0; } } LOG_SDEBUG("write %u frames", frames); while (frames > 0) { size_t f, count; s32_t *optr; IF_DIRECT( f = min(frames, _buf_cont_write(outputbuf) / BYTES_PER_FRAME); optr = (s32_t *)outputbuf->writep; ); IF_PROCESS( f = min(frames, process.max_in_frames - process.in_frames); optr = (s32_t *)((u8_t *)process.inbuf + process.in_frames * BYTES_PER_FRAME); ); count = f; while (count--) { *optr++ = scale(*iptrl++); *optr++ = scale(*iptrr++); } frames -= f; IF_DIRECT( _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames += f; ); } UNLOCK_O_direct; } return eos ? DECODE_COMPLETE : DECODE_RUNNING; } static void mad_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { if (!m->readbuf) { m->readbuf = malloc(READBUF_SIZE + MAD_BUFFER_GUARD); } m->checktags = 1; m->consume = 0; m->skip = MAD_DELAY; m->samples = 0; m->readbuf_len = 0; m->last_error = MAD_ERROR_NONE; MAD(m, stream_init, &m->stream); MAD(m, frame_init, &m->frame); MAD(m, synth_init, &m->synth); } static void mad_close(void) { mad_synth_finish(&m->synth); // macro only in current version MAD(m, frame_finish, &m->frame); MAD(m, stream_finish, &m->stream); free(m->readbuf); m->readbuf = NULL; } static bool load_mad() { #if !LINKALL void *handle = dlopen(LIBMAD, RTLD_NOW); char *err; if (!handle) { LOG_INFO("dlerror: %s", dlerror()); return false; } m->mad_stream_init = dlsym(handle, "mad_stream_init"); m->mad_frame_init = dlsym(handle, "mad_frame_init"); m->mad_synth_init = dlsym(handle, "mad_synth_init"); m->mad_frame_finish = dlsym(handle, "mad_frame_finish"); m->mad_stream_finish = dlsym(handle, "mad_stream_finish"); m->mad_stream_buffer = dlsym(handle, "mad_stream_buffer"); m->mad_frame_decode = dlsym(handle, "mad_frame_decode"); m->mad_synth_frame = dlsym(handle, "mad_synth_frame"); m->mad_stream_errorstr = dlsym(handle, "mad_stream_errorstr"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBMAD); #endif return true; } struct codec *register_mad(void) { static struct codec ret = { 'm', // id "mp3", // types READBUF_SIZE, // min read 206800, // min space mad_open, // open mad_close, // close mad_decode, // decode }; m = malloc(sizeof(struct mad)); if (!m) { return NULL; } m->readbuf = NULL; m->readbuf_len = 0; if (!load_mad()) { return NULL; } LOG_INFO("using mad to decode mp3"); return &ret; } squeezelite-1.8/main.c 0000664 0000000 0000000 00000040125 12463437225 0015023 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #include #define TITLE "Squeezelite " VERSION ", Copyright 2012-2015 Adrian Smith." #define CODECS_BASE "flac,pcm,mp3,ogg,aac" #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_FF CODECS_DSD CODECS_MP3 static void usage(const char *argv0) { printf(TITLE " See -t for license terms\n" "Usage: %s [options]\n" " -s [:]\tConnect to specified server, otherwise uses autodiscovery to find server\n" " -o \tSpecify output device, default \"default\", - = output to stdout\n" " -l \t\t\tList output devices\n" #if ALSA " -a :::\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n" #endif #if PORTAUDIO #if OSX " -a :\t\tSpecify Portaudio params to open output device, l = target latency in ms, r = allow OSX to resample (0|1)\n" #else " -a \t\tSpecify Portaudio params to open output device, l = target latency in ms\n" #endif #endif " -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 " -R -u [params]\tResample, params = ::::::,\n" " \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n" " \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 " -D [delay]\t\tOutput device supports DSD over PCM (DoP), delay = optional delay switching between PCM and DoP in ms\n" #endif #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 " LINUX" #endif #if WIN " WIN" #endif #if OSX " OSX" #endif #if FREEBSD " FREEBSD" #endif #if ALSA " ALSA" #endif #if PORTAUDIO " PORTAUDIO" #endif #if EVENTFD " EVENTFD" #endif #if SELFPIPE " SELFPIPE" #endif #if WINEVENT " WINEVENT" #endif #if RESAMPLE_MP " RESAMPLE_MP" #else #if RESAMPLE " RESAMPLE" #endif #endif #if FFMPEG " FFMPEG" #endif #if VISEXPORT " VISEXPORT" #endif #if IR " IR" #endif #if DSD " DSD" #endif #if LINKALL " LINKALL" #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 .\n\n" #if DSD "Contains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n" "is subject to its own license.\n\n" #endif ); } 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; 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 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; unsigned dop_delay = 0; #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 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 "UV" #endif , opt) && optind < argc - 1) { optarg = argv[optind + 1]; optind += 2; } else if (strstr("ltz?" #if ALSA "L" #endif #if RESAMPLE "uR" #endif #if DSD "D" #endif #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(1); } switch (opt[0]) { case 'o': output_device = optarg; 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 - or , 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); } } break; case 's': server = optarg; break; case 'n': name = optarg; break; case 'N': namefile = optarg; 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 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': if (optind < argc && argv[optind] && argv[optind][0] != '-') { resample = argv[optind++]; } else { resample = ""; } break; #endif #if DSD case 'D': dop = true; if (optind < argc && argv[optind] && argv[optind][0] != '-') { dop_delay = atoi(argv[optind++]); } break; #endif #if VISEXPORT case 'v': 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; 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 // 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 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 winsock_init(); #endif stream_init(log_stream, stream_buf_size); 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, output_mixer, output_mixer_unmute); #endif #if PORTAUDIO output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); #endif } #if DSD dop_init(dop, dop_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); decode_close(); stream_close(); if (!strcmp(output_device, "-")) { output_close_stdout(); } else { #if ALSA output_close_alsa(); #endif #if PORTAUDIO output_close_pa(); #endif } #if IR ir_close(); #endif #if WIN winsock_close(); #endif #if LINUX || FREEBSD if (pidfile) { unlink(pidfile); free(pidfile); } #endif exit(0); } squeezelite-1.8/mpg.c 0000664 0000000 0000000 00000015643 12463437225 0014671 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #include #define READ_SIZE 512 #define WRITE_SIZE 32 * 1024 struct mpg { mpg123_handle *h; bool use16bit; #if !LINKALL // mpg symbols to be dynamically loaded int (* mpg123_init)(void); int (* mpg123_feature)(const enum mpg123_feature_set); void (* mpg123_rates)(const long **, size_t *); int (* mpg123_format_none)(mpg123_handle *); int (* mpg123_format)(mpg123_handle *, long, int, int); mpg123_handle *(* mpg123_new)(const char*, int *); void (* mpg123_delete)(mpg123_handle *); int (* mpg123_open_feed)(mpg123_handle *); int (* mpg123_decode)(mpg123_handle *, const unsigned char *, size_t, unsigned char *, size_t, size_t *); int (* mpg123_getformat)(mpg123_handle *, long *, int *, int *); const char* (* mpg123_plain_strerror)(int); #endif }; static struct mpg *m; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex) #define IF_DIRECT(x) if (decode.direct) { x } #define IF_PROCESS(x) if (!decode.direct) { x } #else #define LOCK_O_direct mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct #define UNLOCK_O_not_direct #define IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define MPG123(h, fn, ...) (mpg123_ ## fn)(__VA_ARGS__) #else #define MPG123(h, fn, ...) (h)->mpg123_##fn(__VA_ARGS__) #endif static decode_state mpg_decode(void) { size_t bytes, space, size; int ret; u8_t *write_buf; LOCK_S; LOCK_O_direct; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); IF_DIRECT( space = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)); write_buf = outputbuf->writep; ); IF_PROCESS( space = process.max_in_frames; write_buf = process.inbuf; ); bytes = min(bytes, READ_SIZE); space = min(space, WRITE_SIZE); if (m->use16bit) { space = (space / BYTES_PER_FRAME) * 4; } // only get the new stream information on first call so we can reset decode.direct appropriately if (decode.new_stream) { space = 0; } ret = MPG123(m, decode, m->h, streambuf->readp, bytes, write_buf, space, &size); if (ret == MPG123_NEW_FORMAT) { if (decode.new_stream) { long rate; int channels, enc; MPG123(m, getformat, m->h, &rate, &channels, &enc); LOG_INFO("setting track_start"); LOCK_O_not_direct; output.next_sample_rate = decode_newstream(rate, output.supported_rates); IF_DSD( output.next_dop = false; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O_not_direct; } else { LOG_WARN("format change mid stream - not supported"); } } // expand 16bit output to 32bit samples if (m->use16bit) { s16_t *iptr; s32_t *optr; size_t count = size / 2; size = count * 4; iptr = (s16_t *)write_buf + count; optr = (s32_t *)write_buf + count; while (count--) { *--optr = *--iptr << 16; } } _buf_inc_readp(streambuf, bytes); IF_DIRECT( _buf_inc_writep(outputbuf, size); ); IF_PROCESS( process.in_frames = size / BYTES_PER_FRAME; ); UNLOCK_O_direct; LOG_SDEBUG("write %u frames", size / BYTES_PER_FRAME); if (ret == MPG123_DONE || (bytes == 0 && size == 0 && stream.state <= DISCONNECT)) { UNLOCK_S; LOG_INFO("stream complete"); return DECODE_COMPLETE; } UNLOCK_S; if (ret == MPG123_ERR) { LOG_WARN("Error"); return DECODE_COMPLETE; } // OK and NEED_MORE keep running return DECODE_RUNNING; } static void mpg_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { int err; const long *list; size_t count, i; if (m->h) { MPG123(m, delete, m->h); } m->h = MPG123(m, new, NULL, &err); if (m->h == NULL) { LOG_WARN("new error: %s", MPG123(m, plain_strerror, err)); } // restrict output to 32bit or 16bit signed 2 channel based on library capability MPG123(m, rates, &list, &count); MPG123(m, format_none, m->h); for (i = 0; i < count; i++) { MPG123(m, format, m->h, list[i], 2, m->use16bit ? MPG123_ENC_SIGNED_16 : MPG123_ENC_SIGNED_32); } err = MPG123(m, open_feed, m->h); if (err) { LOG_WARN("open feed error: %s", MPG123(m, plain_strerror, err)); } } static void mpg_close(void) { MPG123(m, delete, m->h); m->h = NULL; } static bool load_mpg() { #if !LINKALL void *handle = dlopen(LIBMPG, RTLD_NOW); char *err; if (!handle) { LOG_INFO("dlerror: %s", dlerror()); return false; } m->mpg123_init = dlsym(handle, "mpg123_init"); m->mpg123_feature = dlsym(handle, "mpg123_feature"); m->mpg123_rates = dlsym(handle, "mpg123_rates"); m->mpg123_format_none = dlsym(handle, "mpg123_format_none"); m->mpg123_format = dlsym(handle, "mpg123_format"); m->mpg123_new = dlsym(handle, "mpg123_new"); m->mpg123_delete = dlsym(handle, "mpg123_delete"); m->mpg123_open_feed = dlsym(handle, "mpg123_open_feed"); m->mpg123_decode = dlsym(handle, "mpg123_decode"); m->mpg123_getformat = dlsym(handle, "mpg123_getformat"); m->mpg123_plain_strerror = dlsym(handle, "mpg123_plain_strerror"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBMPG); #endif return true; } struct codec *register_mpg(void) { static struct codec ret = { 'm', // id "mp3", // types READ_SIZE, // min read WRITE_SIZE, // min space mpg_open, // open mpg_close, // close mpg_decode, // decode }; m = malloc(sizeof(struct mpg)); if (!m) { return NULL; } m->h = NULL; if (!load_mpg()) { return NULL; } MPG123(m, init); m->use16bit = MPG123(m, feature, MPG123_FEATURE_OUTPUT_32BIT); LOG_INFO("using mpg to decode mp3"); return &ret; } squeezelite-1.8/output.c 0000664 0000000 0000000 00000033715 12463437225 0015446 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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_dop; #endif #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.dop != output.next_dop) { delay = output.dop_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.dop = output.next_dop; ) if (!output.fade == FADE_ACTIVE || !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.dop) { 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) { bytes /= 2; } 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_dop = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME); if (!silencebuf_dop) { LOG_ERROR("unable to malloc silence dop buffer"); exit(0); } dop_silence_frames((u32_t *)silencebuf_dop, 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; if (!rates[0]) { if (!test_open(output.device, output.supported_rates)) { LOG_ERROR("unable to open output device"); exit(0); } } else { 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_dop); ) } 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; } squeezelite-1.8/output_alsa.c 0000664 0000000 0000000 00000061216 12463437225 0016443 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Output using Alsa #include "squeezelite.h" #if ALSA #include #include #include #include #define MAX_DEVICE_LEN 128 static snd_pcm_format_t fmts[] = { SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_UNKNOWN }; #if SL_LITTLE_ENDIAN #define NATIVE_FORMAT SND_PCM_FORMAT_S32_LE #else #define NATIVE_FORMAT SND_PCM_FORMAT_S32_BE #endif // ouput device static struct { char device[MAX_DEVICE_LEN + 1]; snd_pcm_format_t format; snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t period_size; unsigned rate; bool mmap; bool reopen; u8_t *write_buf; const char *volume_mixer_name; int volume_mixer_index; } alsa; static snd_pcm_t *pcmp = NULL; extern u8_t *silencebuf; #if DSD extern u8_t *silencebuf_dop; #endif 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) void list_devices(void) { void **hints, **n; if (snd_device_name_hint(-1, "pcm", &hints) >= 0) { n = hints; printf("Output devices:\n"); while (*n) { char *name = snd_device_name_get_hint(*n, "NAME"); char *desc = snd_device_name_get_hint(*n, "DESC"); if (name) printf(" %-30s", name); if (desc) { char *s1 = strtok(desc, "\n"); char *s2 = strtok(NULL, "\n"); if (s1) printf(" - %s", s1); if (s2) printf(" - %s", s2); } printf("\n"); if (name) free(name); if (desc) free(desc); n++; } snd_device_name_free_hint(hints); } 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) { fprintf(stderr, "%s ALSA %s:%d ", logtime(), function, line); va_start(args, fmt); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); fflush(stderr); } return NULL; } static void alsa_close(void) { int err; if ((err = snd_pcm_close(pcmp)) < 0) { LOG_INFO("snd_pcm_close error: %s", snd_strerror(err)); } } bool test_open(const char *device, unsigned rates[]) { int err; snd_pcm_t *pcm; snd_pcm_hw_params_t *hw_params; hw_params = (snd_pcm_hw_params_t *) alloca(snd_pcm_hw_params_sizeof()); memset(hw_params, 0, snd_pcm_hw_params_sizeof()); // open device if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { LOG_ERROR("playback open error: %s", snd_strerror(err)); return false; } // get max params if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) { LOG_ERROR("hwparam init error: %s", snd_strerror(err)); return false; } // find supported sample rates to enable client side resampling of non supported rates unsigned i, ind; unsigned ref[] TEST_RATES; for (i = 0, ind = 0; ref[i]; ++i) { if (snd_pcm_hw_params_test_rate(pcm, hw_params, ref[i], 0) == 0) { rates[ind++] = ref[i]; } } if ((err = snd_pcm_close(pcm)) < 0) { LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); return false; } return true; } static bool pcm_probe(const char *device) { int err; snd_pcm_t *pcm; if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { return false; } if ((err = snd_pcm_close(pcm)) < 0) { LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); } return true; } static int alsa_open(const char *device, unsigned sample_rate, unsigned alsa_buffer, unsigned alsa_period) { int err; snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); // close if already open if (pcmp) alsa_close(); // reset params alsa.rate = 0; alsa.period_size = 0; strcpy(alsa.device, device); if (strlen(device) > MAX_DEVICE_LEN - 4 - 1) { LOG_ERROR("device name too long: %s", device); return -1; } LOG_INFO("opening device at: %u", sample_rate); bool retry; do { // open device if ((err = snd_pcm_open(&pcmp, alsa.device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { LOG_ERROR("playback open error: %s", snd_strerror(err)); return err; } // init params memset(hw_params, 0, snd_pcm_hw_params_sizeof()); if ((err = snd_pcm_hw_params_any(pcmp, hw_params)) < 0) { LOG_ERROR("hwparam init error: %s", snd_strerror(err)); return err; } // open hw: devices without resampling, if sample rate fails try plughw: with resampling bool hw = !strncmp(alsa.device, "hw:", 3); retry = false; if ((err = snd_pcm_hw_params_set_rate_resample(pcmp, hw_params, !hw)) < 0) { LOG_ERROR("resampling setup failed: %s", snd_strerror(err)); return err; } if ((err = snd_pcm_hw_params_set_rate(pcmp, hw_params, sample_rate, 0)) < 0) { if (hw) { strcpy(alsa.device + 4, device); memcpy(alsa.device, "plug", 4); LOG_INFO("reopening device %s in plug mode as %s for resampling", device, alsa.device); snd_pcm_close(pcmp); retry = true; } } } while (retry); // set access if (!alsa.mmap || snd_pcm_hw_params_set_access(pcmp, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) { if ((err = snd_pcm_hw_params_set_access(pcmp, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { LOG_ERROR("access type not available: %s", snd_strerror(err)); return err; } alsa.mmap = false; } // set the sample format snd_pcm_format_t *fmt = alsa.format ? &alsa.format : (snd_pcm_format_t *)fmts; do { if (snd_pcm_hw_params_set_format(pcmp, hw_params, *fmt) >= 0) { LOG_INFO("opened device %s using format: %s sample rate: %u mmap: %u", alsa.device, snd_pcm_format_name(*fmt), sample_rate, alsa.mmap); alsa.format = *fmt; break; } if (alsa.format) { LOG_ERROR("unable to open audio device requested format: %s", snd_pcm_format_name(alsa.format)); return -1; } ++fmt; if (*fmt == SND_PCM_FORMAT_UNKNOWN) { LOG_ERROR("unable to open audio device with any supported format"); return -1; } } while (*fmt != SND_PCM_FORMAT_UNKNOWN); // set the output format to be used by _scale_and_pack switch(alsa.format) { case SND_PCM_FORMAT_S32_LE: output.format = S32_LE; break; case SND_PCM_FORMAT_S24_LE: output.format = S24_LE; break; case SND_PCM_FORMAT_S24_3LE: output.format = S24_3LE; break; case SND_PCM_FORMAT_S16_LE: output.format = S16_LE; break; default: break; } // set channels if ((err = snd_pcm_hw_params_set_channels (pcmp, hw_params, 2)) < 0) { LOG_ERROR("channel count not available: %s", snd_strerror(err)); return err; } // set period size - value of < 50 treated as period count, otherwise size in bytes if (alsa_period < 50) { unsigned count = alsa_period; if ((err = snd_pcm_hw_params_set_periods_near(pcmp, hw_params, &count, 0)) < 0) { LOG_ERROR("unable to set period count %s", snd_strerror(err)); return err; } } else { snd_pcm_uframes_t size = alsa_period; int dir = 0; if ((err = snd_pcm_hw_params_set_period_size_near(pcmp, hw_params, &size, &dir)) < 0) { LOG_ERROR("unable to set period size %s", snd_strerror(err)); return err; } } // set buffer size - value of < 500 treated as buffer time in ms, otherwise size in bytes if (alsa_buffer < 500) { unsigned time = alsa_buffer * 1000; int dir = 0; if ((err = snd_pcm_hw_params_set_buffer_time_near(pcmp, hw_params, &time, &dir)) < 0) { LOG_ERROR("unable to set buffer time %s", snd_strerror(err)); return err; } } else { snd_pcm_uframes_t size = alsa_buffer; if ((err = snd_pcm_hw_params_set_buffer_size_near(pcmp, hw_params, &size)) < 0) { LOG_ERROR("unable to set buffer size %s", snd_strerror(err)); return err; } } // get period_size if ((err = snd_pcm_hw_params_get_period_size(hw_params, &alsa.period_size, 0)) < 0) { LOG_ERROR("unable to get period size: %s", snd_strerror(err)); return err; } // get buffer_size if ((err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa.buffer_size)) < 0) { LOG_ERROR("unable to get buffer size: %s", snd_strerror(err)); return err; } LOG_INFO("buffer: %u period: %u -> buffer size: %u period size: %u", alsa_buffer, alsa_period, alsa.buffer_size, alsa.period_size); // ensure we have two buffer sizes of samples before starting output output.start_frames = alsa.buffer_size * 2; // create an intermediate buffer for non mmap case for all but NATIVE_FORMAT // this is used to pack samples into the output format before calling writei if (!alsa.mmap && !alsa.write_buf && alsa.format != NATIVE_FORMAT) { alsa.write_buf = malloc(alsa.buffer_size * BYTES_PER_FRAME); if (!alsa.write_buf) { LOG_ERROR("unable to malloc write_buf"); return -1; } } // set params if ((err = snd_pcm_hw_params(pcmp, hw_params)) < 0) { LOG_ERROR("unable to set hw params: %s", snd_strerror(err)); return err; } // dump info if (loglevel == lSDEBUG) { static snd_output_t *debug_output; snd_output_stdio_attach(&debug_output, stderr, 0); snd_pcm_dump(pcmp, debug_output); } // this indicates we have opened the device ok alsa.rate = sample_rate; return 0; } static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { const snd_pcm_channel_area_t *areas; snd_pcm_uframes_t offset; void *outputptr; s32_t *inputptr; int err; if (alsa.mmap) { snd_pcm_uframes_t alsa_frames = (snd_pcm_uframes_t)out_frames; snd_pcm_avail_update(pcmp); if ((err = snd_pcm_mmap_begin(pcmp, &areas, &offset, &alsa_frames)) < 0) { LOG_WARN("error from mmap_begin: %s", snd_strerror(err)); return -1; } out_frames = (frames_t)alsa_frames; } if (!silence) { // applying cross fade is delayed until this point as mmap_begin can change out_frames 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); } } inputptr = (s32_t *) (silence ? silencebuf : outputbuf->readp); IF_DSD( if (output.dop) { if (silence) { inputptr = (s32_t *) silencebuf_dop; } update_dop((u32_t *) inputptr, out_frames, output.invert && !silence); } ) if (alsa.mmap || alsa.format != NATIVE_FORMAT) { outputptr = alsa.mmap ? (areas[0].addr + (areas[0].first + offset * areas[0].step) / 8) : alsa.write_buf; _scale_and_pack_frames(outputptr, inputptr, out_frames, gainL, gainR, output.format); } else { outputptr = (void *)inputptr; if (!silence) { if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { _apply_gain(outputbuf, out_frames, gainL, gainR); } } } if (alsa.mmap) { snd_pcm_sframes_t w = snd_pcm_mmap_commit(pcmp, offset, out_frames); if (w < 0 || w != out_frames) { LOG_WARN("mmap_commit error"); return -1; } } else { snd_pcm_sframes_t w = snd_pcm_writei(pcmp, outputptr, out_frames); if (w < 0) { //if (w != -EAGAIN && ((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { if (((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { static unsigned recover_count = 0; LOG_WARN("recover failed: %s [%u]", snd_strerror(err), ++recover_count); if (recover_count >= 10) { recover_count = 0; alsa_close(); pcmp = NULL; } } return -1; } else { if (w != out_frames) { LOG_WARN("writei only wrote %u of %u", w, out_frames); } out_frames = w; } } return (int)out_frames; } static void *output_thread(void *arg) { bool start = true; bool output_off = (output.state == OUTPUT_OFF); bool probe_device = (arg != NULL); int err; while (running) { // disabled output - player is off while (output_off) { usleep(100000); LOCK; output_off = (output.state == OUTPUT_OFF); UNLOCK; if (!running) return 0; } // wait until device returns - to allow usb audio devices to be turned off if (probe_device) { while (!pcm_probe(output.device)) { LOG_DEBUG("waiting for device %s to return", output.device); sleep(5); } probe_device = false; } if (!pcmp || alsa.rate != output.current_sample_rate) { LOG_INFO("open output device: %s", output.device); LOCK; // FIXME - some alsa hardware requires opening twice for a new sample rate to work // this is a workaround which should be removed if (alsa.reopen) { alsa_open(output.device, output.current_sample_rate, output.buffer, output.period); } if (!!alsa_open(output.device, output.current_sample_rate, output.buffer, output.period)) { output.error_opening = true; UNLOCK; sleep(5); continue; } output.error_opening = false; start = true; UNLOCK; } snd_pcm_state_t state = snd_pcm_state(pcmp); if (state == SND_PCM_STATE_XRUN) { LOG_INFO("XRUN"); if ((err = snd_pcm_recover(pcmp, -EPIPE, 1)) < 0) { LOG_INFO("XRUN recover failed: %s", snd_strerror(err)); } start = true; continue; } else if (state == SND_PCM_STATE_SUSPENDED) { if ((err = snd_pcm_recover(pcmp, -ESTRPIPE, 1)) < 0) { LOG_INFO("SUSPEND recover failed: %s", snd_strerror(err)); } } else if (state == SND_PCM_STATE_DISCONNECTED) { LOG_INFO("Device %s no longer available", output.device); alsa_close(); pcmp = NULL; probe_device = true; continue; } snd_pcm_sframes_t avail = snd_pcm_avail_update(pcmp); if (avail < 0) { if ((err = snd_pcm_recover(pcmp, avail, 1)) < 0) { if (err == -ENODEV) { LOG_INFO("Device %s no longer available", output.device); alsa_close(); pcmp = NULL; probe_device = true; continue; } LOG_WARN("recover failed: %s", snd_strerror(err)); } start = true; continue; } if (avail < alsa.period_size) { if (start) { if (alsa.mmap && ((err = snd_pcm_start(pcmp)) < 0)) { if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { if (err == -ENODEV) { LOG_INFO("Device %s no longer available", output.device); alsa_close(); pcmp = NULL; probe_device = true; continue; } LOG_INFO("start error: %s", snd_strerror(err)); usleep(10000); } } else { start = false; } } else { if ((err = snd_pcm_wait(pcmp, 1000)) < 0) { if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { LOG_INFO("pcm wait error: %s", snd_strerror(err)); } start = true; } } continue; } // 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); } // avoid spinning in cases where wait returns but no bytes available (seen with pulse audio) if (avail == 0) { LOG_SDEBUG("avail 0 - sleeping"); usleep(10000); continue; } LOCK; // turn off if requested if (output.state == OUTPUT_OFF) { UNLOCK; LOG_INFO("disabling output"); alsa_close(); pcmp = NULL; output_off = true; vis_stop(); continue; } // measure output delay snd_pcm_sframes_t delay; if ((err = snd_pcm_delay(pcmp, &delay)) < 0) { if (err == -EPIPE) { // EPIPE indicates underrun - attempt to recover UNLOCK; continue; } else if (err == -EIO) { // EIO can occur with non existant pulse server UNLOCK; LOG_SDEBUG("snd_pcm_delay returns: EIO - sleeping"); usleep(100000); continue; } else { LOG_DEBUG("snd_pcm_delay returns: %d", err); } } else { output.device_frames = delay; output.updated = gettime_ms(); output.frames_played_dmp = output.frames_played; } // process frames frames_t wrote = _output_frames(avail); UNLOCK; // some output devices such as alsa null refuse any data, avoid spinning if (!wrote) { LOG_SDEBUG("wrote 0 - sleeping"); usleep(10000); } } return 0; } static pthread_t thread; void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute) { unsigned alsa_buffer = ALSA_BUFFER_TIME; unsigned alsa_period = ALSA_PERIOD_COUNT; char *alsa_sample_fmt = NULL; 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, ':'); char *m = next_param(NULL, ':'); char *r = next_param(NULL, ':'); if (t) alsa_buffer = atoi(t); if (c) alsa_period = atoi(c); if (s) alsa_sample_fmt = s; if (m) alsa_mmap = atoi(m); if (r) alsa_reopen = atoi(r); loglevel = level; LOG_INFO("init output"); memset(&output, 0, sizeof(output)); alsa.mmap = alsa_mmap; 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; output.start_frames = 0; output.write_cb = &_write_frames; output.rate_delay = rate_delay; if (alsa_sample_fmt) { if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; if (!strcmp(alsa_sample_fmt, "24")) alsa.format = SND_PCM_FORMAT_S24_LE; if (!strcmp(alsa_sample_fmt, "24_3")) alsa.format = SND_PCM_FORMAT_S24_3LE; if (!strcmp(alsa_sample_fmt, "16")) alsa.format = SND_PCM_FORMAT_S16_LE; } LOG_INFO("requested alsa_buffer: %u alsa_period: %u format: %s mmap: %u", output.buffer, output.period, alsa_sample_fmt ? alsa_sample_fmt : "any", alsa.mmap); snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); output_init_common(level, device, output_buf_size, rates, idle); if (mixer_unmute && volume_mixer_name) { set_mixer(output.device, volume_mixer_name, volume_mixer_index ? atoi(volume_mixer_index) : 0, true, 0, 0); } #if LINUX // RT linux - aim to avoid pagefaults by locking memory: // https://rt.wiki.kernel.org/index.php/Threaded_RT-application_with_memory_locking_and_stack_handling_example if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { LOG_INFO("unable to lock memory: %s", strerror(errno)); } else { LOG_INFO("memory locked"); } 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); #endif // start output thread pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); pthread_create(&thread, &attr, output_thread, rates[0] ? "probe" : NULL); pthread_attr_destroy(&attr); // try to set this thread to real-time scheduler class, only works as root or if user has permission struct sched_param param; param.sched_priority = rt_priority; if (pthread_setschedparam(thread, SCHED_FIFO, ¶m) != 0) { LOG_DEBUG("unable to set output sched fifo: %s", strerror(errno)); } else { LOG_DEBUG("set output sched fifo rt: %u", param.sched_priority); } } void output_close_alsa(void) { LOG_INFO("close output"); LOCK; running = false; UNLOCK; pthread_join(thread, NULL); if (alsa.write_buf) free(alsa.write_buf); output_close_common(); } #endif // ALSA squeezelite-1.8/output_pa.c 0000664 0000000 0000000 00000026737 12463437225 0016134 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Portaudio output #include "squeezelite.h" #if PORTAUDIO #include #if OSX #include #endif // ouput device static struct { unsigned rate; PaStream *stream; } pa; 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) extern u8_t *silencebuf; #if DSD extern u8_t *silencebuf_dop; #endif void list_devices(void) { PaError err; int i; if ((err = Pa_Initialize()) != paNoError) { LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); return; } printf("Output devices:\n"); for (i = 0; i < Pa_GetDeviceCount(); ++i) { if (Pa_GetDeviceInfo(i)->maxOutputChannels) { printf(" %i - %s [%s]\n", i, Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); } } printf("\n"); 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) { int len = strlen(device); int i; if (!strncmp(device, "default", 7)) { return Pa_GetDefaultOutputDevice(); } if (len >= 1 && len <= 2 && device[0] >= '0' && device[0] <= '9') { return atoi(device); } #define DEVICE_ID_MAXLEN 256 for (i = 0; i < Pa_GetDeviceCount(); ++i) { char tmp[DEVICE_ID_MAXLEN]; snprintf(tmp, DEVICE_ID_MAXLEN, "%s [%s]", Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); if (!strncmp(tmp, device, len)) { return i; } } return -1; } static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); bool test_open(const char *device, unsigned rates[]) { PaStreamParameters outputParameters; PaError err; unsigned ref[] TEST_RATES; int device_id, i, ind; if ((device_id = pa_device_id(device)) == -1) { LOG_INFO("device %s not found", device); return false; } outputParameters.device = device_id; outputParameters.channelCount = 2; outputParameters.sampleFormat = paInt32; outputParameters.suggestedLatency = output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; // check supported sample rates // Note use Pa_OpenStream as it appears more reliable than Pa_IsFormatSupported on some windows apis for (i = 0, ind = 0; ref[i]; ++i) { err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)ref[i], paFramesPerBufferUnspecified, paNoFlag, pa_callback, NULL); if (err == paNoError) { Pa_CloseStream(pa.stream); rates[ind++] = ref[i]; } } if (!rates[0]) { LOG_WARN("no available rate found"); return false; } pa.stream = NULL; return true; } static void pa_stream_finished(void *userdata) { if (running) { LOG_INFO("stream finished"); LOCK; output.pa_reopen = true; wake_controller(); UNLOCK; } } static thread_type monitor_thread; bool monitor_thread_running = false; static void *pa_monitor() { bool output_off; LOCK; if (monitor_thread_running) { LOG_DEBUG("monitor thread already running"); UNLOCK; return 0; } LOG_DEBUG("start monitor thread"); monitor_thread_running = true; output_off = (output.state == OUTPUT_OFF); while (monitor_thread_running) { if (output_off) { if (output.state != OUTPUT_OFF) { LOG_INFO("output on"); break; } } else { // this is a hack to partially support hot plugging of devices // we rely on terminating and reinitalising PA to get an updated list of devices and use name for output.device LOG_INFO("probing device %s", output.device); Pa_Terminate(); Pa_Initialize(); pa.stream = NULL; if (pa_device_id(output.device) != -1) { LOG_INFO("device reopen"); break; } } UNLOCK; sleep(output_off ? 1 : 5); LOCK; } LOG_DEBUG("end monitor thread"); monitor_thread_running = false; pa.stream = NULL; _pa_open(); UNLOCK; return 0; } void _pa_open(void) { PaStreamParameters outputParameters; PaError err = paNoError; int device_id; if (pa.stream) { if ((err = Pa_CloseStream(pa.stream)) != paNoError) { LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); } } if (output.state == OUTPUT_OFF) { // we get called when transitioning to OUTPUT_OFF to create the probe thread // set err to avoid opening device and logging messages err = 1; } else if ((device_id = pa_device_id(output.device)) == -1) { LOG_INFO("device %s not found", output.device); err = 1; } else { outputParameters.device = device_id; outputParameters.channelCount = 2; outputParameters.sampleFormat = paInt32; outputParameters.suggestedLatency = output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; #if OSX // enable pro mode which aims to avoid resampling if possible // see http://code.google.com/p/squeezelite/issues/detail?id=11 & http://code.google.com/p/squeezelite/issues/detail?id=37 // command line controls osx_playnice which is -1 if not specified, 0 or 1 - choose playnice if -1 or 1 PaMacCoreStreamInfo macInfo; unsigned long streamInfoFlags; if (output.osx_playnice) { LOG_INFO("opening device in PlayNice mode"); streamInfoFlags = paMacCorePlayNice; } else { LOG_INFO("opening device in Pro mode"); streamInfoFlags = paMacCorePro; } PaMacCore_SetupStreamInfo(&macInfo, streamInfoFlags); outputParameters.hostApiSpecificStreamInfo = &macInfo; #endif } if (!err && (err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)output.current_sample_rate, paFramesPerBufferUnspecified, paPrimeOutputBuffersUsingStreamCallback | paDitherOff, pa_callback, NULL)) != paNoError) { LOG_WARN("error opening device %i - %s : %s", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, Pa_GetErrorText(err)); } if (!err) { LOG_INFO("opened device %i - %s at %u latency %u ms", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, (unsigned int)Pa_GetStreamInfo(pa.stream)->sampleRate, (unsigned int)(Pa_GetStreamInfo(pa.stream)->outputLatency * 1000)); pa.rate = output.current_sample_rate; if ((err = Pa_SetStreamFinishedCallback(pa.stream, pa_stream_finished)) != paNoError) { LOG_WARN("error setting finish callback: %s", Pa_GetErrorText(err)); } UNLOCK; // StartStream can call pa_callback in a sychronised thread on freebsd, remove lock while it is called if ((err = Pa_StartStream(pa.stream)) != paNoError) { LOG_WARN("error starting stream: %s", Pa_GetErrorText(err)); } LOCK; } if (err && !monitor_thread_running) { vis_stop(); // create a thread to check for output state change or device return #if LINUX || OSX || FREEBSD pthread_create(&monitor_thread, NULL, pa_monitor, NULL); #endif #if WIN monitor_thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&pa_monitor, NULL, 0, NULL); #endif } output.error_opening = !!err; } static u8_t *optr; static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { 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); } if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { _apply_gain(outputbuf, out_frames, gainL, gainR); } IF_DSD( if (output.dop) { update_dop((u32_t *) outputbuf->readp, out_frames, output.invert); } ) memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); } else { u8_t *buf = silencebuf; IF_DSD( if (output.dop) { buf = silencebuf_dop; update_dop((u32_t *) buf, out_frames, false); // don't invert silence } ) memcpy(optr, buf, out_frames * BYTES_PER_FRAME); } optr += out_frames * BYTES_PER_FRAME; return (int)out_frames; } static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags statusFlags, void *userData) { int ret; double stream_time; frames_t frames; optr = (u8_t *)pa_output; LOCK; stream_time = Pa_GetStreamTime(pa.stream); if (time_info->outputBufferDacTime > stream_time) { // workaround for wdm-ks which can return outputBufferDacTime with a different epoch output.device_frames = (unsigned)((time_info->outputBufferDacTime - stream_time) * output.current_sample_rate); } else { output.device_frames = 0; } output.updated = gettime_ms(); output.frames_played_dmp = output.frames_played; do { frames = _output_frames(pa_frames_wanted); pa_frames_wanted -= frames; } while (pa_frames_wanted > 0 && frames != 0); if (pa_frames_wanted > 0) { LOG_DEBUG("pad with silence"); memset(optr, 0, pa_frames_wanted * BYTES_PER_FRAME); } if (output.state == OUTPUT_OFF) { LOG_INFO("output off"); ret = paComplete; } else if (pa.rate != output.current_sample_rate) { ret = paComplete; } else { ret = paContinue; } UNLOCK; return ret; } void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { PaError err; unsigned latency = 0; int osx_playnice = -1; char *l = next_param(params, ':'); char *p = next_param(NULL, ':'); if (l) latency = (unsigned)atoi(l); if (p) osx_playnice = atoi(p); loglevel = level; LOG_INFO("init output"); memset(&output, 0, sizeof(output)); output.latency = latency; output.osx_playnice = osx_playnice; output.format = 0; output.start_frames = 0; output.write_cb = &_write_frames; output.rate_delay = rate_delay; pa.stream = NULL; LOG_INFO("requested latency: %u", output.latency); 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, idle); LOCK; _pa_open(); UNLOCK; } void output_close_pa(void) { PaError err; LOG_INFO("close output"); LOCK; running = false; monitor_thread_running = false; if (pa.stream) { if ((err = Pa_AbortStream(pa.stream)) != paNoError) { LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); } } if ((err = Pa_Terminate()) != paNoError) { LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); } UNLOCK; output_close_common(); } #endif // PORTAUDIO squeezelite-1.8/output_pack.c 0000664 0000000 0000000 00000021322 12463437225 0016433 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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) { 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; } } squeezelite-1.8/output_stdout.c 0000664 0000000 0000000 00000007676 12463437225 0017057 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Stdout output #include "squeezelite.h" #define FRAME_BLOCK MAX_SILENCE_FRAMES 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) extern u8_t *silencebuf; #if DSD extern u8_t *silencebuf_dop; #endif // 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 int _stdout_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; } IF_DSD( if (output.dop) { if (silence) { obuf = silencebuf_dop; } update_dop((u32_t *)obuf, out_frames, output.invert && !silence); } ) _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; } static thread_type thread; void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { loglevel = level; LOG_INFO("init output stdout"); 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 = &_stdout_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 pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); pthread_create(&thread, &attr, output_thread, NULL); pthread_attr_destroy(&attr); #endif #if WIN thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL); #endif } void output_close_stdout(void) { LOG_INFO("close output"); LOCK; running = false; UNLOCK; free(buf); output_close_common(); } squeezelite-1.8/output_vis.c 0000664 0000000 0000000 00000010010 12463437225 0016306 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Export audio samples for visualiser process (16 bit only best endevours) #include "squeezelite.h" #if VISEXPORT #include #include #include #define VIS_BUF_SIZE 16384 #define VIS_LOCK_NS 1000000 // ns to wait for vis wrlock static struct vis_t { pthread_rwlock_t rwlock; u32_t buf_size; u32_t buf_index; bool running; u32_t rate; time_t updated; s16_t buffer[VIS_BUF_SIZE]; } *vis_mmap = NULL; static char vis_shm_path[40]; static int vis_fd = -1; static log_level loglevel; // attempt to write audio to vis_mmap but do not wait more than VIS_LOCK_NS to get wrlock // this can result in missing audio export to the mmap region, but this is preferable dropping audio void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence) { if (vis_mmap) { int err; err = pthread_rwlock_trywrlock(&vis_mmap->rwlock); if (err) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_nsec += VIS_LOCK_NS; if (ts.tv_nsec > 1000000000) { ts.tv_sec += 1; ts.tv_nsec -= 1000000000; } err = pthread_rwlock_timedwrlock(&vis_mmap->rwlock, &ts); } if (err) { LOG_DEBUG("failed to get wrlock - skipping visulizer export"); } else { if (silence) { vis_mmap->running = false; } else { frames_t vis_cnt = out_frames; s32_t *ptr = (s32_t *) outputbuf->readp; unsigned i = vis_mmap->buf_index; if (!output->current_replay_gain) { while (vis_cnt--) { vis_mmap->buffer[i++] = *(ptr++) >> 16; vis_mmap->buffer[i++] = *(ptr++) >> 16; if (i == VIS_BUF_SIZE) i = 0; } } else { while (vis_cnt--) { vis_mmap->buffer[i++] = gain(*(ptr++), output->current_replay_gain) >> 16; vis_mmap->buffer[i++] = gain(*(ptr++), output->current_replay_gain) >> 16; if (i == VIS_BUF_SIZE) i = 0; } } vis_mmap->updated = time(NULL); vis_mmap->running = true; vis_mmap->buf_index = i; vis_mmap->rate = output->current_sample_rate; } pthread_rwlock_unlock(&vis_mmap->rwlock); } } } void vis_stop(void) { if (vis_mmap) { pthread_rwlock_wrlock(&vis_mmap->rwlock); vis_mmap->running = false; pthread_rwlock_unlock(&vis_mmap->rwlock); } } void output_vis_init(log_level level, u8_t *mac) { loglevel = level; sprintf(vis_shm_path, "/squeezelite-%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); mode_t old_mask = umask(000); // allow any user to read our shm when created vis_fd = shm_open(vis_shm_path, O_CREAT | O_RDWR, 0666); if (vis_fd != -1) { if (ftruncate(vis_fd, sizeof(struct vis_t)) == 0) { vis_mmap = (struct vis_t *)mmap(NULL, sizeof(struct vis_t), PROT_READ | PROT_WRITE, MAP_SHARED, vis_fd, 0); } } if (vis_mmap > 0) { pthread_rwlockattr_t attr; pthread_rwlockattr_init(&attr); pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_rwlock_init(&vis_mmap->rwlock, &attr); vis_mmap->buf_size = VIS_BUF_SIZE; vis_mmap->running = false; vis_mmap->rate = 44100; pthread_rwlockattr_destroy(&attr); LOG_INFO("opened visulizer shared memory as %s", vis_shm_path); } else { LOG_WARN("unable to open visualizer shared memory"); vis_mmap = NULL; } umask(old_mask); } #endif // VISEXPORT squeezelite-1.8/pcm.c 0000664 0000000 0000000 00000024120 12463437225 0014653 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #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; #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 }; 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; LOG_INFO("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); audio_left = len - 8 - offset; LOG_INFO("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) { _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.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); output.track_start = outputbuf->writep; IF_DSD( output.next_dop = false; ) if (output.fade_mode) _checkfade(true); decode.new_stream = false; 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) { 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; } squeezelite-1.8/process.c 0000664 0000000 0000000 00000011524 12463437225 0015556 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // sample processing - only included when building with PROCESS set #include "squeezelite.h" #if PROCESS extern log_level loglevel; extern struct buffer *outputbuf; extern struct decodestate decode; struct processstate process; extern struct codec *codec; #define LOCK_D mutex_lock(decode.mutex); #define UNLOCK_D mutex_unlock(decode.mutex); #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) // macros to map to processing functions - currently only resample.c // this can be made more generic when multiple processing mechanisms get added #if RESAMPLE #define SAMPLES_FUNC resample_samples #define DRAIN_FUNC resample_drain #define NEWSTREAM_FUNC resample_newstream #define FLUSH_FUNC resample_flush #define INIT_FUNC resample_init #endif // transfer all processed frames to the output buf static void _write_samples(void) { size_t frames = process.out_frames; u32_t *iptr = (u32_t *)process.outbuf; unsigned cnt = 10; LOCK_O; while (frames > 0) { frames_t f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; u32_t *optr = (u32_t *)outputbuf->writep; if (f > 0) { f = min(f, frames); memcpy(optr, iptr, f * BYTES_PER_FRAME); frames -= f; _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); iptr += f * BYTES_PER_FRAME / sizeof(*iptr); } else if (cnt--) { // there should normally be space in the output buffer, but may need to wait during drain phase UNLOCK_O; usleep(10000); LOCK_O; } else { // bail out if no space found after 100ms to avoid locking LOG_ERROR("unable to get space in output buffer"); UNLOCK_O; return; } } UNLOCK_O; } // process samples - called with decode mutex set void process_samples(void) { SAMPLES_FUNC(&process); _write_samples(); process.in_frames = 0; } // drain at end of track - called with decode mutex set void process_drain(void) { bool done; do { done = DRAIN_FUNC(&process); _write_samples(); } while (!done); LOG_DEBUG("processing track complete - frames in: %lu out: %lu", process.total_in, process.total_out); } // new stream - called with decode mutex set unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]) { bool active = NEWSTREAM_FUNC(&process, raw_sample_rate, supported_rates); LOG_INFO("processing: %s", active ? "active" : "inactive"); *direct = !active; if (active) { unsigned max_in_frames, max_out_frames; process.in_frames = process.out_frames = 0; process.total_in = process.total_out = 0; max_in_frames = codec->min_space / BYTES_PER_FRAME ; // increase size of output buffer by 10% as output rate is not an exact multiple of input rate if (process.out_sample_rate % process.in_sample_rate == 0) { max_out_frames = max_in_frames * (process.out_sample_rate / process.in_sample_rate); } else { max_out_frames = (int)(1.1 * (float)max_in_frames * (float)process.out_sample_rate / (float)process.in_sample_rate); } if (process.max_in_frames != max_in_frames) { LOG_DEBUG("creating process buf in frames: %u", max_in_frames); if (process.inbuf) free(process.inbuf); process.inbuf = malloc(max_in_frames * BYTES_PER_FRAME); process.max_in_frames = max_in_frames; } if (process.max_out_frames != max_out_frames) { LOG_DEBUG("creating process buf out frames: %u", max_out_frames); if (process.outbuf) free(process.outbuf); process.outbuf = malloc(max_out_frames * BYTES_PER_FRAME); process.max_out_frames = max_out_frames; } if (!process.inbuf || !process.outbuf) { LOG_ERROR("malloc fail creating process buffers"); *direct = true; return raw_sample_rate; } return process.out_sample_rate; } return raw_sample_rate; } // process flush - called with decode mutex set void process_flush(void) { LOG_INFO("process flush"); FLUSH_FUNC(); process.in_frames = 0; } // init - called with no mutex void process_init(char *opt) { bool enabled = INIT_FUNC(opt); memset(&process, 0, sizeof(process)); if (enabled) { LOCK_D; decode.process = true; UNLOCK_D; } } #endif // #if PROCESS squeezelite-1.8/resample.c 0000664 0000000 0000000 00000023630 12463437225 0015711 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // upsampling using libsoxr - only included if RESAMPLE set #include "squeezelite.h" #if RESAMPLE #include #include extern log_level loglevel; struct soxr { soxr_t resampler; size_t old_clips; unsigned long q_recipe; unsigned long q_flags; double q_precision; /* Conversion precision (in bits). 20 */ double q_phase_response; /* 0=minimum, ... 50=linear, ... 100=maximum 50 */ double q_passband_end; /* 0dB pt. bandwidth to preserve; nyquist=1 0.913 */ double q_stopband_begin; /* Aliasing/imaging control; > passband_end 1 */ double scale; bool max_rate; bool exception; #if !LINKALL // soxr symbols to be dynamically loaded soxr_io_spec_t (* soxr_io_spec)(soxr_datatype_t itype, soxr_datatype_t otype); soxr_quality_spec_t (* soxr_quality_spec)(unsigned long recipe, unsigned long flags); soxr_t (* soxr_create)(double, double, unsigned, soxr_error_t *, soxr_io_spec_t const *, soxr_quality_spec_t const *, soxr_runtime_spec_t const *); void (* soxr_delete)(soxr_t); soxr_error_t (* soxr_process)(soxr_t, soxr_in_t, size_t, size_t *, soxr_out_t, size_t olen, size_t *); size_t *(* soxr_num_clips)(soxr_t); #if RESAMPLE_MP soxr_runtime_spec_t (* soxr_runtime_spec)(unsigned num_threads); #endif // soxr_strerror is a macro so not included here #endif }; static struct soxr *r; #if LINKALL #define SOXR(h, fn, ...) (soxr_ ## fn)(__VA_ARGS__) #else #define SOXR(h, fn, ...) (h)->soxr_##fn(__VA_ARGS__) #endif void resample_samples(struct processstate *process) { size_t idone, odone; size_t clip_cnt; soxr_error_t error = 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) { // should not get here if buffers are big enough... LOG_ERROR("should not get here - partial sox process: %u of %u processed %u of %u out", (unsigned)idone, process->in_frames, (unsigned)odone, process->max_out_frames); } process->out_frames = odone; process->total_in += idone; process->total_out += odone; clip_cnt = *(SOXR(r, num_clips, r->resampler)); if (clip_cnt - r->old_clips) { LOG_SDEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips)); r->old_clips = clip_cnt; } } bool resample_drain(struct processstate *process) { size_t odone; size_t clip_cnt; 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; process->total_out += odone; clip_cnt = *(SOXR(r, num_clips, r->resampler)); if (clip_cnt - r->old_clips) { LOG_DEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips)); r->old_clips = clip_cnt; } if (odone == 0) { LOG_INFO("resample track complete - total track clips: %u", r->old_clips); SOXR(r, delete, r->resampler); r->resampler = NULL; return true; } else { return false; } } bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) { unsigned outrate = 0; int i; if (r->exception) { // find direct match - avoid resampling for (i = 0; supported_rates[i]; i++) { if (raw_sample_rate == supported_rates[i]) { outrate = raw_sample_rate; break; } } // else find next highest sync sample rate while (!outrate && i >= 0) { if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) { outrate = supported_rates[i]; break; } i--; } } if (!outrate) { if (r->max_rate) { // resample to max rate for device outrate = supported_rates[0]; } else { // resample to max sync sample rate for (i = 0; supported_rates[i]; i++) { if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) { outrate = supported_rates[i]; break; } } } if (!outrate) { outrate = supported_rates[0]; } } process->in_sample_rate = raw_sample_rate; process->out_sample_rate = outrate; if (r->resampler) { SOXR(r, delete, r->resampler); r->resampler = NULL; } if (raw_sample_rate != outrate) { soxr_io_spec_t io_spec; soxr_quality_spec_t q_spec; soxr_error_t error; #if RESAMPLE_MP soxr_runtime_spec_t r_spec; #endif LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate); io_spec = SOXR(r, io_spec, SOXR_INT32_I, SOXR_INT32_I); io_spec.scale = r->scale; q_spec = SOXR(r, quality_spec, r->q_recipe, r->q_flags); if (r->q_precision > 0) { q_spec.precision = r->q_precision; } if (r->q_passband_end > 0) { q_spec.passband_end = r->q_passband_end; } if (r->q_stopband_begin > 0) { q_spec.stopband_begin = r->q_stopband_begin; } if (r->q_phase_response > -1) { q_spec.phase_response = r->q_phase_response; } #if RESAMPLE_MP r_spec = SOXR(r, runtime_spec, 0); // make use of libsoxr OpenMP support allowing parallel execution if multiple cores #endif LOG_DEBUG("resampling with soxr_quality_spec_t[precision: %03.1f, passband_end: %03.6f, stopband_begin: %03.6f, " "phase_response: %03.1f, flags: 0x%02x], soxr_io_spec_t[scale: %03.2f]", q_spec.precision, q_spec.passband_end, q_spec.stopband_begin, q_spec.phase_response, q_spec.flags, io_spec.scale); #if RESAMPLE_MP r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, &r_spec); #else r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, NULL); #endif if (error) { LOG_INFO("soxr_create error: %s", soxr_strerror(error)); return false; } r->old_clips = 0; return true; } else { LOG_INFO("disable resampling - rates match"); return false; } } void resample_flush(void) { if (r->resampler) { SOXR(r, delete, r->resampler); r->resampler = NULL; } } static bool load_soxr(void) { #if !LINKALL void *handle = dlopen(LIBSOXR, RTLD_NOW); char *err; if (!handle) { LOG_INFO("dlerror: %s", dlerror()); return false; } r->soxr_io_spec = dlsym(handle, "soxr_io_spec"); r->soxr_quality_spec = dlsym(handle, "soxr_quality_spec"); r->soxr_create = dlsym(handle, "soxr_create"); r->soxr_delete = dlsym(handle, "soxr_delete"); r->soxr_process = dlsym(handle, "soxr_process"); r->soxr_num_clips = dlsym(handle, "soxr_num_clips"); #if RESAMPLE_MP r->soxr_runtime_spec = dlsym(handle, "soxr_runtime_spec"); #endif if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBSOXR); #endif return true; } bool resample_init(char *opt) { char *recipe = NULL, *flags = NULL; char *atten = NULL; char *precision = NULL, *passband_end = NULL, *stopband_begin = NULL, *phase_response = NULL; r = malloc(sizeof(struct soxr)); if (!r) { LOG_WARN("resampling disabled"); return false; } r->resampler = NULL; r->old_clips = 0; r->max_rate = false; r->exception = false; if (!load_soxr()) { LOG_WARN("resampling disabled"); return false; } if (opt) { recipe = next_param(opt, ':'); flags = next_param(NULL, ':'); atten = next_param(NULL, ':'); precision = next_param(NULL, ':'); passband_end = next_param(NULL, ':'); stopband_begin = next_param(NULL, ':'); phase_response = next_param(NULL, ':'); } // default to HQ (20 bit) if not user specified r->q_recipe = SOXR_HQ; r->q_flags = 0; // default to 1db of attenuation if not user specified r->scale = pow(10, -1.0 / 20); // override recipe derived values with user specified values r->q_precision = 0; r->q_passband_end = 0; r->q_stopband_begin = 0; r->q_phase_response = -1; if (recipe && recipe[0] != '\0') { if (strchr(recipe, 'v')) r->q_recipe = SOXR_VHQ; if (strchr(recipe, 'h')) r->q_recipe = SOXR_HQ; if (strchr(recipe, 'm')) r->q_recipe = SOXR_MQ; if (strchr(recipe, 'l')) r->q_recipe = SOXR_LQ; if (strchr(recipe, 'q')) r->q_recipe = SOXR_QQ; if (strchr(recipe, 'L')) r->q_recipe |= SOXR_LINEAR_PHASE; if (strchr(recipe, 'I')) r->q_recipe |= SOXR_INTERMEDIATE_PHASE; if (strchr(recipe, 'M')) r->q_recipe |= SOXR_MINIMUM_PHASE; if (strchr(recipe, 's')) r->q_recipe |= SOXR_STEEP_FILTER; // X = async resampling to max_rate if (strchr(recipe, 'X')) r->max_rate = true; // E = exception, only resample if native rate is not supported if (strchr(recipe, 'E')) r->exception = true; } if (flags) { r->q_flags = strtoul(flags, 0, 16); } if (atten) { double scale = pow(10, -atof(atten) / 20); if (scale > 0 && scale <= 1.0) { r->scale = scale; } } if (precision) { r->q_precision = atof(precision); } if (passband_end) { r->q_passband_end = atof(passband_end) / 100; } if (stopband_begin) { r->q_stopband_begin = atof(stopband_begin) / 100; } if (phase_response) { r->q_phase_response = atof(phase_response); } LOG_INFO("resampling %s recipe: 0x%02x, flags: 0x%02x, scale: %03.2f, precision: %03.1f, passband_end: %03.5f, stopband_begin: %03.5f, phase_response: %03.1f", r->max_rate ? "async" : "sync", r->q_recipe, r->q_flags, r->scale, r->q_precision, r->q_passband_end, r->q_stopband_begin, r->q_phase_response); return true; } #endif // #if RESAMPLE squeezelite-1.8/slimproto.c 0000664 0000000 0000000 00000061562 12463437225 0016137 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #include "slimproto.h" static log_level loglevel; #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]) { const char *base_cap = "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION; struct HELO_packet pkt; 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); if (now > status.updated) ms_played += (now - status.updated); 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; 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; LOG_INFO("switch server"); new_server = serv->server_ip; 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) && !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; } #if PORTAUDIO if (output.pa_reopen) { _pa_open(); output.pa_reopen = false; } #endif 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; } 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(void) { struct sockaddr_in d; struct sockaddr_in s; char *buf; struct pollfd pollinfo; 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)); } } 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) { 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; int i; wake_create(wake_e); loglevel = level; running = true; if (server) { server_addr(server, &slimproto_ip, &slimproto_port); } if (!slimproto_ip) { slimproto_ip = discover_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, 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) { 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) { 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(); } } 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); } closesocket(sock); } } void slimproto_stop(void) { LOG_INFO("slimproto stop"); running = false; } squeezelite-1.8/slimproto.h 0000664 0000000 0000000 00000007016 12463437225 0016136 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // packet formats for slimproto #pragma pack(push, 1) // 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; }; #pragma pack(pop) squeezelite-1.8/squeezelite.h 0000664 0000000 0000000 00000044607 12463437225 0016454 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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) #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 #else #error unknown target #endif #if LINUX && !defined(PORTAUDIO) #define ALSA 1 #define PORTAUDIO 0 #else #define ALSA 0 #define PORTAUDIO 1 #endif #if LINUX && !defined(SELFPIPE) #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 #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 && 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 !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__) #include #include #include #include #include #include #include #if LINUX || OSX || FREEBSD #include #include #include #include #include #include #include #include #include #include #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 #define ERROR_WOULDBLOCK EWOULDBLOCK typedef u_int8_t u8_t; typedef u_int16_t u16_t; typedef u_int32_t u32_t; typedef u_int64_t u64_t; 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) #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) #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 #include #include #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 #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 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 #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; 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 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); 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; typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE } output_format; typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state; typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir; typedef enum { FADE_NONE = 0, FADE_CROSSFADE, FADE_IN, FADE_OUT, FADE_INOUT } fade_mode; #define MAX_SUPPORTED_SAMPLERATES 16 #define TEST_RATES = { 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 } 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 osx_playnice; #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 bool next_dop; // set in decode thread bool dop; bool has_dop; // set in dop_init - output device supports dop unsigned dop_delay; // set in dop_init - delay in ms switching to/from dop #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[]); void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute); void output_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, unsigned idle); void output_close_pa(void); void _pa_open(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_flac_dop(u32_t *lptr, u32_t *rptr, 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 // 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); // 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 squeezelite-1.8/stream.c 0000664 0000000 0000000 00000023404 12463437225 0015373 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // stream thread #include "squeezelite.h" #include 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; static void send_header(void) { char *ptr = stream.header; int len = stream.header_len; unsigned try = 0; ssize_t n; while (len) { n = send(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; } LOG_SDEBUG("wrote %d bytes to socket", n); ptr += n; len -= n; } LOG_SDEBUG("wrote header"); } static bool running = true; static void _disconnect(stream_state state, disconnect_code disconnect) { stream.state = state; stream.disconnect = disconnect; 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(&pollinfo, 1, 100)) { LOCK; // check socket has not been closed while in poll if (fd < 0) { UNLOCK; continue; } if ((pollinfo.revents & POLLOUT) && stream.state == SEND_HEADERS) { send_header(); stream.header_len = 0; stream.state = RECV_HEADERS; 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(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(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(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(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; } } 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"); } } 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); } 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 pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + STREAM_THREAD_STACK_SIZE); 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 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; } 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 (fd != -1) { closesocket(fd); fd = -1; disc = true; } stream.state = STOPPED; UNLOCK; return disc; } squeezelite-1.8/utils.c 0000664 0000000 0000000 00000020715 12463437225 0015242 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #if LINUX || OSX || FREEBSD #include #include #include #if FREEBSD #include #include #include #endif #endif #if WIN #include #endif #if OSX #include #include #include #include #endif #include // 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 struct timespec ts; if (!clock_gettime(CLOCK_MONOTONIC, &ts)) { 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 // 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]; 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 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 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 } // 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; } // this only implements numfds == 1 int poll(struct pollfd *fds, unsigned long numfds, int timeout) { fd_set r, w; struct timeval tv; int ret; FD_ZERO(&r); FD_ZERO(&w); if (fds[0].events & POLLIN) FD_SET(fds[0].fd, &r); if (fds[0].events & POLLOUT) FD_SET(fds[0].fd, &w); tv.tv_sec = timeout / 1000; 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; if (FD_ISSET(fds[0].fd, &r)) fds[0].revents |= POLLIN; if (FD_ISSET(fds[0].fd, &w)) fds[0].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 squeezelite-1.8/vorbis.c 0000664 0000000 0000000 00000020623 12463437225 0015404 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" // automatically select between floating point (preferred) and fixed point libraries: // NOTE: works with Tremor version here: http://svn.xiph.org/trunk/Tremor, not vorbisidec.1.0.2 currently in ubuntu // we take common definations from even though we can use tremor at run time // tremor's OggVorbis_File struct is normally smaller so this is ok, but padding added to malloc in case it is bigger #define OV_EXCLUDE_STATIC_CALLBACKS #include struct vorbis { OggVorbis_File *vf; bool opened; #if !LINKALL // vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library vorbis_info *(* ov_info)(OggVorbis_File *vf, int link); int (* ov_clear)(OggVorbis_File *vf); long (* ov_read)(OggVorbis_File *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream); long (* ov_read_tremor)(OggVorbis_File *vf, char *buffer, int length, int *bitstream); int (* ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks); #endif }; static struct vorbis *v; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex) #define IF_DIRECT(x) if (decode.direct) { x } #define IF_PROCESS(x) if (!decode.direct) { x } #else #define LOCK_O_direct mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct #define UNLOCK_O_not_direct #define IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define OV(h, fn, ...) (ov_ ## fn)(__VA_ARGS__) #define TREMOR(h) 0 extern int ov_read_tremor(); // needed to enable compilation, not linked #else #define OV(h, fn, ...) (h)->ov_##fn(__VA_ARGS__) #define TREMOR(h) (h)->ov_read_tremor #endif // called with mutex locked within vorbis_decode to avoid locking O before S static size_t _read_cb(void *ptr, size_t size, size_t nmemb, void *datasource) { size_t bytes; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, size * nmemb); memcpy(ptr, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); return bytes / size; } // these are needed for older versions of tremor, later versions and libvorbis allow NULL to be used static int _seek_cb(void *datasource, ogg_int64_t offset, int whence) { return -1; } static int _close_cb(void *datasource) { return 0; } static long _tell_cb(void *datasource) { return 0; } static decode_state vorbis_decode(void) { static int channels; bool end; frames_t frames; int bytes, s, n; u8_t *write_buf; LOCK_S; LOCK_O_direct; end = (stream.state <= DISCONNECT); IF_DIRECT( frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; ); IF_PROCESS( frames = process.max_in_frames; ); if (!frames && end) { UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } if (decode.new_stream) { ov_callbacks cbs; int err; struct vorbis_info *info; cbs.read_func = _read_cb; if (TREMOR(v)) { cbs.seek_func = _seek_cb; cbs.close_func = _close_cb; cbs.tell_func = _tell_cb; } else { cbs.seek_func = NULL; cbs.close_func = NULL; cbs.tell_func = NULL; } if ((err = OV(v, open_callbacks, streambuf, v->vf, NULL, 0, cbs)) < 0) { LOG_WARN("open_callbacks error: %d", err); UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } v->opened = true; info = OV(v, info, v->vf, -1); LOG_INFO("setting track_start"); LOCK_O_not_direct; output.next_sample_rate = decode_newstream(info->rate, output.supported_rates); IF_DSD( output.next_dop = false; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O_not_direct; IF_PROCESS( frames = process.max_in_frames; ); channels = info->channels; if (channels > 2) { LOG_WARN("too many channels: %d", channels); UNLOCK_O_direct; UNLOCK_S; return DECODE_ERROR; } } bytes = frames * 2 * channels; // samples returned are 16 bits IF_DIRECT( write_buf = outputbuf->writep; ); IF_PROCESS( write_buf = process.inbuf; ); // write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them if (!TREMOR(v)) { #if SL_LITTLE_ENDIAN n = OV(v, read, v->vf, (char *)write_buf, bytes, 0, 2, 1, &s); #else n = OV(v, read, v->vf, (char *)write_buf, bytes, 1, 2, 1, &s); #endif } else { n = OV(v, read_tremor, v->vf, (char *)write_buf, bytes, &s); } if (n > 0) { frames_t count; s16_t *iptr; s32_t *optr; frames = n / 2 / channels; count = frames * channels; // work backward to unpack samples to 4 bytes per sample iptr = (s16_t *)write_buf + count; optr = (s32_t *)write_buf + frames * 2; if (channels == 2) { while (count--) { *--optr = *--iptr << 16; } } else if (channels == 1) { while (count--) { *--optr = *--iptr << 16; *--optr = *iptr << 16; } } IF_DIRECT( _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames = frames; ); LOG_SDEBUG("wrote %u frames", frames); } else if (n == 0) { LOG_INFO("end of stream"); UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } else if (n == OV_HOLE) { // recoverable hole in stream, seen when skipping LOG_DEBUG("hole in stream"); } else { LOG_INFO("ov_read error: %d", n); UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } UNLOCK_O_direct; UNLOCK_S; return DECODE_RUNNING; } static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { if (!v->vf) { v->vf = malloc(sizeof(OggVorbis_File) + 128); // add some padding as struct size may be larger memset(v->vf, 0, sizeof(OggVorbis_File) + 128); } else { if (v->opened) { OV(v, clear, v->vf); v->opened = false; } } } static void vorbis_close(void) { if (v->opened) { OV(v, clear, v->vf); v->opened = false; } free(v->vf); v->vf = NULL; } static bool load_vorbis() { #if !LINKALL void *handle = dlopen(LIBVORBIS, RTLD_NOW); char *err; bool tremor = false; if (!handle) { handle = dlopen(LIBTREMOR, RTLD_NOW); if (handle) { tremor = true; } else { LOG_INFO("dlerror: %s", dlerror()); return false; } } v->ov_read = tremor ? NULL : dlsym(handle, "ov_read"); v->ov_read_tremor = tremor ? dlsym(handle, "ov_read") : NULL; v->ov_info = dlsym(handle, "ov_info"); v->ov_clear = dlsym(handle, "ov_clear"); v->ov_open_callbacks = dlsym(handle, "ov_open_callbacks"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded %s", tremor ? LIBTREMOR : LIBVORBIS); #endif return true; } struct codec *register_vorbis(void) { static struct codec ret = { 'o', // id "ogg", // types 2048, // min read 20480, // min space vorbis_open, // open vorbis_close, // close vorbis_decode,// decode }; v = malloc(sizeof(struct vorbis)); if (!v) { return NULL; } v->vf = NULL; v->opened = false; if (!load_vorbis()) { return NULL; } LOG_INFO("using vorbis to decode ogg"); return &ret; }
::\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n" #endif #if PORTAUDIO #if OSX " -a :\t\tSpecify Portaudio params to open output device, l = target latency in ms, r = allow OSX to resample (0|1)\n" #else " -a \t\tSpecify Portaudio params to open output device, l = target latency in ms\n" #endif #endif " -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 " -R -u [params]\tResample, params = ::::::,\n" " \t\t\t recipe = (v|h|m|l|q)(L|I|M)(s) [E|X], E = exception - resample only if native rate not supported, X = async - resample to max rate for device, otherwise to max sync rate\n" " \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 " -D [delay]\t\tOutput device supports DSD over PCM (DoP), delay = optional delay switching between PCM and DoP in ms\n" #endif #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 " LINUX" #endif #if WIN " WIN" #endif #if OSX " OSX" #endif #if FREEBSD " FREEBSD" #endif #if ALSA " ALSA" #endif #if PORTAUDIO " PORTAUDIO" #endif #if EVENTFD " EVENTFD" #endif #if SELFPIPE " SELFPIPE" #endif #if WINEVENT " WINEVENT" #endif #if RESAMPLE_MP " RESAMPLE_MP" #else #if RESAMPLE " RESAMPLE" #endif #endif #if FFMPEG " FFMPEG" #endif #if VISEXPORT " VISEXPORT" #endif #if IR " IR" #endif #if DSD " DSD" #endif #if LINKALL " LINKALL" #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 .\n\n" #if DSD "Contains dsd2pcm library Copyright 2009, 2011 Sebastian Gesemann which\n" "is subject to its own license.\n\n" #endif ); } 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; 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 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; unsigned dop_delay = 0; #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 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 "UV" #endif , opt) && optind < argc - 1) { optarg = argv[optind + 1]; optind += 2; } else if (strstr("ltz?" #if ALSA "L" #endif #if RESAMPLE "uR" #endif #if DSD "D" #endif #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(1); } switch (opt[0]) { case 'o': output_device = optarg; 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 - or , 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); } } break; case 's': server = optarg; break; case 'n': name = optarg; break; case 'N': namefile = optarg; 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 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': if (optind < argc && argv[optind] && argv[optind][0] != '-') { resample = argv[optind++]; } else { resample = ""; } break; #endif #if DSD case 'D': dop = true; if (optind < argc && argv[optind] && argv[optind][0] != '-') { dop_delay = atoi(argv[optind++]); } break; #endif #if VISEXPORT case 'v': 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; 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 // 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 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 winsock_init(); #endif stream_init(log_stream, stream_buf_size); 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, output_mixer, output_mixer_unmute); #endif #if PORTAUDIO output_init_pa(log_output, output_device, output_buf_size, output_params, rates, rate_delay, idle); #endif } #if DSD dop_init(dop, dop_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); decode_close(); stream_close(); if (!strcmp(output_device, "-")) { output_close_stdout(); } else { #if ALSA output_close_alsa(); #endif #if PORTAUDIO output_close_pa(); #endif } #if IR ir_close(); #endif #if WIN winsock_close(); #endif #if LINUX || FREEBSD if (pidfile) { unlink(pidfile); free(pidfile); } #endif exit(0); } squeezelite-1.8/mpg.c 0000664 0000000 0000000 00000015643 12463437225 0014671 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #include #define READ_SIZE 512 #define WRITE_SIZE 32 * 1024 struct mpg { mpg123_handle *h; bool use16bit; #if !LINKALL // mpg symbols to be dynamically loaded int (* mpg123_init)(void); int (* mpg123_feature)(const enum mpg123_feature_set); void (* mpg123_rates)(const long **, size_t *); int (* mpg123_format_none)(mpg123_handle *); int (* mpg123_format)(mpg123_handle *, long, int, int); mpg123_handle *(* mpg123_new)(const char*, int *); void (* mpg123_delete)(mpg123_handle *); int (* mpg123_open_feed)(mpg123_handle *); int (* mpg123_decode)(mpg123_handle *, const unsigned char *, size_t, unsigned char *, size_t, size_t *); int (* mpg123_getformat)(mpg123_handle *, long *, int *, int *); const char* (* mpg123_plain_strerror)(int); #endif }; static struct mpg *m; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex) #define IF_DIRECT(x) if (decode.direct) { x } #define IF_PROCESS(x) if (!decode.direct) { x } #else #define LOCK_O_direct mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct #define UNLOCK_O_not_direct #define IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define MPG123(h, fn, ...) (mpg123_ ## fn)(__VA_ARGS__) #else #define MPG123(h, fn, ...) (h)->mpg123_##fn(__VA_ARGS__) #endif static decode_state mpg_decode(void) { size_t bytes, space, size; int ret; u8_t *write_buf; LOCK_S; LOCK_O_direct; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); IF_DIRECT( space = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)); write_buf = outputbuf->writep; ); IF_PROCESS( space = process.max_in_frames; write_buf = process.inbuf; ); bytes = min(bytes, READ_SIZE); space = min(space, WRITE_SIZE); if (m->use16bit) { space = (space / BYTES_PER_FRAME) * 4; } // only get the new stream information on first call so we can reset decode.direct appropriately if (decode.new_stream) { space = 0; } ret = MPG123(m, decode, m->h, streambuf->readp, bytes, write_buf, space, &size); if (ret == MPG123_NEW_FORMAT) { if (decode.new_stream) { long rate; int channels, enc; MPG123(m, getformat, m->h, &rate, &channels, &enc); LOG_INFO("setting track_start"); LOCK_O_not_direct; output.next_sample_rate = decode_newstream(rate, output.supported_rates); IF_DSD( output.next_dop = false; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O_not_direct; } else { LOG_WARN("format change mid stream - not supported"); } } // expand 16bit output to 32bit samples if (m->use16bit) { s16_t *iptr; s32_t *optr; size_t count = size / 2; size = count * 4; iptr = (s16_t *)write_buf + count; optr = (s32_t *)write_buf + count; while (count--) { *--optr = *--iptr << 16; } } _buf_inc_readp(streambuf, bytes); IF_DIRECT( _buf_inc_writep(outputbuf, size); ); IF_PROCESS( process.in_frames = size / BYTES_PER_FRAME; ); UNLOCK_O_direct; LOG_SDEBUG("write %u frames", size / BYTES_PER_FRAME); if (ret == MPG123_DONE || (bytes == 0 && size == 0 && stream.state <= DISCONNECT)) { UNLOCK_S; LOG_INFO("stream complete"); return DECODE_COMPLETE; } UNLOCK_S; if (ret == MPG123_ERR) { LOG_WARN("Error"); return DECODE_COMPLETE; } // OK and NEED_MORE keep running return DECODE_RUNNING; } static void mpg_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { int err; const long *list; size_t count, i; if (m->h) { MPG123(m, delete, m->h); } m->h = MPG123(m, new, NULL, &err); if (m->h == NULL) { LOG_WARN("new error: %s", MPG123(m, plain_strerror, err)); } // restrict output to 32bit or 16bit signed 2 channel based on library capability MPG123(m, rates, &list, &count); MPG123(m, format_none, m->h); for (i = 0; i < count; i++) { MPG123(m, format, m->h, list[i], 2, m->use16bit ? MPG123_ENC_SIGNED_16 : MPG123_ENC_SIGNED_32); } err = MPG123(m, open_feed, m->h); if (err) { LOG_WARN("open feed error: %s", MPG123(m, plain_strerror, err)); } } static void mpg_close(void) { MPG123(m, delete, m->h); m->h = NULL; } static bool load_mpg() { #if !LINKALL void *handle = dlopen(LIBMPG, RTLD_NOW); char *err; if (!handle) { LOG_INFO("dlerror: %s", dlerror()); return false; } m->mpg123_init = dlsym(handle, "mpg123_init"); m->mpg123_feature = dlsym(handle, "mpg123_feature"); m->mpg123_rates = dlsym(handle, "mpg123_rates"); m->mpg123_format_none = dlsym(handle, "mpg123_format_none"); m->mpg123_format = dlsym(handle, "mpg123_format"); m->mpg123_new = dlsym(handle, "mpg123_new"); m->mpg123_delete = dlsym(handle, "mpg123_delete"); m->mpg123_open_feed = dlsym(handle, "mpg123_open_feed"); m->mpg123_decode = dlsym(handle, "mpg123_decode"); m->mpg123_getformat = dlsym(handle, "mpg123_getformat"); m->mpg123_plain_strerror = dlsym(handle, "mpg123_plain_strerror"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBMPG); #endif return true; } struct codec *register_mpg(void) { static struct codec ret = { 'm', // id "mp3", // types READ_SIZE, // min read WRITE_SIZE, // min space mpg_open, // open mpg_close, // close mpg_decode, // decode }; m = malloc(sizeof(struct mpg)); if (!m) { return NULL; } m->h = NULL; if (!load_mpg()) { return NULL; } MPG123(m, init); m->use16bit = MPG123(m, feature, MPG123_FEATURE_OUTPUT_32BIT); LOG_INFO("using mpg to decode mp3"); return &ret; } squeezelite-1.8/output.c 0000664 0000000 0000000 00000033715 12463437225 0015446 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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_dop; #endif #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.dop != output.next_dop) { delay = output.dop_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.dop = output.next_dop; ) if (!output.fade == FADE_ACTIVE || !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.dop) { 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) { bytes /= 2; } 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_dop = malloc(MAX_SILENCE_FRAMES * BYTES_PER_FRAME); if (!silencebuf_dop) { LOG_ERROR("unable to malloc silence dop buffer"); exit(0); } dop_silence_frames((u32_t *)silencebuf_dop, 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; if (!rates[0]) { if (!test_open(output.device, output.supported_rates)) { LOG_ERROR("unable to open output device"); exit(0); } } else { 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_dop); ) } 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; } squeezelite-1.8/output_alsa.c 0000664 0000000 0000000 00000061216 12463437225 0016443 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Output using Alsa #include "squeezelite.h" #if ALSA #include #include #include #include #define MAX_DEVICE_LEN 128 static snd_pcm_format_t fmts[] = { SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_UNKNOWN }; #if SL_LITTLE_ENDIAN #define NATIVE_FORMAT SND_PCM_FORMAT_S32_LE #else #define NATIVE_FORMAT SND_PCM_FORMAT_S32_BE #endif // ouput device static struct { char device[MAX_DEVICE_LEN + 1]; snd_pcm_format_t format; snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t period_size; unsigned rate; bool mmap; bool reopen; u8_t *write_buf; const char *volume_mixer_name; int volume_mixer_index; } alsa; static snd_pcm_t *pcmp = NULL; extern u8_t *silencebuf; #if DSD extern u8_t *silencebuf_dop; #endif 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) void list_devices(void) { void **hints, **n; if (snd_device_name_hint(-1, "pcm", &hints) >= 0) { n = hints; printf("Output devices:\n"); while (*n) { char *name = snd_device_name_get_hint(*n, "NAME"); char *desc = snd_device_name_get_hint(*n, "DESC"); if (name) printf(" %-30s", name); if (desc) { char *s1 = strtok(desc, "\n"); char *s2 = strtok(NULL, "\n"); if (s1) printf(" - %s", s1); if (s2) printf(" - %s", s2); } printf("\n"); if (name) free(name); if (desc) free(desc); n++; } snd_device_name_free_hint(hints); } 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) { fprintf(stderr, "%s ALSA %s:%d ", logtime(), function, line); va_start(args, fmt); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); fflush(stderr); } return NULL; } static void alsa_close(void) { int err; if ((err = snd_pcm_close(pcmp)) < 0) { LOG_INFO("snd_pcm_close error: %s", snd_strerror(err)); } } bool test_open(const char *device, unsigned rates[]) { int err; snd_pcm_t *pcm; snd_pcm_hw_params_t *hw_params; hw_params = (snd_pcm_hw_params_t *) alloca(snd_pcm_hw_params_sizeof()); memset(hw_params, 0, snd_pcm_hw_params_sizeof()); // open device if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { LOG_ERROR("playback open error: %s", snd_strerror(err)); return false; } // get max params if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0) { LOG_ERROR("hwparam init error: %s", snd_strerror(err)); return false; } // find supported sample rates to enable client side resampling of non supported rates unsigned i, ind; unsigned ref[] TEST_RATES; for (i = 0, ind = 0; ref[i]; ++i) { if (snd_pcm_hw_params_test_rate(pcm, hw_params, ref[i], 0) == 0) { rates[ind++] = ref[i]; } } if ((err = snd_pcm_close(pcm)) < 0) { LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); return false; } return true; } static bool pcm_probe(const char *device) { int err; snd_pcm_t *pcm; if ((err = snd_pcm_open(&pcm, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { return false; } if ((err = snd_pcm_close(pcm)) < 0) { LOG_ERROR("snd_pcm_close error: %s", snd_strerror(err)); } return true; } static int alsa_open(const char *device, unsigned sample_rate, unsigned alsa_buffer, unsigned alsa_period) { int err; snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca(&hw_params); // close if already open if (pcmp) alsa_close(); // reset params alsa.rate = 0; alsa.period_size = 0; strcpy(alsa.device, device); if (strlen(device) > MAX_DEVICE_LEN - 4 - 1) { LOG_ERROR("device name too long: %s", device); return -1; } LOG_INFO("opening device at: %u", sample_rate); bool retry; do { // open device if ((err = snd_pcm_open(&pcmp, alsa.device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { LOG_ERROR("playback open error: %s", snd_strerror(err)); return err; } // init params memset(hw_params, 0, snd_pcm_hw_params_sizeof()); if ((err = snd_pcm_hw_params_any(pcmp, hw_params)) < 0) { LOG_ERROR("hwparam init error: %s", snd_strerror(err)); return err; } // open hw: devices without resampling, if sample rate fails try plughw: with resampling bool hw = !strncmp(alsa.device, "hw:", 3); retry = false; if ((err = snd_pcm_hw_params_set_rate_resample(pcmp, hw_params, !hw)) < 0) { LOG_ERROR("resampling setup failed: %s", snd_strerror(err)); return err; } if ((err = snd_pcm_hw_params_set_rate(pcmp, hw_params, sample_rate, 0)) < 0) { if (hw) { strcpy(alsa.device + 4, device); memcpy(alsa.device, "plug", 4); LOG_INFO("reopening device %s in plug mode as %s for resampling", device, alsa.device); snd_pcm_close(pcmp); retry = true; } } } while (retry); // set access if (!alsa.mmap || snd_pcm_hw_params_set_access(pcmp, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) { if ((err = snd_pcm_hw_params_set_access(pcmp, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { LOG_ERROR("access type not available: %s", snd_strerror(err)); return err; } alsa.mmap = false; } // set the sample format snd_pcm_format_t *fmt = alsa.format ? &alsa.format : (snd_pcm_format_t *)fmts; do { if (snd_pcm_hw_params_set_format(pcmp, hw_params, *fmt) >= 0) { LOG_INFO("opened device %s using format: %s sample rate: %u mmap: %u", alsa.device, snd_pcm_format_name(*fmt), sample_rate, alsa.mmap); alsa.format = *fmt; break; } if (alsa.format) { LOG_ERROR("unable to open audio device requested format: %s", snd_pcm_format_name(alsa.format)); return -1; } ++fmt; if (*fmt == SND_PCM_FORMAT_UNKNOWN) { LOG_ERROR("unable to open audio device with any supported format"); return -1; } } while (*fmt != SND_PCM_FORMAT_UNKNOWN); // set the output format to be used by _scale_and_pack switch(alsa.format) { case SND_PCM_FORMAT_S32_LE: output.format = S32_LE; break; case SND_PCM_FORMAT_S24_LE: output.format = S24_LE; break; case SND_PCM_FORMAT_S24_3LE: output.format = S24_3LE; break; case SND_PCM_FORMAT_S16_LE: output.format = S16_LE; break; default: break; } // set channels if ((err = snd_pcm_hw_params_set_channels (pcmp, hw_params, 2)) < 0) { LOG_ERROR("channel count not available: %s", snd_strerror(err)); return err; } // set period size - value of < 50 treated as period count, otherwise size in bytes if (alsa_period < 50) { unsigned count = alsa_period; if ((err = snd_pcm_hw_params_set_periods_near(pcmp, hw_params, &count, 0)) < 0) { LOG_ERROR("unable to set period count %s", snd_strerror(err)); return err; } } else { snd_pcm_uframes_t size = alsa_period; int dir = 0; if ((err = snd_pcm_hw_params_set_period_size_near(pcmp, hw_params, &size, &dir)) < 0) { LOG_ERROR("unable to set period size %s", snd_strerror(err)); return err; } } // set buffer size - value of < 500 treated as buffer time in ms, otherwise size in bytes if (alsa_buffer < 500) { unsigned time = alsa_buffer * 1000; int dir = 0; if ((err = snd_pcm_hw_params_set_buffer_time_near(pcmp, hw_params, &time, &dir)) < 0) { LOG_ERROR("unable to set buffer time %s", snd_strerror(err)); return err; } } else { snd_pcm_uframes_t size = alsa_buffer; if ((err = snd_pcm_hw_params_set_buffer_size_near(pcmp, hw_params, &size)) < 0) { LOG_ERROR("unable to set buffer size %s", snd_strerror(err)); return err; } } // get period_size if ((err = snd_pcm_hw_params_get_period_size(hw_params, &alsa.period_size, 0)) < 0) { LOG_ERROR("unable to get period size: %s", snd_strerror(err)); return err; } // get buffer_size if ((err = snd_pcm_hw_params_get_buffer_size(hw_params, &alsa.buffer_size)) < 0) { LOG_ERROR("unable to get buffer size: %s", snd_strerror(err)); return err; } LOG_INFO("buffer: %u period: %u -> buffer size: %u period size: %u", alsa_buffer, alsa_period, alsa.buffer_size, alsa.period_size); // ensure we have two buffer sizes of samples before starting output output.start_frames = alsa.buffer_size * 2; // create an intermediate buffer for non mmap case for all but NATIVE_FORMAT // this is used to pack samples into the output format before calling writei if (!alsa.mmap && !alsa.write_buf && alsa.format != NATIVE_FORMAT) { alsa.write_buf = malloc(alsa.buffer_size * BYTES_PER_FRAME); if (!alsa.write_buf) { LOG_ERROR("unable to malloc write_buf"); return -1; } } // set params if ((err = snd_pcm_hw_params(pcmp, hw_params)) < 0) { LOG_ERROR("unable to set hw params: %s", snd_strerror(err)); return err; } // dump info if (loglevel == lSDEBUG) { static snd_output_t *debug_output; snd_output_stdio_attach(&debug_output, stderr, 0); snd_pcm_dump(pcmp, debug_output); } // this indicates we have opened the device ok alsa.rate = sample_rate; return 0; } static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { const snd_pcm_channel_area_t *areas; snd_pcm_uframes_t offset; void *outputptr; s32_t *inputptr; int err; if (alsa.mmap) { snd_pcm_uframes_t alsa_frames = (snd_pcm_uframes_t)out_frames; snd_pcm_avail_update(pcmp); if ((err = snd_pcm_mmap_begin(pcmp, &areas, &offset, &alsa_frames)) < 0) { LOG_WARN("error from mmap_begin: %s", snd_strerror(err)); return -1; } out_frames = (frames_t)alsa_frames; } if (!silence) { // applying cross fade is delayed until this point as mmap_begin can change out_frames 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); } } inputptr = (s32_t *) (silence ? silencebuf : outputbuf->readp); IF_DSD( if (output.dop) { if (silence) { inputptr = (s32_t *) silencebuf_dop; } update_dop((u32_t *) inputptr, out_frames, output.invert && !silence); } ) if (alsa.mmap || alsa.format != NATIVE_FORMAT) { outputptr = alsa.mmap ? (areas[0].addr + (areas[0].first + offset * areas[0].step) / 8) : alsa.write_buf; _scale_and_pack_frames(outputptr, inputptr, out_frames, gainL, gainR, output.format); } else { outputptr = (void *)inputptr; if (!silence) { if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { _apply_gain(outputbuf, out_frames, gainL, gainR); } } } if (alsa.mmap) { snd_pcm_sframes_t w = snd_pcm_mmap_commit(pcmp, offset, out_frames); if (w < 0 || w != out_frames) { LOG_WARN("mmap_commit error"); return -1; } } else { snd_pcm_sframes_t w = snd_pcm_writei(pcmp, outputptr, out_frames); if (w < 0) { //if (w != -EAGAIN && ((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { if (((err = snd_pcm_recover(pcmp, w, 1)) < 0)) { static unsigned recover_count = 0; LOG_WARN("recover failed: %s [%u]", snd_strerror(err), ++recover_count); if (recover_count >= 10) { recover_count = 0; alsa_close(); pcmp = NULL; } } return -1; } else { if (w != out_frames) { LOG_WARN("writei only wrote %u of %u", w, out_frames); } out_frames = w; } } return (int)out_frames; } static void *output_thread(void *arg) { bool start = true; bool output_off = (output.state == OUTPUT_OFF); bool probe_device = (arg != NULL); int err; while (running) { // disabled output - player is off while (output_off) { usleep(100000); LOCK; output_off = (output.state == OUTPUT_OFF); UNLOCK; if (!running) return 0; } // wait until device returns - to allow usb audio devices to be turned off if (probe_device) { while (!pcm_probe(output.device)) { LOG_DEBUG("waiting for device %s to return", output.device); sleep(5); } probe_device = false; } if (!pcmp || alsa.rate != output.current_sample_rate) { LOG_INFO("open output device: %s", output.device); LOCK; // FIXME - some alsa hardware requires opening twice for a new sample rate to work // this is a workaround which should be removed if (alsa.reopen) { alsa_open(output.device, output.current_sample_rate, output.buffer, output.period); } if (!!alsa_open(output.device, output.current_sample_rate, output.buffer, output.period)) { output.error_opening = true; UNLOCK; sleep(5); continue; } output.error_opening = false; start = true; UNLOCK; } snd_pcm_state_t state = snd_pcm_state(pcmp); if (state == SND_PCM_STATE_XRUN) { LOG_INFO("XRUN"); if ((err = snd_pcm_recover(pcmp, -EPIPE, 1)) < 0) { LOG_INFO("XRUN recover failed: %s", snd_strerror(err)); } start = true; continue; } else if (state == SND_PCM_STATE_SUSPENDED) { if ((err = snd_pcm_recover(pcmp, -ESTRPIPE, 1)) < 0) { LOG_INFO("SUSPEND recover failed: %s", snd_strerror(err)); } } else if (state == SND_PCM_STATE_DISCONNECTED) { LOG_INFO("Device %s no longer available", output.device); alsa_close(); pcmp = NULL; probe_device = true; continue; } snd_pcm_sframes_t avail = snd_pcm_avail_update(pcmp); if (avail < 0) { if ((err = snd_pcm_recover(pcmp, avail, 1)) < 0) { if (err == -ENODEV) { LOG_INFO("Device %s no longer available", output.device); alsa_close(); pcmp = NULL; probe_device = true; continue; } LOG_WARN("recover failed: %s", snd_strerror(err)); } start = true; continue; } if (avail < alsa.period_size) { if (start) { if (alsa.mmap && ((err = snd_pcm_start(pcmp)) < 0)) { if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { if (err == -ENODEV) { LOG_INFO("Device %s no longer available", output.device); alsa_close(); pcmp = NULL; probe_device = true; continue; } LOG_INFO("start error: %s", snd_strerror(err)); usleep(10000); } } else { start = false; } } else { if ((err = snd_pcm_wait(pcmp, 1000)) < 0) { if ((err = snd_pcm_recover(pcmp, err, 1)) < 0) { LOG_INFO("pcm wait error: %s", snd_strerror(err)); } start = true; } } continue; } // 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); } // avoid spinning in cases where wait returns but no bytes available (seen with pulse audio) if (avail == 0) { LOG_SDEBUG("avail 0 - sleeping"); usleep(10000); continue; } LOCK; // turn off if requested if (output.state == OUTPUT_OFF) { UNLOCK; LOG_INFO("disabling output"); alsa_close(); pcmp = NULL; output_off = true; vis_stop(); continue; } // measure output delay snd_pcm_sframes_t delay; if ((err = snd_pcm_delay(pcmp, &delay)) < 0) { if (err == -EPIPE) { // EPIPE indicates underrun - attempt to recover UNLOCK; continue; } else if (err == -EIO) { // EIO can occur with non existant pulse server UNLOCK; LOG_SDEBUG("snd_pcm_delay returns: EIO - sleeping"); usleep(100000); continue; } else { LOG_DEBUG("snd_pcm_delay returns: %d", err); } } else { output.device_frames = delay; output.updated = gettime_ms(); output.frames_played_dmp = output.frames_played; } // process frames frames_t wrote = _output_frames(avail); UNLOCK; // some output devices such as alsa null refuse any data, avoid spinning if (!wrote) { LOG_SDEBUG("wrote 0 - sleeping"); usleep(10000); } } return 0; } static pthread_t thread; void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute) { unsigned alsa_buffer = ALSA_BUFFER_TIME; unsigned alsa_period = ALSA_PERIOD_COUNT; char *alsa_sample_fmt = NULL; 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, ':'); char *m = next_param(NULL, ':'); char *r = next_param(NULL, ':'); if (t) alsa_buffer = atoi(t); if (c) alsa_period = atoi(c); if (s) alsa_sample_fmt = s; if (m) alsa_mmap = atoi(m); if (r) alsa_reopen = atoi(r); loglevel = level; LOG_INFO("init output"); memset(&output, 0, sizeof(output)); alsa.mmap = alsa_mmap; 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; output.start_frames = 0; output.write_cb = &_write_frames; output.rate_delay = rate_delay; if (alsa_sample_fmt) { if (!strcmp(alsa_sample_fmt, "32")) alsa.format = SND_PCM_FORMAT_S32_LE; if (!strcmp(alsa_sample_fmt, "24")) alsa.format = SND_PCM_FORMAT_S24_LE; if (!strcmp(alsa_sample_fmt, "24_3")) alsa.format = SND_PCM_FORMAT_S24_3LE; if (!strcmp(alsa_sample_fmt, "16")) alsa.format = SND_PCM_FORMAT_S16_LE; } LOG_INFO("requested alsa_buffer: %u alsa_period: %u format: %s mmap: %u", output.buffer, output.period, alsa_sample_fmt ? alsa_sample_fmt : "any", alsa.mmap); snd_lib_error_set_handler((snd_lib_error_handler_t)alsa_error_handler); output_init_common(level, device, output_buf_size, rates, idle); if (mixer_unmute && volume_mixer_name) { set_mixer(output.device, volume_mixer_name, volume_mixer_index ? atoi(volume_mixer_index) : 0, true, 0, 0); } #if LINUX // RT linux - aim to avoid pagefaults by locking memory: // https://rt.wiki.kernel.org/index.php/Threaded_RT-application_with_memory_locking_and_stack_handling_example if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { LOG_INFO("unable to lock memory: %s", strerror(errno)); } else { LOG_INFO("memory locked"); } 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); #endif // start output thread pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); pthread_create(&thread, &attr, output_thread, rates[0] ? "probe" : NULL); pthread_attr_destroy(&attr); // try to set this thread to real-time scheduler class, only works as root or if user has permission struct sched_param param; param.sched_priority = rt_priority; if (pthread_setschedparam(thread, SCHED_FIFO, ¶m) != 0) { LOG_DEBUG("unable to set output sched fifo: %s", strerror(errno)); } else { LOG_DEBUG("set output sched fifo rt: %u", param.sched_priority); } } void output_close_alsa(void) { LOG_INFO("close output"); LOCK; running = false; UNLOCK; pthread_join(thread, NULL); if (alsa.write_buf) free(alsa.write_buf); output_close_common(); } #endif // ALSA squeezelite-1.8/output_pa.c 0000664 0000000 0000000 00000026737 12463437225 0016134 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Portaudio output #include "squeezelite.h" #if PORTAUDIO #include #if OSX #include #endif // ouput device static struct { unsigned rate; PaStream *stream; } pa; 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) extern u8_t *silencebuf; #if DSD extern u8_t *silencebuf_dop; #endif void list_devices(void) { PaError err; int i; if ((err = Pa_Initialize()) != paNoError) { LOG_WARN("error initialising port audio: %s", Pa_GetErrorText(err)); return; } printf("Output devices:\n"); for (i = 0; i < Pa_GetDeviceCount(); ++i) { if (Pa_GetDeviceInfo(i)->maxOutputChannels) { printf(" %i - %s [%s]\n", i, Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); } } printf("\n"); 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) { int len = strlen(device); int i; if (!strncmp(device, "default", 7)) { return Pa_GetDefaultOutputDevice(); } if (len >= 1 && len <= 2 && device[0] >= '0' && device[0] <= '9') { return atoi(device); } #define DEVICE_ID_MAXLEN 256 for (i = 0; i < Pa_GetDeviceCount(); ++i) { char tmp[DEVICE_ID_MAXLEN]; snprintf(tmp, DEVICE_ID_MAXLEN, "%s [%s]", Pa_GetDeviceInfo(i)->name, Pa_GetHostApiInfo(Pa_GetDeviceInfo(i)->hostApi)->name); if (!strncmp(tmp, device, len)) { return i; } } return -1; } static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); bool test_open(const char *device, unsigned rates[]) { PaStreamParameters outputParameters; PaError err; unsigned ref[] TEST_RATES; int device_id, i, ind; if ((device_id = pa_device_id(device)) == -1) { LOG_INFO("device %s not found", device); return false; } outputParameters.device = device_id; outputParameters.channelCount = 2; outputParameters.sampleFormat = paInt32; outputParameters.suggestedLatency = output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; // check supported sample rates // Note use Pa_OpenStream as it appears more reliable than Pa_IsFormatSupported on some windows apis for (i = 0, ind = 0; ref[i]; ++i) { err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)ref[i], paFramesPerBufferUnspecified, paNoFlag, pa_callback, NULL); if (err == paNoError) { Pa_CloseStream(pa.stream); rates[ind++] = ref[i]; } } if (!rates[0]) { LOG_WARN("no available rate found"); return false; } pa.stream = NULL; return true; } static void pa_stream_finished(void *userdata) { if (running) { LOG_INFO("stream finished"); LOCK; output.pa_reopen = true; wake_controller(); UNLOCK; } } static thread_type monitor_thread; bool monitor_thread_running = false; static void *pa_monitor() { bool output_off; LOCK; if (monitor_thread_running) { LOG_DEBUG("monitor thread already running"); UNLOCK; return 0; } LOG_DEBUG("start monitor thread"); monitor_thread_running = true; output_off = (output.state == OUTPUT_OFF); while (monitor_thread_running) { if (output_off) { if (output.state != OUTPUT_OFF) { LOG_INFO("output on"); break; } } else { // this is a hack to partially support hot plugging of devices // we rely on terminating and reinitalising PA to get an updated list of devices and use name for output.device LOG_INFO("probing device %s", output.device); Pa_Terminate(); Pa_Initialize(); pa.stream = NULL; if (pa_device_id(output.device) != -1) { LOG_INFO("device reopen"); break; } } UNLOCK; sleep(output_off ? 1 : 5); LOCK; } LOG_DEBUG("end monitor thread"); monitor_thread_running = false; pa.stream = NULL; _pa_open(); UNLOCK; return 0; } void _pa_open(void) { PaStreamParameters outputParameters; PaError err = paNoError; int device_id; if (pa.stream) { if ((err = Pa_CloseStream(pa.stream)) != paNoError) { LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); } } if (output.state == OUTPUT_OFF) { // we get called when transitioning to OUTPUT_OFF to create the probe thread // set err to avoid opening device and logging messages err = 1; } else if ((device_id = pa_device_id(output.device)) == -1) { LOG_INFO("device %s not found", output.device); err = 1; } else { outputParameters.device = device_id; outputParameters.channelCount = 2; outputParameters.sampleFormat = paInt32; outputParameters.suggestedLatency = output.latency ? (double)output.latency/(double)1000 : Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; #if OSX // enable pro mode which aims to avoid resampling if possible // see http://code.google.com/p/squeezelite/issues/detail?id=11 & http://code.google.com/p/squeezelite/issues/detail?id=37 // command line controls osx_playnice which is -1 if not specified, 0 or 1 - choose playnice if -1 or 1 PaMacCoreStreamInfo macInfo; unsigned long streamInfoFlags; if (output.osx_playnice) { LOG_INFO("opening device in PlayNice mode"); streamInfoFlags = paMacCorePlayNice; } else { LOG_INFO("opening device in Pro mode"); streamInfoFlags = paMacCorePro; } PaMacCore_SetupStreamInfo(&macInfo, streamInfoFlags); outputParameters.hostApiSpecificStreamInfo = &macInfo; #endif } if (!err && (err = Pa_OpenStream(&pa.stream, NULL, &outputParameters, (double)output.current_sample_rate, paFramesPerBufferUnspecified, paPrimeOutputBuffersUsingStreamCallback | paDitherOff, pa_callback, NULL)) != paNoError) { LOG_WARN("error opening device %i - %s : %s", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, Pa_GetErrorText(err)); } if (!err) { LOG_INFO("opened device %i - %s at %u latency %u ms", outputParameters.device, Pa_GetDeviceInfo(outputParameters.device)->name, (unsigned int)Pa_GetStreamInfo(pa.stream)->sampleRate, (unsigned int)(Pa_GetStreamInfo(pa.stream)->outputLatency * 1000)); pa.rate = output.current_sample_rate; if ((err = Pa_SetStreamFinishedCallback(pa.stream, pa_stream_finished)) != paNoError) { LOG_WARN("error setting finish callback: %s", Pa_GetErrorText(err)); } UNLOCK; // StartStream can call pa_callback in a sychronised thread on freebsd, remove lock while it is called if ((err = Pa_StartStream(pa.stream)) != paNoError) { LOG_WARN("error starting stream: %s", Pa_GetErrorText(err)); } LOCK; } if (err && !monitor_thread_running) { vis_stop(); // create a thread to check for output state change or device return #if LINUX || OSX || FREEBSD pthread_create(&monitor_thread, NULL, pa_monitor, NULL); #endif #if WIN monitor_thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&pa_monitor, NULL, 0, NULL); #endif } output.error_opening = !!err; } static u8_t *optr; static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, s32_t cross_gain_in, s32_t cross_gain_out, s32_t **cross_ptr) { 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); } if (gainL != FIXED_ONE || gainR!= FIXED_ONE) { _apply_gain(outputbuf, out_frames, gainL, gainR); } IF_DSD( if (output.dop) { update_dop((u32_t *) outputbuf->readp, out_frames, output.invert); } ) memcpy(optr, outputbuf->readp, out_frames * BYTES_PER_FRAME); } else { u8_t *buf = silencebuf; IF_DSD( if (output.dop) { buf = silencebuf_dop; update_dop((u32_t *) buf, out_frames, false); // don't invert silence } ) memcpy(optr, buf, out_frames * BYTES_PER_FRAME); } optr += out_frames * BYTES_PER_FRAME; return (int)out_frames; } static int pa_callback(const void *pa_input, void *pa_output, unsigned long pa_frames_wanted, const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags statusFlags, void *userData) { int ret; double stream_time; frames_t frames; optr = (u8_t *)pa_output; LOCK; stream_time = Pa_GetStreamTime(pa.stream); if (time_info->outputBufferDacTime > stream_time) { // workaround for wdm-ks which can return outputBufferDacTime with a different epoch output.device_frames = (unsigned)((time_info->outputBufferDacTime - stream_time) * output.current_sample_rate); } else { output.device_frames = 0; } output.updated = gettime_ms(); output.frames_played_dmp = output.frames_played; do { frames = _output_frames(pa_frames_wanted); pa_frames_wanted -= frames; } while (pa_frames_wanted > 0 && frames != 0); if (pa_frames_wanted > 0) { LOG_DEBUG("pad with silence"); memset(optr, 0, pa_frames_wanted * BYTES_PER_FRAME); } if (output.state == OUTPUT_OFF) { LOG_INFO("output off"); ret = paComplete; } else if (pa.rate != output.current_sample_rate) { ret = paComplete; } else { ret = paContinue; } UNLOCK; return ret; } void output_init_pa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { PaError err; unsigned latency = 0; int osx_playnice = -1; char *l = next_param(params, ':'); char *p = next_param(NULL, ':'); if (l) latency = (unsigned)atoi(l); if (p) osx_playnice = atoi(p); loglevel = level; LOG_INFO("init output"); memset(&output, 0, sizeof(output)); output.latency = latency; output.osx_playnice = osx_playnice; output.format = 0; output.start_frames = 0; output.write_cb = &_write_frames; output.rate_delay = rate_delay; pa.stream = NULL; LOG_INFO("requested latency: %u", output.latency); 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, idle); LOCK; _pa_open(); UNLOCK; } void output_close_pa(void) { PaError err; LOG_INFO("close output"); LOCK; running = false; monitor_thread_running = false; if (pa.stream) { if ((err = Pa_AbortStream(pa.stream)) != paNoError) { LOG_WARN("error closing stream: %s", Pa_GetErrorText(err)); } } if ((err = Pa_Terminate()) != paNoError) { LOG_WARN("error closing port audio: %s", Pa_GetErrorText(err)); } UNLOCK; output_close_common(); } #endif // PORTAUDIO squeezelite-1.8/output_pack.c 0000664 0000000 0000000 00000021322 12463437225 0016433 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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) { 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; } } squeezelite-1.8/output_stdout.c 0000664 0000000 0000000 00000007676 12463437225 0017057 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Stdout output #include "squeezelite.h" #define FRAME_BLOCK MAX_SILENCE_FRAMES 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) extern u8_t *silencebuf; #if DSD extern u8_t *silencebuf_dop; #endif // 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 int _stdout_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; } IF_DSD( if (output.dop) { if (silence) { obuf = silencebuf_dop; } update_dop((u32_t *)obuf, out_frames, output.invert && !silence); } ) _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; } static thread_type thread; void output_init_stdout(log_level level, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay) { loglevel = level; LOG_INFO("init output stdout"); 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 = &_stdout_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 pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE); pthread_create(&thread, &attr, output_thread, NULL); pthread_attr_destroy(&attr); #endif #if WIN thread = CreateThread(NULL, OUTPUT_THREAD_STACK_SIZE, (LPTHREAD_START_ROUTINE)&output_thread, NULL, 0, NULL); #endif } void output_close_stdout(void) { LOG_INFO("close output"); LOCK; running = false; UNLOCK; free(buf); output_close_common(); } squeezelite-1.8/output_vis.c 0000664 0000000 0000000 00000010010 12463437225 0016306 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // Export audio samples for visualiser process (16 bit only best endevours) #include "squeezelite.h" #if VISEXPORT #include #include #include #define VIS_BUF_SIZE 16384 #define VIS_LOCK_NS 1000000 // ns to wait for vis wrlock static struct vis_t { pthread_rwlock_t rwlock; u32_t buf_size; u32_t buf_index; bool running; u32_t rate; time_t updated; s16_t buffer[VIS_BUF_SIZE]; } *vis_mmap = NULL; static char vis_shm_path[40]; static int vis_fd = -1; static log_level loglevel; // attempt to write audio to vis_mmap but do not wait more than VIS_LOCK_NS to get wrlock // this can result in missing audio export to the mmap region, but this is preferable dropping audio void _vis_export(struct buffer *outputbuf, struct outputstate *output, frames_t out_frames, bool silence) { if (vis_mmap) { int err; err = pthread_rwlock_trywrlock(&vis_mmap->rwlock); if (err) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_nsec += VIS_LOCK_NS; if (ts.tv_nsec > 1000000000) { ts.tv_sec += 1; ts.tv_nsec -= 1000000000; } err = pthread_rwlock_timedwrlock(&vis_mmap->rwlock, &ts); } if (err) { LOG_DEBUG("failed to get wrlock - skipping visulizer export"); } else { if (silence) { vis_mmap->running = false; } else { frames_t vis_cnt = out_frames; s32_t *ptr = (s32_t *) outputbuf->readp; unsigned i = vis_mmap->buf_index; if (!output->current_replay_gain) { while (vis_cnt--) { vis_mmap->buffer[i++] = *(ptr++) >> 16; vis_mmap->buffer[i++] = *(ptr++) >> 16; if (i == VIS_BUF_SIZE) i = 0; } } else { while (vis_cnt--) { vis_mmap->buffer[i++] = gain(*(ptr++), output->current_replay_gain) >> 16; vis_mmap->buffer[i++] = gain(*(ptr++), output->current_replay_gain) >> 16; if (i == VIS_BUF_SIZE) i = 0; } } vis_mmap->updated = time(NULL); vis_mmap->running = true; vis_mmap->buf_index = i; vis_mmap->rate = output->current_sample_rate; } pthread_rwlock_unlock(&vis_mmap->rwlock); } } } void vis_stop(void) { if (vis_mmap) { pthread_rwlock_wrlock(&vis_mmap->rwlock); vis_mmap->running = false; pthread_rwlock_unlock(&vis_mmap->rwlock); } } void output_vis_init(log_level level, u8_t *mac) { loglevel = level; sprintf(vis_shm_path, "/squeezelite-%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); mode_t old_mask = umask(000); // allow any user to read our shm when created vis_fd = shm_open(vis_shm_path, O_CREAT | O_RDWR, 0666); if (vis_fd != -1) { if (ftruncate(vis_fd, sizeof(struct vis_t)) == 0) { vis_mmap = (struct vis_t *)mmap(NULL, sizeof(struct vis_t), PROT_READ | PROT_WRITE, MAP_SHARED, vis_fd, 0); } } if (vis_mmap > 0) { pthread_rwlockattr_t attr; pthread_rwlockattr_init(&attr); pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); pthread_rwlock_init(&vis_mmap->rwlock, &attr); vis_mmap->buf_size = VIS_BUF_SIZE; vis_mmap->running = false; vis_mmap->rate = 44100; pthread_rwlockattr_destroy(&attr); LOG_INFO("opened visulizer shared memory as %s", vis_shm_path); } else { LOG_WARN("unable to open visualizer shared memory"); vis_mmap = NULL; } umask(old_mask); } #endif // VISEXPORT squeezelite-1.8/pcm.c 0000664 0000000 0000000 00000024120 12463437225 0014653 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #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; #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 }; 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; LOG_INFO("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); audio_left = len - 8 - offset; LOG_INFO("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) { _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.next_sample_rate = decode_newstream(sample_rate, output.supported_rates); output.track_start = outputbuf->writep; IF_DSD( output.next_dop = false; ) if (output.fade_mode) _checkfade(true); decode.new_stream = false; 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) { 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; } squeezelite-1.8/process.c 0000664 0000000 0000000 00000011524 12463437225 0015556 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // sample processing - only included when building with PROCESS set #include "squeezelite.h" #if PROCESS extern log_level loglevel; extern struct buffer *outputbuf; extern struct decodestate decode; struct processstate process; extern struct codec *codec; #define LOCK_D mutex_lock(decode.mutex); #define UNLOCK_D mutex_unlock(decode.mutex); #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) // macros to map to processing functions - currently only resample.c // this can be made more generic when multiple processing mechanisms get added #if RESAMPLE #define SAMPLES_FUNC resample_samples #define DRAIN_FUNC resample_drain #define NEWSTREAM_FUNC resample_newstream #define FLUSH_FUNC resample_flush #define INIT_FUNC resample_init #endif // transfer all processed frames to the output buf static void _write_samples(void) { size_t frames = process.out_frames; u32_t *iptr = (u32_t *)process.outbuf; unsigned cnt = 10; LOCK_O; while (frames > 0) { frames_t f = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; u32_t *optr = (u32_t *)outputbuf->writep; if (f > 0) { f = min(f, frames); memcpy(optr, iptr, f * BYTES_PER_FRAME); frames -= f; _buf_inc_writep(outputbuf, f * BYTES_PER_FRAME); iptr += f * BYTES_PER_FRAME / sizeof(*iptr); } else if (cnt--) { // there should normally be space in the output buffer, but may need to wait during drain phase UNLOCK_O; usleep(10000); LOCK_O; } else { // bail out if no space found after 100ms to avoid locking LOG_ERROR("unable to get space in output buffer"); UNLOCK_O; return; } } UNLOCK_O; } // process samples - called with decode mutex set void process_samples(void) { SAMPLES_FUNC(&process); _write_samples(); process.in_frames = 0; } // drain at end of track - called with decode mutex set void process_drain(void) { bool done; do { done = DRAIN_FUNC(&process); _write_samples(); } while (!done); LOG_DEBUG("processing track complete - frames in: %lu out: %lu", process.total_in, process.total_out); } // new stream - called with decode mutex set unsigned process_newstream(bool *direct, unsigned raw_sample_rate, unsigned supported_rates[]) { bool active = NEWSTREAM_FUNC(&process, raw_sample_rate, supported_rates); LOG_INFO("processing: %s", active ? "active" : "inactive"); *direct = !active; if (active) { unsigned max_in_frames, max_out_frames; process.in_frames = process.out_frames = 0; process.total_in = process.total_out = 0; max_in_frames = codec->min_space / BYTES_PER_FRAME ; // increase size of output buffer by 10% as output rate is not an exact multiple of input rate if (process.out_sample_rate % process.in_sample_rate == 0) { max_out_frames = max_in_frames * (process.out_sample_rate / process.in_sample_rate); } else { max_out_frames = (int)(1.1 * (float)max_in_frames * (float)process.out_sample_rate / (float)process.in_sample_rate); } if (process.max_in_frames != max_in_frames) { LOG_DEBUG("creating process buf in frames: %u", max_in_frames); if (process.inbuf) free(process.inbuf); process.inbuf = malloc(max_in_frames * BYTES_PER_FRAME); process.max_in_frames = max_in_frames; } if (process.max_out_frames != max_out_frames) { LOG_DEBUG("creating process buf out frames: %u", max_out_frames); if (process.outbuf) free(process.outbuf); process.outbuf = malloc(max_out_frames * BYTES_PER_FRAME); process.max_out_frames = max_out_frames; } if (!process.inbuf || !process.outbuf) { LOG_ERROR("malloc fail creating process buffers"); *direct = true; return raw_sample_rate; } return process.out_sample_rate; } return raw_sample_rate; } // process flush - called with decode mutex set void process_flush(void) { LOG_INFO("process flush"); FLUSH_FUNC(); process.in_frames = 0; } // init - called with no mutex void process_init(char *opt) { bool enabled = INIT_FUNC(opt); memset(&process, 0, sizeof(process)); if (enabled) { LOCK_D; decode.process = true; UNLOCK_D; } } #endif // #if PROCESS squeezelite-1.8/resample.c 0000664 0000000 0000000 00000023630 12463437225 0015711 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // upsampling using libsoxr - only included if RESAMPLE set #include "squeezelite.h" #if RESAMPLE #include #include extern log_level loglevel; struct soxr { soxr_t resampler; size_t old_clips; unsigned long q_recipe; unsigned long q_flags; double q_precision; /* Conversion precision (in bits). 20 */ double q_phase_response; /* 0=minimum, ... 50=linear, ... 100=maximum 50 */ double q_passband_end; /* 0dB pt. bandwidth to preserve; nyquist=1 0.913 */ double q_stopband_begin; /* Aliasing/imaging control; > passband_end 1 */ double scale; bool max_rate; bool exception; #if !LINKALL // soxr symbols to be dynamically loaded soxr_io_spec_t (* soxr_io_spec)(soxr_datatype_t itype, soxr_datatype_t otype); soxr_quality_spec_t (* soxr_quality_spec)(unsigned long recipe, unsigned long flags); soxr_t (* soxr_create)(double, double, unsigned, soxr_error_t *, soxr_io_spec_t const *, soxr_quality_spec_t const *, soxr_runtime_spec_t const *); void (* soxr_delete)(soxr_t); soxr_error_t (* soxr_process)(soxr_t, soxr_in_t, size_t, size_t *, soxr_out_t, size_t olen, size_t *); size_t *(* soxr_num_clips)(soxr_t); #if RESAMPLE_MP soxr_runtime_spec_t (* soxr_runtime_spec)(unsigned num_threads); #endif // soxr_strerror is a macro so not included here #endif }; static struct soxr *r; #if LINKALL #define SOXR(h, fn, ...) (soxr_ ## fn)(__VA_ARGS__) #else #define SOXR(h, fn, ...) (h)->soxr_##fn(__VA_ARGS__) #endif void resample_samples(struct processstate *process) { size_t idone, odone; size_t clip_cnt; soxr_error_t error = 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) { // should not get here if buffers are big enough... LOG_ERROR("should not get here - partial sox process: %u of %u processed %u of %u out", (unsigned)idone, process->in_frames, (unsigned)odone, process->max_out_frames); } process->out_frames = odone; process->total_in += idone; process->total_out += odone; clip_cnt = *(SOXR(r, num_clips, r->resampler)); if (clip_cnt - r->old_clips) { LOG_SDEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips)); r->old_clips = clip_cnt; } } bool resample_drain(struct processstate *process) { size_t odone; size_t clip_cnt; 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; process->total_out += odone; clip_cnt = *(SOXR(r, num_clips, r->resampler)); if (clip_cnt - r->old_clips) { LOG_DEBUG("resampling clips: %u", (unsigned)(clip_cnt - r->old_clips)); r->old_clips = clip_cnt; } if (odone == 0) { LOG_INFO("resample track complete - total track clips: %u", r->old_clips); SOXR(r, delete, r->resampler); r->resampler = NULL; return true; } else { return false; } } bool resample_newstream(struct processstate *process, unsigned raw_sample_rate, unsigned supported_rates[]) { unsigned outrate = 0; int i; if (r->exception) { // find direct match - avoid resampling for (i = 0; supported_rates[i]; i++) { if (raw_sample_rate == supported_rates[i]) { outrate = raw_sample_rate; break; } } // else find next highest sync sample rate while (!outrate && i >= 0) { if (supported_rates[i] > raw_sample_rate && supported_rates[i] % raw_sample_rate == 0) { outrate = supported_rates[i]; break; } i--; } } if (!outrate) { if (r->max_rate) { // resample to max rate for device outrate = supported_rates[0]; } else { // resample to max sync sample rate for (i = 0; supported_rates[i]; i++) { if (supported_rates[i] % raw_sample_rate == 0 || raw_sample_rate % supported_rates[i] == 0) { outrate = supported_rates[i]; break; } } } if (!outrate) { outrate = supported_rates[0]; } } process->in_sample_rate = raw_sample_rate; process->out_sample_rate = outrate; if (r->resampler) { SOXR(r, delete, r->resampler); r->resampler = NULL; } if (raw_sample_rate != outrate) { soxr_io_spec_t io_spec; soxr_quality_spec_t q_spec; soxr_error_t error; #if RESAMPLE_MP soxr_runtime_spec_t r_spec; #endif LOG_INFO("resampling from %u -> %u", raw_sample_rate, outrate); io_spec = SOXR(r, io_spec, SOXR_INT32_I, SOXR_INT32_I); io_spec.scale = r->scale; q_spec = SOXR(r, quality_spec, r->q_recipe, r->q_flags); if (r->q_precision > 0) { q_spec.precision = r->q_precision; } if (r->q_passband_end > 0) { q_spec.passband_end = r->q_passband_end; } if (r->q_stopband_begin > 0) { q_spec.stopband_begin = r->q_stopband_begin; } if (r->q_phase_response > -1) { q_spec.phase_response = r->q_phase_response; } #if RESAMPLE_MP r_spec = SOXR(r, runtime_spec, 0); // make use of libsoxr OpenMP support allowing parallel execution if multiple cores #endif LOG_DEBUG("resampling with soxr_quality_spec_t[precision: %03.1f, passband_end: %03.6f, stopband_begin: %03.6f, " "phase_response: %03.1f, flags: 0x%02x], soxr_io_spec_t[scale: %03.2f]", q_spec.precision, q_spec.passband_end, q_spec.stopband_begin, q_spec.phase_response, q_spec.flags, io_spec.scale); #if RESAMPLE_MP r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, &r_spec); #else r->resampler = SOXR(r, create, raw_sample_rate, outrate, 2, &error, &io_spec, &q_spec, NULL); #endif if (error) { LOG_INFO("soxr_create error: %s", soxr_strerror(error)); return false; } r->old_clips = 0; return true; } else { LOG_INFO("disable resampling - rates match"); return false; } } void resample_flush(void) { if (r->resampler) { SOXR(r, delete, r->resampler); r->resampler = NULL; } } static bool load_soxr(void) { #if !LINKALL void *handle = dlopen(LIBSOXR, RTLD_NOW); char *err; if (!handle) { LOG_INFO("dlerror: %s", dlerror()); return false; } r->soxr_io_spec = dlsym(handle, "soxr_io_spec"); r->soxr_quality_spec = dlsym(handle, "soxr_quality_spec"); r->soxr_create = dlsym(handle, "soxr_create"); r->soxr_delete = dlsym(handle, "soxr_delete"); r->soxr_process = dlsym(handle, "soxr_process"); r->soxr_num_clips = dlsym(handle, "soxr_num_clips"); #if RESAMPLE_MP r->soxr_runtime_spec = dlsym(handle, "soxr_runtime_spec"); #endif if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded "LIBSOXR); #endif return true; } bool resample_init(char *opt) { char *recipe = NULL, *flags = NULL; char *atten = NULL; char *precision = NULL, *passband_end = NULL, *stopband_begin = NULL, *phase_response = NULL; r = malloc(sizeof(struct soxr)); if (!r) { LOG_WARN("resampling disabled"); return false; } r->resampler = NULL; r->old_clips = 0; r->max_rate = false; r->exception = false; if (!load_soxr()) { LOG_WARN("resampling disabled"); return false; } if (opt) { recipe = next_param(opt, ':'); flags = next_param(NULL, ':'); atten = next_param(NULL, ':'); precision = next_param(NULL, ':'); passband_end = next_param(NULL, ':'); stopband_begin = next_param(NULL, ':'); phase_response = next_param(NULL, ':'); } // default to HQ (20 bit) if not user specified r->q_recipe = SOXR_HQ; r->q_flags = 0; // default to 1db of attenuation if not user specified r->scale = pow(10, -1.0 / 20); // override recipe derived values with user specified values r->q_precision = 0; r->q_passband_end = 0; r->q_stopband_begin = 0; r->q_phase_response = -1; if (recipe && recipe[0] != '\0') { if (strchr(recipe, 'v')) r->q_recipe = SOXR_VHQ; if (strchr(recipe, 'h')) r->q_recipe = SOXR_HQ; if (strchr(recipe, 'm')) r->q_recipe = SOXR_MQ; if (strchr(recipe, 'l')) r->q_recipe = SOXR_LQ; if (strchr(recipe, 'q')) r->q_recipe = SOXR_QQ; if (strchr(recipe, 'L')) r->q_recipe |= SOXR_LINEAR_PHASE; if (strchr(recipe, 'I')) r->q_recipe |= SOXR_INTERMEDIATE_PHASE; if (strchr(recipe, 'M')) r->q_recipe |= SOXR_MINIMUM_PHASE; if (strchr(recipe, 's')) r->q_recipe |= SOXR_STEEP_FILTER; // X = async resampling to max_rate if (strchr(recipe, 'X')) r->max_rate = true; // E = exception, only resample if native rate is not supported if (strchr(recipe, 'E')) r->exception = true; } if (flags) { r->q_flags = strtoul(flags, 0, 16); } if (atten) { double scale = pow(10, -atof(atten) / 20); if (scale > 0 && scale <= 1.0) { r->scale = scale; } } if (precision) { r->q_precision = atof(precision); } if (passband_end) { r->q_passband_end = atof(passband_end) / 100; } if (stopband_begin) { r->q_stopband_begin = atof(stopband_begin) / 100; } if (phase_response) { r->q_phase_response = atof(phase_response); } LOG_INFO("resampling %s recipe: 0x%02x, flags: 0x%02x, scale: %03.2f, precision: %03.1f, passband_end: %03.5f, stopband_begin: %03.5f, phase_response: %03.1f", r->max_rate ? "async" : "sync", r->q_recipe, r->q_flags, r->scale, r->q_precision, r->q_passband_end, r->q_stopband_begin, r->q_phase_response); return true; } #endif // #if RESAMPLE squeezelite-1.8/slimproto.c 0000664 0000000 0000000 00000061562 12463437225 0016137 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #include "slimproto.h" static log_level loglevel; #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]) { const char *base_cap = "Model=squeezelite,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION; struct HELO_packet pkt; 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); if (now > status.updated) ms_played += (now - status.updated); 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; 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; LOG_INFO("switch server"); new_server = serv->server_ip; 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) && !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; } #if PORTAUDIO if (output.pa_reopen) { _pa_open(); output.pa_reopen = false; } #endif 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; } 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(void) { struct sockaddr_in d; struct sockaddr_in s; char *buf; struct pollfd pollinfo; 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)); } } 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) { 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; int i; wake_create(wake_e); loglevel = level; running = true; if (server) { server_addr(server, &slimproto_ip, &slimproto_port); } if (!slimproto_ip) { slimproto_ip = discover_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, 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) { 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) { 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(); } } 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); } closesocket(sock); } } void slimproto_stop(void) { LOG_INFO("slimproto stop"); running = false; } squeezelite-1.8/slimproto.h 0000664 0000000 0000000 00000007016 12463437225 0016136 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // packet formats for slimproto #pragma pack(push, 1) // 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; }; #pragma pack(pop) squeezelite-1.8/squeezelite.h 0000664 0000000 0000000 00000044607 12463437225 0016454 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // 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) #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 #else #error unknown target #endif #if LINUX && !defined(PORTAUDIO) #define ALSA 1 #define PORTAUDIO 0 #else #define ALSA 0 #define PORTAUDIO 1 #endif #if LINUX && !defined(SELFPIPE) #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 #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 && 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 !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__) #include #include #include #include #include #include #include #if LINUX || OSX || FREEBSD #include #include #include #include #include #include #include #include #include #include #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 #define ERROR_WOULDBLOCK EWOULDBLOCK typedef u_int8_t u8_t; typedef u_int16_t u16_t; typedef u_int32_t u32_t; typedef u_int64_t u64_t; 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) #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) #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 #include #include #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 #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 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 #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; 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 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); 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; typedef enum { S32_LE, S24_LE, S24_3LE, S16_LE } output_format; typedef enum { FADE_INACTIVE = 0, FADE_DUE, FADE_ACTIVE } fade_state; typedef enum { FADE_UP = 1, FADE_DOWN, FADE_CROSS } fade_dir; typedef enum { FADE_NONE = 0, FADE_CROSSFADE, FADE_IN, FADE_OUT, FADE_INOUT } fade_mode; #define MAX_SUPPORTED_SAMPLERATES 16 #define TEST_RATES = { 384000, 352800, 192000, 176400, 96000, 88200, 48000, 44100, 32000, 24000, 22500, 16000, 12000, 11025, 8000, 0 } 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 osx_playnice; #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 bool next_dop; // set in decode thread bool dop; bool has_dop; // set in dop_init - output device supports dop unsigned dop_delay; // set in dop_init - delay in ms switching to/from dop #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[]); void output_init_alsa(log_level level, const char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned rt_priority, unsigned idle, char *volume_mixer, bool mixer_unmute); void output_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, unsigned idle); void output_close_pa(void); void _pa_open(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_flac_dop(u32_t *lptr, u32_t *rptr, 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 // 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); // 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 squeezelite-1.8/stream.c 0000664 0000000 0000000 00000023404 12463437225 0015373 0 ustar 00root root 0000000 0000000 /* * 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 . * */ // stream thread #include "squeezelite.h" #include 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; static void send_header(void) { char *ptr = stream.header; int len = stream.header_len; unsigned try = 0; ssize_t n; while (len) { n = send(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; } LOG_SDEBUG("wrote %d bytes to socket", n); ptr += n; len -= n; } LOG_SDEBUG("wrote header"); } static bool running = true; static void _disconnect(stream_state state, disconnect_code disconnect) { stream.state = state; stream.disconnect = disconnect; 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(&pollinfo, 1, 100)) { LOCK; // check socket has not been closed while in poll if (fd < 0) { UNLOCK; continue; } if ((pollinfo.revents & POLLOUT) && stream.state == SEND_HEADERS) { send_header(); stream.header_len = 0; stream.state = RECV_HEADERS; 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(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(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(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(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; } } 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"); } } 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); } 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 pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + STREAM_THREAD_STACK_SIZE); 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 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; } 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 (fd != -1) { closesocket(fd); fd = -1; disc = true; } stream.state = STOPPED; UNLOCK; return disc; } squeezelite-1.8/utils.c 0000664 0000000 0000000 00000020715 12463437225 0015242 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" #if LINUX || OSX || FREEBSD #include #include #include #if FREEBSD #include #include #include #endif #endif #if WIN #include #endif #if OSX #include #include #include #include #endif #include // 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 struct timespec ts; if (!clock_gettime(CLOCK_MONOTONIC, &ts)) { 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 // 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]; 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 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 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 } // 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; } // this only implements numfds == 1 int poll(struct pollfd *fds, unsigned long numfds, int timeout) { fd_set r, w; struct timeval tv; int ret; FD_ZERO(&r); FD_ZERO(&w); if (fds[0].events & POLLIN) FD_SET(fds[0].fd, &r); if (fds[0].events & POLLOUT) FD_SET(fds[0].fd, &w); tv.tv_sec = timeout / 1000; 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; if (FD_ISSET(fds[0].fd, &r)) fds[0].revents |= POLLIN; if (FD_ISSET(fds[0].fd, &w)) fds[0].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 squeezelite-1.8/vorbis.c 0000664 0000000 0000000 00000020623 12463437225 0015404 0 ustar 00root root 0000000 0000000 /* * 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 . * */ #include "squeezelite.h" // automatically select between floating point (preferred) and fixed point libraries: // NOTE: works with Tremor version here: http://svn.xiph.org/trunk/Tremor, not vorbisidec.1.0.2 currently in ubuntu // we take common definations from even though we can use tremor at run time // tremor's OggVorbis_File struct is normally smaller so this is ok, but padding added to malloc in case it is bigger #define OV_EXCLUDE_STATIC_CALLBACKS #include struct vorbis { OggVorbis_File *vf; bool opened; #if !LINKALL // vorbis symbols to be dynamically loaded - from either vorbisfile or vorbisidec (tremor) version of library vorbis_info *(* ov_info)(OggVorbis_File *vf, int link); int (* ov_clear)(OggVorbis_File *vf); long (* ov_read)(OggVorbis_File *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream); long (* ov_read_tremor)(OggVorbis_File *vf, char *buffer, int length, int *bitstream); int (* ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks); #endif }; static struct vorbis *v; extern log_level loglevel; extern struct buffer *streambuf; extern struct buffer *outputbuf; extern struct streamstate stream; extern struct outputstate output; extern struct decodestate decode; extern struct processstate process; #define LOCK_S mutex_lock(streambuf->mutex) #define UNLOCK_S mutex_unlock(streambuf->mutex) #define LOCK_O mutex_lock(outputbuf->mutex) #define UNLOCK_O mutex_unlock(outputbuf->mutex) #if PROCESS #define LOCK_O_direct if (decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct if (!decode.direct) mutex_lock(outputbuf->mutex) #define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex) #define IF_DIRECT(x) if (decode.direct) { x } #define IF_PROCESS(x) if (!decode.direct) { x } #else #define LOCK_O_direct mutex_lock(outputbuf->mutex) #define UNLOCK_O_direct mutex_unlock(outputbuf->mutex) #define LOCK_O_not_direct #define UNLOCK_O_not_direct #define IF_DIRECT(x) { x } #define IF_PROCESS(x) #endif #if LINKALL #define OV(h, fn, ...) (ov_ ## fn)(__VA_ARGS__) #define TREMOR(h) 0 extern int ov_read_tremor(); // needed to enable compilation, not linked #else #define OV(h, fn, ...) (h)->ov_##fn(__VA_ARGS__) #define TREMOR(h) (h)->ov_read_tremor #endif // called with mutex locked within vorbis_decode to avoid locking O before S static size_t _read_cb(void *ptr, size_t size, size_t nmemb, void *datasource) { size_t bytes; bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); bytes = min(bytes, size * nmemb); memcpy(ptr, streambuf->readp, bytes); _buf_inc_readp(streambuf, bytes); return bytes / size; } // these are needed for older versions of tremor, later versions and libvorbis allow NULL to be used static int _seek_cb(void *datasource, ogg_int64_t offset, int whence) { return -1; } static int _close_cb(void *datasource) { return 0; } static long _tell_cb(void *datasource) { return 0; } static decode_state vorbis_decode(void) { static int channels; bool end; frames_t frames; int bytes, s, n; u8_t *write_buf; LOCK_S; LOCK_O_direct; end = (stream.state <= DISCONNECT); IF_DIRECT( frames = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME; ); IF_PROCESS( frames = process.max_in_frames; ); if (!frames && end) { UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } if (decode.new_stream) { ov_callbacks cbs; int err; struct vorbis_info *info; cbs.read_func = _read_cb; if (TREMOR(v)) { cbs.seek_func = _seek_cb; cbs.close_func = _close_cb; cbs.tell_func = _tell_cb; } else { cbs.seek_func = NULL; cbs.close_func = NULL; cbs.tell_func = NULL; } if ((err = OV(v, open_callbacks, streambuf, v->vf, NULL, 0, cbs)) < 0) { LOG_WARN("open_callbacks error: %d", err); UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } v->opened = true; info = OV(v, info, v->vf, -1); LOG_INFO("setting track_start"); LOCK_O_not_direct; output.next_sample_rate = decode_newstream(info->rate, output.supported_rates); IF_DSD( output.next_dop = false; ) output.track_start = outputbuf->writep; if (output.fade_mode) _checkfade(true); decode.new_stream = false; UNLOCK_O_not_direct; IF_PROCESS( frames = process.max_in_frames; ); channels = info->channels; if (channels > 2) { LOG_WARN("too many channels: %d", channels); UNLOCK_O_direct; UNLOCK_S; return DECODE_ERROR; } } bytes = frames * 2 * channels; // samples returned are 16 bits IF_DIRECT( write_buf = outputbuf->writep; ); IF_PROCESS( write_buf = process.inbuf; ); // write the decoded frames into outputbuf even though they are 16 bits per sample, then unpack them if (!TREMOR(v)) { #if SL_LITTLE_ENDIAN n = OV(v, read, v->vf, (char *)write_buf, bytes, 0, 2, 1, &s); #else n = OV(v, read, v->vf, (char *)write_buf, bytes, 1, 2, 1, &s); #endif } else { n = OV(v, read_tremor, v->vf, (char *)write_buf, bytes, &s); } if (n > 0) { frames_t count; s16_t *iptr; s32_t *optr; frames = n / 2 / channels; count = frames * channels; // work backward to unpack samples to 4 bytes per sample iptr = (s16_t *)write_buf + count; optr = (s32_t *)write_buf + frames * 2; if (channels == 2) { while (count--) { *--optr = *--iptr << 16; } } else if (channels == 1) { while (count--) { *--optr = *--iptr << 16; *--optr = *iptr << 16; } } IF_DIRECT( _buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME); ); IF_PROCESS( process.in_frames = frames; ); LOG_SDEBUG("wrote %u frames", frames); } else if (n == 0) { LOG_INFO("end of stream"); UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } else if (n == OV_HOLE) { // recoverable hole in stream, seen when skipping LOG_DEBUG("hole in stream"); } else { LOG_INFO("ov_read error: %d", n); UNLOCK_O_direct; UNLOCK_S; return DECODE_COMPLETE; } UNLOCK_O_direct; UNLOCK_S; return DECODE_RUNNING; } static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) { if (!v->vf) { v->vf = malloc(sizeof(OggVorbis_File) + 128); // add some padding as struct size may be larger memset(v->vf, 0, sizeof(OggVorbis_File) + 128); } else { if (v->opened) { OV(v, clear, v->vf); v->opened = false; } } } static void vorbis_close(void) { if (v->opened) { OV(v, clear, v->vf); v->opened = false; } free(v->vf); v->vf = NULL; } static bool load_vorbis() { #if !LINKALL void *handle = dlopen(LIBVORBIS, RTLD_NOW); char *err; bool tremor = false; if (!handle) { handle = dlopen(LIBTREMOR, RTLD_NOW); if (handle) { tremor = true; } else { LOG_INFO("dlerror: %s", dlerror()); return false; } } v->ov_read = tremor ? NULL : dlsym(handle, "ov_read"); v->ov_read_tremor = tremor ? dlsym(handle, "ov_read") : NULL; v->ov_info = dlsym(handle, "ov_info"); v->ov_clear = dlsym(handle, "ov_clear"); v->ov_open_callbacks = dlsym(handle, "ov_open_callbacks"); if ((err = dlerror()) != NULL) { LOG_INFO("dlerror: %s", err); return false; } LOG_INFO("loaded %s", tremor ? LIBTREMOR : LIBVORBIS); #endif return true; } struct codec *register_vorbis(void) { static struct codec ret = { 'o', // id "ogg", // types 2048, // min read 20480, // min space vorbis_open, // open vorbis_close, // close vorbis_decode,// decode }; v = malloc(sizeof(struct vorbis)); if (!v) { return NULL; } v->vf = NULL; v->opened = false; if (!load_vorbis()) { return NULL; } LOG_INFO("using vorbis to decode ogg"); return &ret; }