diff --git a/Makefile.am b/Makefile.am index c016d98a..203fffbb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ SUBDIRS = man bin_PROGRAMS = shairport-sync -shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c +shairport_sync_SOURCES = shairport.c rtsp.c mdns.c mdns_external.c common.c rtp.c player.c alac.c audio.c audio_dummy.c audio_pipe.c audio_stdout.c AM_CFLAGS = -Wno-multichar diff --git a/audio.c b/audio.c index 9887531f..e8f56718 100644 --- a/audio.c +++ b/audio.c @@ -41,7 +41,7 @@ extern audio_output audio_pulse; #ifdef CONFIG_ALSA extern audio_output audio_alsa; #endif -extern audio_output audio_dummy, audio_pipe; +extern audio_output audio_dummy, audio_pipe, audio_stdout; static audio_output *outputs[] = { #ifdef CONFIG_SNDIO @@ -56,7 +56,7 @@ static audio_output *outputs[] = { #ifdef CONFIG_AO &audio_ao, #endif - &audio_dummy, &audio_pipe, NULL}; + &audio_dummy, &audio_pipe, &audio_stdout, NULL}; audio_output *audio_get_output(char *name) { audio_output **out; diff --git a/audio_pipe.c b/audio_pipe.c index 12b33360..b1e5e09c 100644 --- a/audio_pipe.c +++ b/audio_pipe.c @@ -53,6 +53,9 @@ static void stop(void) { } static int init(int argc, char **argv) { + const char *str; + int value; + config.audio_backend_buffer_desired_length = 44100; // one second. config.audio_backend_latency_offset = 0; @@ -62,6 +65,26 @@ static int init(int argc, char **argv) { if (config_lookup_string(config.cfg, "pipe.name", &str)) { pipename = (char *)str; } + + /* Get the desired buffer size setting. */ + if (config_lookup_int(config.cfg, "pipe.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 66150)) + die("Invalid pipe audio backend buffer desired length \"%sd\". It should be between 0 and " + "66150, default is 44100", + value); + else + config.audio_backend_buffer_desired_length = value; + } + + /* Get the latency offset. */ + if (config_lookup_int(config.cfg, "pipe.audio_backend_latency_offset", &value)) { + if ((value < -22050) || (value > 22050)) + die("Invalid pipe audio backend buffer latency offset \"%sd\". It should be between -22050 " + "and +22050, default is 0", + value); + else + config.audio_backend_latency_offset = value; + } } if ((pipename == NULL) && (argc != 1)) @@ -73,7 +96,7 @@ static int init(int argc, char **argv) { // here, create the pipe if (strcasecmp(pipename, "STDOUT") != 0) if (mkfifo(pipename, 0644) && errno != EEXIST) - die("Could not create metadata FIFO %s", pipename); + die("Could not create output pipe \"%s\"", pipename); debug(1, "Pipename is \"%s\"", pipename); @@ -90,7 +113,7 @@ static void deinit(void) { } static void help(void) { - printf(" pipe takes 1 argument: the name of the FIFO to write to, which can be \"stdout\".\n"); + printf(" pipe takes 1 argument: the name of the FIFO to write to.\n"); } audio_output audio_pipe = {.name = "pipe", diff --git a/audio_stdout.c b/audio_stdout.c new file mode 100644 index 00000000..481a8095 --- /dev/null +++ b/audio_stdout.c @@ -0,0 +1,101 @@ +/* + * pipe output driver. This file is part of Shairport. + * Copyright (c) James Laird 2013 + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "audio.h" + +static int fd = -1; + +static void start(int sample_rate) { + fd = STDOUT_FILENO; +} + +static void play(short buf[], int samples) { + int ignore = write(fd, buf, samples * 4); +} + +static void stop(void) { + // don't close stdout +} + +static int init(int argc, char **argv) { + const char *str; + int value; + + config.audio_backend_buffer_desired_length = 44100; // one second. + config.audio_backend_latency_offset = 0; + + if (config.cfg != NULL) { + /* Get the desired buffer size setting. */ + debug(1,"Get buffer length"); + if (config_lookup_int(config.cfg, "stdout.audio_backend_buffer_desired_length", &value)) { + if ((value < 0) || (value > 132300)) + die("Invalid stdout audio backend buffer desired length \"%d\". It should be between 0 and 132300, default is 44100", + value); + else + config.audio_backend_buffer_desired_length = value; + } + + /* Get the latency offset. */ + debug(1,"Get latency"); + if (config_lookup_int(config.cfg, "stdout.audio_backend_latency_offset", &value)) { + debug(1,"Got a latency value of %d.",value); + if ((value < -66150) || (value > 66150)) + die("Invalid stdout audio backend buffer latency offset \"%d\". It should be between -66150 and +66150, default is 0", + value); + else + config.audio_backend_latency_offset = value; + } + } + debug(1,"buffer: %d, latency: %d.",config.audio_backend_buffer_desired_length,config.audio_backend_latency_offset); + return 0; +} + +static void deinit(void) { + // don't close stdout +} + +static void help(void) { + printf(" stdout takes no arguments\n"); +} + +audio_output audio_stdout = {.name = "stdout", + .help = &help, + .init = &init, + .deinit = &deinit, + .start = &start, + .stop = &stop, + .flush = NULL, + .delay = NULL, + .play = &play, + .volume = NULL, + .parameters = NULL}; diff --git a/player.c b/player.c index 6d28d785..65fa6542 100644 --- a/player.c +++ b/player.c @@ -855,6 +855,11 @@ typedef struct stats { // statistics for running averages } stats_t; static void *player_thread_func(void *arg) { + // check that there are enough buffers to accommodate the desired latency and the latency offset + + int maximum_latency = config.latency+config.audio_backend_latency_offset; + if ((maximum_latency+(352-1))/352 + 10 > BUFFER_FRAMES) + die("Not enough buffers available for a total latency of %d frames. A maximum of %d 352-frame packets may be accommodated.",maximum_latency,BUFFER_FRAMES); connection_state_to_output = get_requested_connection_state_to_output(); // this is about half a minute #define trend_interval 3758 diff --git a/rtsp.c b/rtsp.c index 0f9b1226..f25ba57b 100644 --- a/rtsp.c +++ b/rtsp.c @@ -972,9 +972,6 @@ void metadata_process(uint32_t type, uint32_t code, char *data, uint32_t length) return; remaining_data += towrite_count; remaining_count -= towrite_count; - // ret = write(fd,"\r\n",2); - // if (ret<0) - // return; } snprintf(thestring, 1024, "\n"); ret = non_blocking_write(fd, thestring, strlen(thestring)); diff --git a/shairport.c b/shairport.c index 5ab2638c..97cd0bbd 100644 --- a/shairport.c +++ b/shairport.c @@ -497,38 +497,6 @@ int parse_options(int argc, char **argv) { } #endif - - /* Print out options */ - - debug(2, "statistics_requester status is %d.", config.statistics_requested); - debug(2, "daemon status is %d.", config.daemonise); - debug(2, "rtsp listening port is %d.", config.port); - debug(2, "Shairport Sync player name is \"%s\".", config.apname); - debug(2, "Audio Output name is \"%s\".", config.output_name); - debug(2, "on-start action is \"%s\".", config.cmd_start); - debug(2, "on-stop action is \"%s\".", config.cmd_stop); - debug(2, "wait-cmd status is %d.", config.cmd_blocking); - debug(2, "mdns backend \"%s\".", config.mdns_name); - debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); - debug(2, "AirPlayLatency is %d.", config.AirPlayLatency); - debug(2, "iTunesLatency is %d.", config.iTunesLatency); - debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); - debug(2, "stuffing option is \"%d\".", config.packet_stuffing); - debug(2, "resync time is %d.", config.resyncthreshold); - debug(2, "allow a session to be interrupted: %d.", config.allow_session_interruption); - debug(2, "busy timeout time is %d.", config.timeout); - debug(2, "tolerance is %d frames.", config.tolerance); - debug(2, "password is \"%s\".", config.password); - debug(2, "ignore_volume_control is %d.", config.ignore_volume_control); - debug(2, "audio backend desired buffer length is %d.", - config.audio_backend_buffer_desired_length); - debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); -#ifdef CONFIG_METADATA - debug(2, "metdata enabled is %d.", config.metadata_enabled); - debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); - debug(2, "get-coverart is %d.", config.get_coverart); -#endif - return optind + 1; } @@ -797,6 +765,37 @@ int main(int argc, char **argv) { config.output->init(argc - audio_arg, argv + audio_arg); daemon_log(LOG_NOTICE, "startup"); + + /* Print out options */ + + debug(2, "statistics_requester status is %d.", config.statistics_requested); + debug(2, "daemon status is %d.", config.daemonise); + debug(2, "rtsp listening port is %d.", config.port); + debug(2, "Shairport Sync player name is \"%s\".", config.apname); + debug(2, "Audio Output name is \"%s\".", config.output_name); + debug(2, "on-start action is \"%s\".", config.cmd_start); + debug(2, "on-stop action is \"%s\".", config.cmd_stop); + debug(2, "wait-cmd status is %d.", config.cmd_blocking); + debug(2, "mdns backend \"%s\".", config.mdns_name); + debug(2, "userSuppliedLatency is %d.", config.userSuppliedLatency); + debug(2, "AirPlayLatency is %d.", config.AirPlayLatency); + debug(2, "iTunesLatency is %d.", config.iTunesLatency); + debug(2, "forkedDaapdLatency is %d.", config.ForkedDaapdLatency); + debug(2, "stuffing option is \"%d\".", config.packet_stuffing); + debug(2, "resync time is %d.", config.resyncthreshold); + debug(2, "allow a session to be interrupted: %d.", config.allow_session_interruption); + debug(2, "busy timeout time is %d.", config.timeout); + debug(2, "tolerance is %d frames.", config.tolerance); + debug(2, "password is \"%s\".", config.password); + debug(2, "ignore_volume_control is %d.", config.ignore_volume_control); + debug(2, "audio backend desired buffer length is %d.", + config.audio_backend_buffer_desired_length); + debug(2, "audio backend latency offset is %d.", config.audio_backend_latency_offset); +#ifdef CONFIG_METADATA + debug(2, "metdata enabled is %d.", config.metadata_enabled); + debug(2, "metadata pipename is \"%s\".", config.metadata_pipename); + debug(2, "get-coverart is %d.", config.get_coverart); +#endif uint8_t ap_md5[16];