Large diffs are not rendered by default.

@@ -0,0 +1,57 @@
#ifndef __ALAC__DECOMP_H
#define __ALAC__DECOMP_H

#include <stdint.h>

typedef struct alac_file alac_file;

alac_file *alac_create(int samplesize, int numchannels);
void alac_decode_frame(alac_file *alac,
unsigned char *inbuffer,
void *outbuffer, int *outputsize);
void alac_set_info(alac_file *alac, char *inputbuffer);
void alac_allocate_buffers(alac_file *alac);
void alac_free(alac_file *alac);

struct alac_file
{
unsigned char *input_buffer;
int input_buffer_bitaccumulator; /* used so we can do arbitary
bit reads */

int samplesize;
int numchannels;
int bytespersample;


/* buffers */
int32_t *predicterror_buffer_a;
int32_t *predicterror_buffer_b;

int32_t *outputsamples_buffer_a;
int32_t *outputsamples_buffer_b;

int32_t *uncompressed_bytes_buffer_a;
int32_t *uncompressed_bytes_buffer_b;



/* stuff from setinfo */
uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */ /* max samples per frame? */
uint8_t setinfo_7a; /* 0x00 */
uint8_t setinfo_sample_size; /* 0x10 */
uint8_t setinfo_rice_historymult; /* 0x28 */
uint8_t setinfo_rice_initialhistory; /* 0x0a */
uint8_t setinfo_rice_kmodifier; /* 0x0e */
uint8_t setinfo_7f; /* 0x02 */
uint16_t setinfo_80; /* 0x00ff */
uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */
uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */
uint32_t setinfo_8a_rate; /* 0x0000ac44 */
/* end setinfo stuff */

};


#endif /* __ALAC__DECOMP_H */

@@ -0,0 +1,91 @@
/*
* Audio driver handler. 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 <stdio.h>
#include <string.h>
#include "audio.h"
#include "config.h"

#ifdef CONFIG_SNDIO
extern audio_output audio_sndio;
#endif
#ifdef CONFIG_AO
extern audio_output audio_ao;
#endif
#ifdef CONFIG_PULSE
extern audio_output audio_pulse;
#endif
#ifdef CONFIG_ALSA
extern audio_output audio_alsa;
#endif
extern audio_output audio_dummy, audio_pipe;

static audio_output *outputs[] = {
#ifdef CONFIG_SNDIO
&audio_sndio,
#endif
#ifdef CONFIG_ALSA
&audio_alsa,
#endif
#ifdef CONFIG_PULSE
&audio_pulse,
#endif
#ifdef CONFIG_AO
&audio_ao,
#endif
&audio_dummy,
&audio_pipe,
NULL
};


audio_output *audio_get_output(char *name) {
audio_output **out;

// default to the first
if (!name)
return outputs[0];

for (out=outputs; *out; out++)
if (!strcasecmp(name, (*out)->name))
return *out;

return NULL;
}

void audio_ls_outputs(void) {
audio_output **out;

printf("Available audio outputs:\n");
for (out=outputs; *out; out++)
printf(" %s%s\n", (*out)->name, out==outputs ? " (default)" : "");

for (out=outputs; *out; out++) {
printf("\n");
printf("Options for output %s:\n", (*out)->name);
(*out)->help();
}
}
@@ -0,0 +1,25 @@
#ifndef _AUDIO_H
#define _AUDIO_H

typedef struct {
void (*help)(void);
char *name;

// start of program
int (*init)(int argc, char **argv);
// at end of program
void (*deinit)(void);

void (*start)(int sample_rate);
// block of samples
void (*play)(short buf[], int samples);
void (*stop)(void);

// may be NULL, in which case soft volume is applied
void (*volume)(double vol);
} audio_output;

audio_output *audio_get_output(char *name);
void audio_ls_outputs(void);

#endif //_AUDIO_H
@@ -0,0 +1,195 @@
/*
* libalsa output driver. This file is part of Shairport.
* Copyright (c) Muffinman, Skaman 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.
*/

#define ALSA_PCM_NEW_HW_PARAMS_API

#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <alsa/asoundlib.h>
#include "common.h"
#include "audio.h"

static void help(void);
static int init(int argc, char **argv);
static void deinit(void);
static void start(int sample_rate);
static void play(short buf[], int samples);
static void stop(void);
static void volume(double vol);

audio_output audio_alsa = {
.name = "alsa",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.play = &play,
.volume = NULL
};

static snd_pcm_t *alsa_handle = NULL;
static snd_pcm_hw_params_t *alsa_params = NULL;

static snd_mixer_t *alsa_mix_handle = NULL;
static snd_mixer_elem_t *alsa_mix_elem = NULL;
static snd_mixer_selem_id_t *alsa_mix_sid = NULL;
static long alsa_mix_minv, alsa_mix_range;

static char *alsa_out_dev = "default";
static char *alsa_mix_dev = NULL;
static char *alsa_mix_ctrl = "Master";
static int alsa_mix_index = 0;

static void help(void) {
printf(" -d output-device set the output device [default*|...]\n"
" -t mixer-type set the mixer type [software*|hardware]\n"
" -m mixer-device set the mixer device ['output-device'*|...]\n"
" -c mixer-control set the mixer control [Master*|...]\n"
" -i mixer-index set the mixer index [0*|...]\n"
" *) default option\n"
);
}

static int init(int argc, char **argv) {
int hardware_mixer = 0;

optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
argc++;
// some platforms apparently require optreset = 1; - which?
int opt;
while ((opt = getopt(argc, argv, "d:t:m:c:i:")) > 0) {
switch (opt) {
case 'd':
alsa_out_dev = optarg;
break;
case 't':
if (strcmp(optarg, "hardware") == 0)
hardware_mixer = 1;
break;
case 'm':
alsa_mix_dev = optarg;
break;
case 'c':
alsa_mix_ctrl = optarg;
break;
case 'i':
alsa_mix_index = strtol(optarg, NULL, 10);
break;
default:
help();
die("Invalid audio option -%c specified", opt);
}
}

if (optind < argc)
die("Invalid audio argument: %s", argv[optind]);

if (!hardware_mixer)
return 0;

if (alsa_mix_dev == NULL)
alsa_mix_dev = alsa_out_dev;
audio_alsa.volume = &volume;

int ret = 0;
long alsa_mix_maxv;

snd_mixer_selem_id_alloca(&alsa_mix_sid);
snd_mixer_selem_id_set_index(alsa_mix_sid, alsa_mix_index);
snd_mixer_selem_id_set_name(alsa_mix_sid, alsa_mix_ctrl);

if ((snd_mixer_open(&alsa_mix_handle, 0)) < 0)
die ("Failed to open mixer");
if ((snd_mixer_attach(alsa_mix_handle, alsa_mix_dev)) < 0)
die ("Failed to attach mixer");
if ((snd_mixer_selem_register(alsa_mix_handle, NULL, NULL)) < 0)
die ("Failed to register mixer element");

ret = snd_mixer_load(alsa_mix_handle);
if (ret < 0)
die ("Failed to load mixer element");
alsa_mix_elem = snd_mixer_find_selem(alsa_mix_handle, alsa_mix_sid);
if (!alsa_mix_elem)
die ("Failed to find mixer element");
snd_mixer_selem_get_playback_volume_range (alsa_mix_elem, &alsa_mix_minv, &alsa_mix_maxv);
alsa_mix_range = alsa_mix_maxv - alsa_mix_minv;

return 0;
}

static void deinit(void) {
stop();
if (alsa_mix_handle) {
snd_mixer_close(alsa_mix_handle);
}
}

static void start(int sample_rate) {
if (sample_rate != 44100)
die("Unexpected sample rate!");

int ret, dir = 0;
snd_pcm_uframes_t frames = 64;
ret = snd_pcm_open(&alsa_handle, alsa_out_dev, SND_PCM_STREAM_PLAYBACK, 0);
if (ret < 0)
die("Alsa initialization failed: unable to open pcm device: %s\n", snd_strerror(ret));

snd_pcm_hw_params_alloca(&alsa_params);
snd_pcm_hw_params_any(alsa_handle, alsa_params);
snd_pcm_hw_params_set_access(alsa_handle, alsa_params, SND_PCM_ACCESS_MMAP_INTERLEAVED);
snd_pcm_hw_params_set_format(alsa_handle, alsa_params, SND_PCM_FORMAT_S16);
snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, 2);
snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, (unsigned int *)&sample_rate, &dir);
snd_pcm_hw_params_set_period_size_near(alsa_handle, alsa_params, &frames, &dir);
ret = snd_pcm_hw_params(alsa_handle, alsa_params);
if (ret < 0)
die("unable to set hw parameters: %s\n", snd_strerror(ret));
}

static void play(short buf[], int samples) {
int err = snd_pcm_mmap_writei(alsa_handle, (char*)buf, samples);
if (err < 0)
err = snd_pcm_recover(alsa_handle, err, 0);
if (err < 0)
die("Failed to write to PCM device: %s\n", snd_strerror(err));
}

static void stop(void) {
if (alsa_handle) {
snd_pcm_drain(alsa_handle);
snd_pcm_close(alsa_handle);
alsa_handle = NULL;
}
}

static void volume(double vol) {
long alsa_volume = (vol*alsa_mix_range)+alsa_mix_minv;
if(snd_mixer_selem_set_playback_volume_all(alsa_mix_elem, alsa_volume) != 0)
die ("Failed to set playback volume");
}
@@ -0,0 +1,129 @@
/*
* libao 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 <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <ao/ao.h>
#include "common.h"
#include "audio.h"

ao_device *dev = NULL;

static void help(void) {
printf(" -d driver set the output driver\n"
" -o name=value set an arbitrary ao option\n"
" -i id shorthand for -o id=<id>\n"
" -n name shorthand for -o dev=<name> -o dsp=<name>\n"
);
}

static int init(int argc, char **argv) {
ao_initialize();
int driver = ao_default_driver_id();
ao_option *ao_opts = NULL;

optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
argc++;
// some platforms apparently require optreset = 1; - which?
int opt;
char *mid;
while ((opt = getopt(argc, argv, "d:i:n:o:")) > 0) {
switch (opt) {
case 'd':
driver = ao_driver_id(optarg);
if (driver < 0)
die("could not find ao driver %s", optarg);
break;
case 'i':
ao_append_option(&ao_opts, "id", optarg);
break;
case 'n':
ao_append_option(&ao_opts, "dev", optarg);
// Old libao versions (for example, 0.8.8) only support
// "dsp" instead of "dev".
ao_append_option(&ao_opts, "dsp", optarg);
break;
case 'o':
mid = strchr(optarg, '=');
if (!mid)
die("Expected an = in audio option %s", optarg);
*mid = 0;
ao_append_option(&ao_opts, optarg, mid+1);
break;
default:
help();
die("Invalid audio option -%c specified", opt);
}
}

if (optind < argc)
die("Invalid audio argument: %s", argv[optind]);

ao_sample_format fmt;
memset(&fmt, 0, sizeof(fmt));

fmt.bits = 16;
fmt.rate = 44100;
fmt.channels = 2;
fmt.byte_format = AO_FMT_NATIVE;

dev = ao_open_live(driver, &fmt, ao_opts);

return dev ? 0 : 1;
}

static void deinit(void) {
if (dev)
ao_close(dev);
dev = NULL;
ao_shutdown();
}

static void start(int sample_rate) {
if (sample_rate != 44100)
die("unexpected sample rate!");
}

static void play(short buf[], int samples) {
ao_play(dev, (char*)buf, samples*4);
}

static void stop(void) {
}

audio_output audio_ao = {
.name = "ao",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.play = &play,
.volume = NULL
};
@@ -0,0 +1,84 @@
/*
* dummy 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 <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include "audio.h"

int Fs;
long long starttime, samples_played;

static int init(int argc, char **argv) {
return 0;
}

static void deinit(void) {
}

static void start(int sample_rate) {
Fs = sample_rate;
starttime = 0;
samples_played = 0;
printf("dummy audio output started at Fs=%d Hz\n", sample_rate);
}

static void play(short buf[], int samples) {
struct timeval tv;

// this is all a bit expensive but it's long-term stable.
gettimeofday(&tv, NULL);

long long nowtime = tv.tv_usec + 1e6*tv.tv_sec;

if (!starttime)
starttime = nowtime;

samples_played += samples;

long long finishtime = starttime + samples_played * 1e6 / Fs;

usleep(finishtime - nowtime);
}

static void stop(void) {
printf("dummy audio stopped\n");
}

static void help(void) {
printf(" There are no options for dummy audio.\n");
}

audio_output audio_dummy = {
.name = "dummy",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.play = &play,
.volume = NULL
};
@@ -0,0 +1,140 @@
/*
* 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 <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "common.h"
#include "audio.h"

static int fd = -1;
static char *pipename = NULL;
static int Fs;
static long long starttime, samples_played;

static void stop(void) {
close(fd);
fd = -1;
}

static void start(int sample_rate) {
if (fd >= 0)
stop();

fd = open(pipename, O_WRONLY | O_NONBLOCK);
if ((fd < 0) && (errno != ENXIO)) {
perror("open");
die("could not open specified pipe for writing");
}

// The other end is ready, reopen with blocking
if (fd >= 0) {
close(fd);
fd = open(pipename, O_WRONLY);
}

Fs = sample_rate;
starttime = 0;
samples_played = 0;
}

// Wait procedure taken from audio_dummy.c
static void wait_samples(int samples) {
struct timeval tv;

// this is all a bit expensive but it's long-term stable.
gettimeofday(&tv, NULL);

long long nowtime = tv.tv_usec + 1e6*tv.tv_sec;

if (!starttime)
starttime = nowtime;

samples_played += samples;

long long finishtime = starttime + samples_played * 1e6 / Fs;

usleep(finishtime - nowtime);
}

static void play(short buf[], int samples) {
if (fd < 0) {
wait_samples(samples);

// check if the other end is ready every 5 seconds
if (samples_played > 5 * Fs)
start(Fs);

return;
}

if (write(fd, buf, samples*4) < 0)
stop();
}

static int init(int argc, char **argv) {
struct stat sb;

if (argc != 1)
die("bad argument(s) to pipe");

pipename = strdup(argv[0]);

if (stat(pipename, &sb) < 0)
die("could not stat() pipe");

if (!S_ISFIFO(sb.st_mode))
die("not a pipe");

return 0;
}

static void deinit(void) {
if (fd >= 0)
stop();
if (pipename)
free(pipename);
}

static void help(void) {
printf(" pipe takes 1 argument: the name of the FIFO to write to.\n");
}

audio_output audio_pipe = {
.name = "pipe",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.play = &play,
.volume = NULL
};
@@ -0,0 +1,128 @@
/*
* PulseAudio output driver. This file is part of Shairport.
* Copyright (c) Paul Lietar 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 <stdio.h>
#include <unistd.h>
#include <memory.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include "common.h"
#include "audio.h"

static pa_simple *pa_dev = NULL;
static int pa_error;

static void help(void) {
printf(" -a server set the server name\n"
" -s sink set the output sink\n"
" -n name set the application name, as seen by PulseAudio\n"
" defaults to the access point name\n"
);
}

static int init(int argc, char **argv) {
char *pa_server = NULL;
char *pa_sink = NULL;
char *pa_appname = config.apname;

optind = 1; // optind=0 is equivalent to optind=1 plus special behaviour
argv--; // so we shift the arguments to satisfy getopt()
argc++;

// some platforms apparently require optreset = 1; - which?
int opt;
while ((opt = getopt(argc, argv, "a:s:n:")) > 0) {
switch (opt) {
case 'a':
pa_server = optarg;
break;
case 's':
pa_sink = optarg;
break;
case 'n':
pa_appname = optarg;
break;
default:
help();
die("Invalid audio option -%c specified", opt);
}
}

if (optind < argc)
die("Invalid audio argument: %s", argv[optind]);

static const pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE,
.rate = 44100,
.channels = 2
};

pa_dev = pa_simple_new(pa_server,
pa_appname,
PA_STREAM_PLAYBACK,
pa_sink,
"Shairport Stream",
&ss, NULL, NULL,
&pa_error);

if (!pa_dev)
die("Could not connect to pulseaudio server: %s", pa_strerror(pa_error));

return 0;
}

static void deinit(void) {
if (pa_dev)
pa_simple_free(pa_dev);
pa_dev = NULL;
}

static void start(int sample_rate) {
if (sample_rate != 44100)
die("unexpected sample rate!");
}

static void play(short buf[], int samples) {
if( pa_simple_write(pa_dev, (char *)buf, (size_t)samples * 4, &pa_error) < 0 )
fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(pa_error));
}

static void stop(void) {
if (pa_simple_drain(pa_dev, &pa_error) < 0)
fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(pa_error));
}

audio_output audio_pulse = {
.name = "pulse",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.play = &play,
.volume = NULL
};
@@ -0,0 +1,84 @@
/*
* sndio output driver. This file is part of Shairport.
* Copyright (c) 2013 Dimitri Sokolyuk <demon@dim13.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <stdio.h>
#include <unistd.h>
#include <sndio.h>
#include "audio.h"

static struct sio_hdl *sio;
static struct sio_par par;

static int init(int argc, char **argv) {
sio = sio_open(SIO_DEVANY, SIO_PLAY, 0);
if (!sio)
die("sndio: cannot connect to sound server");

sio_initpar(&par);

par.bits = 16;
par.rate = 44100;
par.pchan = 2;
par.le = SIO_LE_NATIVE;
par.sig = 1;

if (!sio_setpar(sio, &par))
die("sndio: failed to set audio parameters");
if (!sio_getpar(sio, &par))
die("sndio: failed to get audio parameters");

return 0;
}

static void deinit(void) {
sio_close(sio);
}

static void start(int sample_rate) {
if (sample_rate != par.rate)
die("unexpected sample rate!");
sio_start(sio);
}

static void play(short buf[], int samples) {
sio_write(sio, (char *)buf, samples * par.bps * par.pchan);
}

static void stop(void) {
sio_stop(sio);
}

static void help(void) {
printf(" There are no options for sndio audio.\n");
printf(" Use AUDIODEVICE environment variable.\n");
}

static void volume(double vol) {
unsigned int v = vol * SIO_MAXVOL;
sio_setvol(sio, v);
}

audio_output audio_sndio = {
.name = "sndio",
.help = &help,
.init = &init,
.deinit = &deinit,
.start = &start,
.stop = &stop,
.play = &play,
.volume = &volume
};
@@ -0,0 +1,204 @@
/*
* Utility routines. 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 <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <memory.h>
#include <unistd.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include "common.h"
#include "daemon.h"

shairport_cfg config;

int debuglev = 0;

void die(char *format, ...) {
fprintf(stderr, "FATAL: ");

va_list args;
va_start(args, format);

vfprintf(stderr, format, args);
if (config.daemonise)
daemon_fail(format, args); // Send error message to parent

va_end(args);

fprintf(stderr, "\n");
shairport_shutdown(1);
}

void warn(char *format, ...) {
fprintf(stderr, "WARNING: ");
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fprintf(stderr, "\n");
}

void debug(int level, char *format, ...) {
if (level > debuglev)
return;
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}


char *base64_enc(uint8_t *input, int length) {
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
BIO_write(b64, input, length);
BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);

char *buf = (char *)malloc(bptr->length);
if (bptr->length) {
memcpy(buf, bptr->data, bptr->length-1);
buf[bptr->length-1] = 0;
}

BIO_free_all(bmem);

return buf;
}

uint8_t *base64_dec(char *input, int *outlen) {
BIO *bmem, *b64;
int inlen = strlen(input);

b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);

// Apple cut the padding off their challenges; restore it
BIO_write(bmem, input, inlen);
while (inlen++ & 3)
BIO_write(bmem, "=", 1);
BIO_flush(bmem);

int bufsize = strlen(input)*3/4 + 1;
uint8_t *buf = malloc(bufsize);
int nread;

nread = BIO_read(b64, buf, bufsize);

BIO_free_all(bmem);

*outlen = nread;
return buf;
}

static char super_secret_key[] =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
"-----END RSA PRIVATE KEY-----";

uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
static RSA *rsa = NULL;

if (!rsa) {
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
BIO_free(bmem);
}

uint8_t *out = malloc(RSA_size(rsa));
switch (mode) {
case RSA_MODE_AUTH:
*outlen = RSA_private_encrypt(inlen, input, out, rsa,
RSA_PKCS1_PADDING);
break;
case RSA_MODE_KEY:
*outlen = RSA_private_decrypt(inlen, input, out, rsa,
RSA_PKCS1_OAEP_PADDING);
break;
default:
die("bad rsa mode");
}
return out;
}

void command_start(void) {
if (!config.cmd_start)
return;
if (!config.cmd_blocking && fork())
return;

debug(1, "running start command: %s", config.cmd_start);
if (system(config.cmd_start))
warn("exec of external start command failed");

if (!config.cmd_blocking)
exit(0);
}

void command_stop(void) {
if (!config.cmd_stop)
return;
if (!config.cmd_blocking && fork())
return;

debug(1, "running stop command: %s", config.cmd_stop);
if (system(config.cmd_stop))
warn("exec of external stop command failed");

if (!config.cmd_blocking)
exit(0);
}
@@ -0,0 +1,66 @@
#ifndef _COMMON_H
#define _COMMON_H

#include <openssl/rsa.h>
#include <stdint.h>
#include <sys/socket.h>
#include "audio.h"
#include "mdns.h"

// struct sockaddr_in6 is bigger than struct sockaddr. derp
#ifdef AF_INET6
#define SOCKADDR struct sockaddr_storage
#define SAFAMILY ss_family
#else
#define SOCKADDR struct sockaddr
#define SAFAMILY sa_family
#endif


typedef struct {
char *password;
char *apname;
uint8_t hw_addr[6];
int port;
char *output_name;
audio_output *output;
char *mdns_name;
mdns_backend *mdns;
int buffer_start_fill;
int daemonise;
char *cmd_start, *cmd_stop;
int cmd_blocking;
char *meta_dir;
char *pidfile;
char *logfile;
char *errfile;
} shairport_cfg;

extern int debuglev;
void die(char *format, ...);
void warn(char *format, ...);
void debug(int level, char *format, ...);

/* functions that ignore return values without compiler warnings.
* for use only where return values really don't matter!
*/
#define write_unchecked(...) (void)(write(__VA_ARGS__)+1)
#define read_unchecked(...) (void)(read (__VA_ARGS__)+1)
#define lockf_unchecked(...) (void)(lockf(__VA_ARGS__)+1)

uint8_t *base64_dec(char *input, int *outlen);
char *base64_enc(uint8_t *input, int length);

#define RSA_MODE_AUTH (0)
#define RSA_MODE_KEY (1)
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode);

void command_start(void);
void command_stop(void);

extern shairport_cfg config;

void shairport_shutdown(int retval);
void shairport_startup_complete(void);

#endif // _COMMON_H
@@ -0,0 +1,4 @@
// automatically generated file
#define CONFIG_ALSA
#define CONFIG_AVAHI
#define CONFIG_HAVE_GETOPT_H
@@ -0,0 +1,5 @@
CONFIG_ALSA=yes
CONFIG_AVAHI=yes
CONFIG_HAVE_GETOPT_H=yes
CFLAGS+=-Os -pipe -mno-branch-likely -mips32r2 -mtune=24kec -mdsp -fno-caller-saves -fhonour-copts -Wno-error=unused-but-set-variable -Wno-error=unused-result -msoft-float -fpic -I/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include -I/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include -I/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include/alsa -D_REENTRANT -I/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/include
LDFLAGS+=-L/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/lib -L/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/lib -L/home/mango/openwrt/staging_dir/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/usr/lib -L/home/mango/openwrt/staging_dir/toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2/lib -lm -lpthread -L/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/lib -lssl -lcrypto -L/home/mango/openwrt/staging_dir/target-mipsel_24kec+dsp_uClibc-0.9.33.2/usr/lib -lasound -lavahi-common -lavahi-client
@@ -0,0 +1,88 @@
#!/bin/sh

[ -z "${CC}" ] && CC=gcc

echo Configuring Shairport

rm -f config.mk config.h

echo "// automatically generated file" > config.h

LDFLAGS="${LDFLAGS} -lm -lpthread"

export_config()
{
echo "#define $1" >> config.h
echo "$1=yes" >> config.mk
eval "$1=yes"
}

# check for header $1 with CONFIG_ var $2
check_header()
{
echo "#include<$1>" > .header_test.c
if ${CC} ${CFLAGS} ${LDFLAGS} -c .header_test.c > /dev/null 2>&1 /dev/null; then
echo "$1 found"
if [ "$2" ]; then
export_config $2
fi
else
echo "$1 not found"
if [ ! "$2" ]; then
echo "Required header not found, cannot continue"
rm .header_test.c
rm -f config.mk config.h
exit 1
fi
fi

rm -f .header_test.c .header_test.o
}

# check for and use pkg-config package $2 with CONFIG_ var $3
do_pkg_config()
{
if pkg-config $2 2>/dev/null; then
CFLAGS="${CFLAGS} `pkg-config --cflags $2`"
LDFLAGS="${LDFLAGS} `pkg-config --libs $2`"
if [ "$3" ]; then
export_config $3
fi
echo "$1 found"
else
echo "$1 or its dev package not found"
if [ ! "$3" ]; then
echo "Required package not found, cannot continue"
rm -f config.mk config.h
exit 1
fi
fi
}

do_pkg_config OpenSSL openssl
#do_pkg_config libao ao CONFIG_AO
#do_pkg_config PulseAudio libpulse-simple CONFIG_PULSE
do_pkg_config ALSA alsa CONFIG_ALSA
do_pkg_config Avahi\ client avahi-client CONFIG_AVAHI

if [ `uname` = 'OpenBSD' ]; then
echo "OpenBSD machine, use sndio"
export_config CONFIG_SNDIO
LDFLAGS="${LDFLAGS} -lsndio"
fi

check_header getopt.h CONFIG_HAVE_GETOPT_H

# Don't build dns_sd backend if we have avahi
if [ -z "$CONFIG_AVAHI" ]; then
check_header dns_sd.h CONFIG_HAVE_DNS_SD_H
fi


echo "CFLAGS+=${CFLAGS}" >> config.mk
echo "LDFLAGS+=${LDFLAGS}" >> config.mk

echo CFLAGS: ${CFLAGS}
echo LDFLAGS: ${LDFLAGS}

echo "Configure successful. You may now build with 'make'"
@@ -0,0 +1,109 @@
/*
* This file is part of Shairport.
* Copyright (c) Paul Lietar 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 <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

#include "common.h"

static int lock_fd = -1;
static int daemon_pipe[2] = {-1, -1};

void daemon_init() {
int ret;
ret = pipe(daemon_pipe);
if (ret < 0)
die("couldn't create a pipe?!");

pid_t pid = fork();
if (pid < 0)
die("failed to fork!");

if (pid) {
close(daemon_pipe[1]);

char buf[64];
ret = read(daemon_pipe[0], buf, sizeof(buf));
if (ret < 0) {
// No response from child, something failed
fprintf(stderr, "Spawning the daemon failed.\n");
exit(1);
} else if (buf[0] != 0) {
// First byte is non zero, child sent error message
write_unchecked(STDERR_FILENO, buf, ret);
fprintf(stderr, "\n");
exit(1);
} else {
// Success !
if (!config.pidfile)
printf("%d\n", pid);
exit(0);
}
} else {
close(daemon_pipe[0]);

if (config.pidfile) {
lock_fd = open(config.pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (lock_fd < 0) {
die("Could not open pidfile");
}

ret = lockf(lock_fd,F_TLOCK,0);
if (ret < 0) {
die("Could not lock pidfile. Is an other instance running ?");
}

dprintf(lock_fd, "%d\n", getpid());
}
}
}

void daemon_ready() {
char ok = 0;
write_unchecked(daemon_pipe[1], &ok, 1);
close(daemon_pipe[1]);
daemon_pipe[1] = -1;
}

void daemon_fail(const char *format, va_list arg) {
// Are we still initializing ?
if (daemon_pipe[1] > 0) {
vdprintf(daemon_pipe[1], format, arg);
}
}

void daemon_exit() {
if (lock_fd > 0) {
lockf_unchecked(lock_fd, F_ULOCK, 0);
close(lock_fd);
unlink(config.pidfile);
lock_fd = -1;
}
}

@@ -0,0 +1,9 @@
#ifndef _DAEMON_H
#define _DAEMON_H

void daemon_init();
void daemon_ready();
void daemon_fail(const char *format, va_list arg);
void daemon_exit();

#endif // _DAEMON_H
@@ -0,0 +1,197 @@
/*
* getopt_long() -- long options parser
*
* Portions Copyright (c) 1987, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Portions Copyright (c) 2003
* PostgreSQL Global Development Group
*
* 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.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 THE REGENTS 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.
*
*/

#include <string.h>
#include "common.h"
#include "getopt_long.h"

#define BADCH '?'
#define BADARG ':'
#define EMSG ""


/*
* getopt_long
* Parse argc/argv argument vector, with long options.
*
* This implementation does not use optreset. Instead, we guarantee that
* it can be restarted on a new argv array after a previous call returned -1,
* if the caller resets optind to 1 before the first call of the new series.
* (Internally, this means we must be sure to reset "place" to EMSG before
* returning -1.)
*/
int
getopt_long(int argc, char *const argv[],
const char *optstring,
const struct option * longopts, int *longindex)
{
static char *place = EMSG; /* option letter processing */
char *oli; /* option letter list index */

if (!*place)
{ /* update scanning pointer */
if (optind >= argc)
{
place = EMSG;
return -1;
}

place = argv[optind];

if (place[0] != '-')
{
place = EMSG;
return -1;
}

place++;

if (place[0] && place[0] == '-' && place[1] == '\0')
{ /* found "--" */
++optind;
place = EMSG;
return -1;
}

if (place[0] && place[0] == '-' && place[1])
{
/* long option */
size_t namelen;
int i;

place++;

namelen = strcspn(place, "=");
for (i = 0; longopts[i].name != NULL; i++)
{
if (strlen(longopts[i].name) == namelen
&& strncmp(place, longopts[i].name, namelen) == 0)
{
if (longopts[i].has_arg)
{
if (place[namelen] == '=')
optarg = place + namelen + 1;
else if (optind < argc - 1)
{
optind++;
optarg = argv[optind];
}
else
{
if (optstring[0] == ':')
return BADARG;
if (opterr)
warn(
"%s: option requires an argument -- %s\n",
argv[0], place);
place = EMSG;
optind++;
return BADCH;
}
}
else
{
optarg = NULL;
if (place[namelen] != 0)
{
/* XXX error? */
}
}

optind++;

if (longindex)
*longindex = i;

place = EMSG;

if (longopts[i].flag == NULL)
return longopts[i].val;
else
{
*longopts[i].flag = longopts[i].val;
return 0;
}
}
}

if (opterr && optstring[0] != ':')
warn("illegal option -- %s\n", place);
place = EMSG;
optind++;
return BADCH;
}
}

/* short option */
optopt = (int) *place++;

oli = strchr(optstring, optopt);
if (!oli)
{
if (!*place)
++optind;
if (opterr && *optstring != ':')
warn("illegal option -- %c\n", optopt);
return BADCH;
}

if (oli[1] != ':')
{ /* don't need argument */
optarg = NULL;
if (!*place)
++optind;
}
else
{ /* need an argument */
if (*place) /* no white space */
optarg = place;
else if (argc <= ++optind)
{ /* no arg */
place = EMSG;
if (*optstring == ':')
return BADARG;
if (opterr)
warn("option requires an argument -- %c\n", optopt);
return BADCH;
}
else
/* white space */
optarg = argv[optind];
place = EMSG;
++optind;
}
return optopt;
}
@@ -0,0 +1,41 @@
/*
* Portions Copyright (c) 1987, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Portions Copyright (c) 2003-2013, PostgreSQL Global Development Group
*
*/
#ifndef GETOPT_LONG_H
#define GETOPT_LONG_H

#include "config.h"
#ifdef CONFIG_HAVE_GETOPT_H

#include <getopt.h>

#else

/* These are picked up from the system's getopt() facility. */
extern int opterr;
extern int optind;
extern int optopt;
extern char *optarg;

struct option
{
const char *name;
int has_arg;
int *flag;
int val;
};

#define no_argument 0
#define required_argument 1

extern int getopt_long(int argc, char *const argv[],
const char *optstring,
const struct option * longopts, int *longindex);

#endif /* CONFIG_HAVE_GETOPT_H */

#endif /* GETOPT_LONG_H */
@@ -0,0 +1,120 @@
/*
* mDNS registration handler. 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 <memory.h>
#include <string.h>
#include "config.h"
#include "common.h"
#include "mdns.h"

#ifdef CONFIG_AVAHI
extern mdns_backend mdns_avahi;
#endif

extern mdns_backend mdns_external_avahi;
extern mdns_backend mdns_external_dns_sd;
extern mdns_backend mdns_tinysvcmdns;

#ifdef CONFIG_HAVE_DNS_SD_H
extern mdns_backend mdns_dns_sd;
#endif

static mdns_backend *mdns_backends[] = {
#ifdef CONFIG_AVAHI
&mdns_avahi,
#endif
#ifdef CONFIG_HAVE_DNS_SD_H
&mdns_dns_sd,
#endif
&mdns_external_avahi,
&mdns_external_dns_sd,
&mdns_tinysvcmdns,
NULL
};

void mdns_register(void) {
char *mdns_apname = malloc(strlen(config.apname) + 14);
char *p = mdns_apname;
int i;
for (i=0; i<6; i++) {
sprintf(p, "%02X", config.hw_addr[i]);
p += 2;
}
*p++ = '@';
strcpy(p, config.apname);

mdns_backend **b = NULL;

if (config.mdns_name != NULL)
{
for (b = mdns_backends; *b; b++)
{
if (strcmp((*b)->name, config.mdns_name) != 0) // Not the one we are looking for
continue;
int error = (*b)->mdns_register(mdns_apname, config.port);
if (error >= 0)
{
config.mdns = *b;
}
break;
}

if (*b == NULL)
warn("%s mDNS backend not found");
}
else
{
for (b = mdns_backends; *b; b++)
{
int error = (*b)->mdns_register(mdns_apname, config.port);
if (error >= 0)
{
config.mdns = *b;
break;
}
}
}

if (config.mdns == NULL)
die("Could not establish mDNS advertisement!");
}

void mdns_unregister(void) {
if (config.mdns) {
config.mdns->mdns_unregister();
}
}

void mdns_ls_backends(void) {
mdns_backend **b = NULL;
printf("Available mDNS backends: \n");
for (b = mdns_backends; *b; b++)
{
printf(" %s\n", (*b)->name);
}
}

@@ -0,0 +1,20 @@
#ifndef _MDNS_H
#define _MDNS_H

extern int mdns_pid;

void mdns_unregister(void);
void mdns_register(void);
void mdns_ls_backends(void);

typedef struct {
char *name;
int (*mdns_register)(char *apname, int port);
void (*mdns_unregister)(void);
} mdns_backend;

#define MDNS_RECORD "tp=UDP", "sm=false", "ek=1", "et=0,1", "cn=0,1", "ch=2", \
"ss=16", "sr=44100", "vn=3", "txtvers=1", "da=true", "md=0,1,2", \
config.password ? "pw=true" : "pw=false"

#endif // _MDNS_H
@@ -0,0 +1,148 @@
/*
* Embedded Avahi client. 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 <avahi-client/client.h>
#include <avahi-client/publish.h>
#include <avahi-common/thread-watch.h>
#include <avahi-common/error.h>

#include <string.h>
#include "common.h"
#include "mdns.h"

static AvahiClient *client = NULL;
static AvahiEntryGroup *group = NULL;
static AvahiThreadedPoll *tpoll = NULL;

static char *name = NULL;
static int port = 0;

static void egroup_callback(AvahiEntryGroup *g,
AvahiEntryGroupState state,
AVAHI_GCC_UNUSED void *userdata) {
if (state==AVAHI_ENTRY_GROUP_COLLISION)
die("service name already exists on network!");
if (state==AVAHI_ENTRY_GROUP_FAILURE)
die("avahi entry group failure!");
}

static void register_service(AvahiClient *c) {
debug(1, "avahi: register_service\n");
if (!group)
group = avahi_entry_group_new(c, egroup_callback, NULL);
if (!group)
die("avahi_entry_group_new failed");

if (!avahi_entry_group_is_empty(group))
return;

int ret;
ret = avahi_entry_group_add_service(group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_UNSPEC,
0,
name,
"_raop._tcp",
NULL,
NULL,
port,
MDNS_RECORD,
NULL);
if (ret < 0)
die("avahi_entry_group_add_service failed");

ret = avahi_entry_group_commit(group);
if (ret < 0)
die("avahi_entry_group_commit failed");
}

static void client_callback(AvahiClient *c,
AvahiClientState state,
AVAHI_GCC_UNUSED void * userdata) {
switch (state) {
case AVAHI_CLIENT_S_REGISTERING:
if (group)
avahi_entry_group_reset(group);
break;

case AVAHI_CLIENT_S_RUNNING:
register_service(c);
break;

case AVAHI_CLIENT_FAILURE:
case AVAHI_CLIENT_S_COLLISION:
die("avahi client failure");

case AVAHI_CLIENT_CONNECTING:
break;
}
}

static int avahi_register(char *srvname, int srvport) {
debug(1, "avahi: avahi_register\n");
name = strdup(srvname);
port = srvport;

int err;
if (!(tpoll = avahi_threaded_poll_new())) {
warn("couldn't create avahi threaded tpoll!");
return -1;
}
if (!(client = avahi_client_new(avahi_threaded_poll_get(tpoll),
0,
client_callback,
NULL,
&err))) {
warn("couldn't create avahi client: %s!", avahi_strerror(err));
return -1;
}

if (avahi_threaded_poll_start(tpoll) < 0) {
warn("couldn't start avahi tpoll thread");
return -1;
}

return 0;
}

static void avahi_unregister(void) {
debug(1, "avahi: avahi_unregister\n");
if (tpoll)
avahi_threaded_poll_stop(tpoll);
tpoll = NULL;

if (name)
free(name);
name = NULL;
}

mdns_backend mdns_avahi =
{
.name = "avahi",
.mdns_register = avahi_register,
.mdns_unregister = avahi_unregister
};

@@ -0,0 +1,99 @@
/*
* Embedded dns-sd client. This file is part of Shairport.
* Copyright (c) Paul Lietar 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 <dns_sd.h>
#include <string.h>
#include "mdns.h"
#include "common.h"

static DNSServiceRef service;

static int mdns_dns_sd_register(char *apname, int port) {
const char *record[] = { MDNS_RECORD, NULL };
uint16_t length = 0;
const char **field;

// Concatenate string contained i record into buf.

for (field = record; *field; field ++)
{
length += strlen(*field) + 1; // One byte for length each time
}

char *buf = malloc(length * sizeof(char));
if (buf == NULL)
{
warn("dns_sd: buffer record allocation failed");
return -1;
}

char *p = buf;

for (field = record; *field; field ++)
{
char * newp = stpcpy(p + 1, *field);
*p = newp - p - 1;
p = newp;
}

DNSServiceErrorType error;
error = DNSServiceRegister(&service,
0,
kDNSServiceInterfaceIndexAny,
apname,
"_raop._tcp",
"",
NULL,
htons((uint16_t)port),
length,
buf,
NULL,
NULL);

free(buf);

if (error == kDNSServiceErr_NoError)
return 0;
else
{
warn("dns-sd: DNSServiceRegister error %d", error);
return -1;
}
}

static void mdns_dns_sd_unregister(void) {
if (service)
{
DNSServiceRefDeallocate(service);
service = NULL;
}
}

mdns_backend mdns_dns_sd = {
.name = "dns-sd",
.mdns_register = mdns_dns_sd_register,
.mdns_unregister = mdns_dns_sd_unregister
};
@@ -0,0 +1,154 @@
/*
* mDNS registration handler. This file is part of Shairport.
* Copyright (c) Paul Lietar 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 <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "common.h"
#include "mdns.h"

int mdns_pid = 0;

/*
* Do a fork followed by a execvp, handling execvp errors correctly.
* Return the pid of the new process upon success, or -1 if something failed.
* Check errno for error details.
*/
static int fork_execvp(const char *file, char *const argv[]) {
int execpipe[2];
int pid = 0;
if (pipe(execpipe) < 0) {
return -1;
}

if (fcntl(execpipe[1], F_SETFD, fcntl(execpipe[1], F_GETFD) | FD_CLOEXEC) < 0) {
close(execpipe[0]);
close(execpipe[1]);
return -1;
}

pid = fork();
if (pid < 0) {
close(execpipe[0]);
close(execpipe[1]);
return -1;
}
else if(pid == 0) { // Child
close(execpipe[0]); // Close the read end
execvp(file, argv);

// If we reach this point then execve has failed.
// Write erno's value into the pipe and exit.
write_unchecked(execpipe[1], &errno, sizeof(errno));

_exit(-1);
return 0; // Just to make the compiler happy.
}
else { // Parent
close(execpipe[1]); // Close the write end

int childErrno;
// Block until child closes the pipe or sends errno.
if(read(execpipe[0], &childErrno, sizeof(childErrno)) == sizeof(childErrno)) { // We received errno
errno = childErrno;
return -1;
}
else { // Child closed the pipe. execvp was successful.
return pid;
}
}
}

static int mdns_external_avahi_register(char *apname, int port) {
char mdns_port[6];
sprintf(mdns_port, "%d", config.port);

char *argv[] = {
NULL, apname, "_raop._tcp", mdns_port, MDNS_RECORD, NULL
};

argv[0] = "avahi-publish-service";
int pid = fork_execvp(argv[0], argv);
if (pid >= 0) {
mdns_pid = pid;
return 0;
}
else
debug(1, "Calling %s failed", argv[0]);

argv[0] = "mDNSPublish";
pid = fork_execvp(argv[0], argv);
if (pid >= 0)
{
mdns_pid = pid;
return 0;
}
else
debug(1, "Calling %s failed", argv[0]);

// If we reach here, both execvp calls failed.
return -1;
}

static int mdns_external_dns_sd_register(char *apname, int port) {
char mdns_port[6];
sprintf(mdns_port, "%d", config.port);

char *argv[] = {"dns-sd", "-R", apname, "_raop._tcp", ".",
mdns_port, MDNS_RECORD, NULL};

int pid = fork_execvp(argv[0], argv);
if (pid >= 0)
{
mdns_pid = pid;
return 0;
}
else
debug(1, "Calling %s failed", argv[0]);

return -1;
}

static void kill_mdns_child(void) {
if (mdns_pid)
kill(mdns_pid, SIGTERM);
mdns_pid = 0;
}

mdns_backend mdns_external_avahi = {
.name = "external-avahi",
.mdns_register = mdns_external_avahi_register,
.mdns_unregister = kill_mdns_child
};

mdns_backend mdns_external_dns_sd = {
.name = "external-dns-sd",
.mdns_register = mdns_external_dns_sd_register,
.mdns_unregister = kill_mdns_child
};

@@ -0,0 +1,152 @@
/*
* mDNS registration handler. This file is part of Shairport.
* Copyright (c) Paul Lietar 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 <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <ifaddrs.h>
#include <unistd.h>
#include <netinet/in.h>
#include "common.h"
#include "mdns.h"

#include "tinysvcmdns.h"

static struct mdnsd *svr = NULL;

static int mdns_tinysvcmdns_register(char *apname, int port) {
struct ifaddrs *ifalist;
struct ifaddrs *ifa;

svr = mdnsd_start();
if (svr == NULL) {
warn("tinysvcmdns: mdnsd_start() failed");
return -1;
}

// room for name + .local + NULL
char hostname[100 + 6];
gethostname(hostname, 99);
// according to POSIX, this may be truncated without a final NULL !
hostname[99] = 0;

// will not work if the hostname doesn't end in .local
char *hostend = hostname + strlen(hostname);
if ((strlen(hostname) > 6) &&
strcmp(hostend - 6, ".local"))
{
strcat(hostname, ".local");
}

if (getifaddrs(&ifalist) < 0)
{
warn("tinysvcmdns: getifaddrs() failed");
return -1;
}

ifa = ifalist;

// Look for an ipv4/ipv6 non-loopback interface to use as the main one.
for (ifa = ifalist; ifa != NULL; ifa = ifa->ifa_next)
{
if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr &&
ifa->ifa_addr->sa_family == AF_INET)
{
uint32_t main_ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;

mdnsd_set_hostname(svr, hostname, main_ip);
break;
}
else if (!(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr &&
ifa->ifa_addr->sa_family == AF_INET6)
{
struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;

mdnsd_set_hostname_v6(svr, hostname, addr);
break;
}
}

if (ifa == NULL)
{
warn("tinysvcmdns: no non-loopback ipv4 or ipv6 interface found");
return -1;
}


// Skip the first one, it was already added by set_hostname
for (ifa = ifa->ifa_next; ifa != NULL; ifa = ifa->ifa_next)
{
if (ifa->ifa_flags & IFF_LOOPBACK) // Skip loop-back interfaces
continue;

switch (ifa->ifa_addr->sa_family)
{
case AF_INET: { // ipv4
uint32_t ip = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
struct rr_entry *a_e = rr_create_a(create_nlabel(hostname), ip);
mdnsd_add_rr(svr, a_e);
}
break;
case AF_INET6: { // ipv6
struct in6_addr *addr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
struct rr_entry *aaaa_e = rr_create_aaaa(create_nlabel(hostname), addr);
mdnsd_add_rr(svr, aaaa_e);
}
break;
}
}

freeifaddrs(ifa);

const char *txt[] = { MDNS_RECORD, NULL };
struct mdns_service *svc = mdnsd_register_svc(svr,
apname,
"_raop._tcp.local",
port,
NULL,
txt);

mdns_service_destroy(svc);

return 0;
}

static void mdns_tinysvcmdns_unregister(void) {
if (svr)
{
mdnsd_stop(svr);
svr = NULL;
}
}

mdns_backend mdns_tinysvcmdns = {
.name = "tinysvcmdns",
.mdns_register = mdns_tinysvcmdns_register,
.mdns_unregister = mdns_tinysvcmdns_unregister
};

@@ -0,0 +1,165 @@
/*
* Metadate structure and utility methods. This file is part of Shairport.
* Copyright (c) Benjamin Maus 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 <memory.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <openssl/md5.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "common.h"
#include "metadata.h"

metadata player_meta;
static int fd = -1;
static int dirty = 0;

void metadata_set(char** field, const char* value) {
if (*field) {
if (!strcmp(*field, value))
return;
free(*field);
}
*field = strdup(value);
dirty = 1;
}

void metadata_open(void) {
if (!config.meta_dir)
return;

const char fn[] = "now_playing";
size_t pl = strlen(config.meta_dir) + 1 + strlen(fn);

char* path = malloc(pl+1);
snprintf(path, pl+1, "%s/%s", config.meta_dir, fn);

if (mkfifo(path, 0644) && errno != EEXIST)
die("Could not create metadata FIFO %s", path);

fd = open(path, O_WRONLY | O_NONBLOCK);
if (fd < 0)
debug(1, "Could not open metadata FIFO %s. Will try again later.", path);

free(path);
}

static void metadata_close(void) {
close(fd);
fd = -1;
}

static void print_one(const char *name, const char *value) {
write_unchecked(fd, name, strlen(name));
write_unchecked(fd, "=", 1);
if (value)
write_unchecked(fd, value, strlen(value));
write_unchecked(fd, "\n", 1);
}

#define write_one(name) \
print_one(#name, player_meta.name)

void metadata_write(void) {
int ret;

// readers may go away and come back
if (fd < 0)
metadata_open();
if (fd < 0)
return;

if (!dirty)
return;

dirty = 0;

write_one(artist);
write_one(title);
write_one(album);
write_one(artwork);
write_one(genre);
write_one(comment);

ret = write(fd, "\n", 1);
if (ret < 1) // no reader
metadata_close();
}

void metadata_cover_image(const char *buf, int len, const char *ext) {
if (!config.meta_dir)
return;

if (buf) {
debug(1, "Cover Art set\n");
} else {
debug(1, "Cover Art cleared\n");
return;
}

uint8_t img_md5[16];
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, buf, len);
MD5_Final(img_md5, &ctx);

char img_md5_str[33];
int i;
for (i = 0; i < 16; i++)
sprintf(&img_md5_str[i*2], "%02x", (uint8_t)img_md5[i]);

char *dir = config.meta_dir;
char *prefix = "cover-";

size_t pl = strlen(dir) + 1 + strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext);

char *path = malloc(pl+1);
snprintf(path, pl+1, "%s/%s%s.%s", dir, prefix, img_md5_str, ext);

int cover_fd = open(path, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);

if (cover_fd < 0) {
warn("Could not open file %s for writing cover art", path);
return;
}

if (write(cover_fd, buf, len) < len) {
warn("writing %s failed\n", path);
free(path);
return;
}
close(cover_fd);

debug(1, "Cover Art file is %s\n", path);
metadata_set(&player_meta.artwork, path+strlen(dir)+1);

free(path);
}
@@ -0,0 +1,22 @@
#ifndef _METADATA_H
#define _METADATA_H

#include <stdio.h>

typedef struct {
char *artist;
char *title;
char *album;
char *artwork;
char *comment;
char *genre;
} metadata;

void metadata_set(char** field, const char* value);
void metadata_open(void);
void metadata_write(void);
void metadata_cover_image(const char *buf, int len, const char *ext);

extern metadata player_meta;

#endif // _METADATA_H

Large diffs are not rendered by default.

@@ -0,0 +1,32 @@
#ifndef _PLAYER_H
#define _PLAYER_H

#include "audio.h"
#include "metadata.h"

typedef struct {
uint8_t aesiv[16], aeskey[16];
int32_t fmtp[12];
} stream_cfg;

typedef uint16_t seq_t;

// wrapped number between two seq_t.
static inline uint16_t seq_diff(seq_t a, seq_t b) {
int16_t diff = b - a;
return diff;
}

int player_play(stream_cfg *cfg);
void player_stop(void);

void player_volume(double f);
void player_metadata();
void player_cover_image(char *buf, int len, char *ext);
void player_cover_clear();
void player_flush(void);
void player_resync(void);

void player_put_packet(seq_t seqno, uint8_t *data, int len);

#endif //_PLAYER_H
@@ -0,0 +1,193 @@
/*
* Apple RTP protocol handler. 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 <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include "common.h"
#include "player.h"

// only one RTP session can be active at a time.
static int running = 0;
static int please_shutdown;

static SOCKADDR rtp_client;
static int sock;
static pthread_t rtp_thread;

static void *rtp_receiver(void *arg) {
// we inherit the signal mask (SIGUSR1)
uint8_t packet[2048], *pktp;

ssize_t nread;
while (1) {
if (please_shutdown)
break;
nread = recv(sock, packet, sizeof(packet), 0);
if (nread < 0)
break;

ssize_t plen = nread;
uint8_t type = packet[1] & ~0x80;
if (type == 0x54) // sync
continue;
if (type == 0x60 || type == 0x56) { // audio data / resend
pktp = packet;
if (type==0x56) {
pktp += 4;
plen -= 4;
}
seq_t seqno = ntohs(*(unsigned short *)(pktp+2));

pktp += 12;
plen -= 12;

// check if packet contains enough content to be reasonable
if (plen >= 16) {
player_put_packet(seqno, pktp, plen);
continue;
}
if (type == 0x56 && seqno == 0) {
debug(2, "resend-related request packet received, ignoring.\n");
continue;
}
debug(1, "Unknown RTP packet of type 0x%02X length %d seqno %d\n", type, nread, seqno);
continue;
}
warn("Unknown RTP packet of type 0x%02X length %d", type, nread);
}

debug(1, "RTP thread interrupted. terminating.\n");
close(sock);

return NULL;
}

static int bind_port(SOCKADDR *remote) {
struct addrinfo hints, *info;

memset(&hints, 0, sizeof(hints));
hints.ai_family = remote->SAFAMILY;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;

int ret = getaddrinfo(NULL, "0", &hints, &info);

if (ret < 0)
die("failed to get usable addrinfo?! %s", gai_strerror(ret));

sock = socket(remote->SAFAMILY, SOCK_DGRAM, IPPROTO_UDP);
ret = bind(sock, info->ai_addr, info->ai_addrlen);

freeaddrinfo(info);

if (ret < 0)
die("could not bind a UDP port!");

int sport;
SOCKADDR local;
socklen_t local_len = sizeof(local);
getsockname(sock, (struct sockaddr*)&local, &local_len);
#ifdef AF_INET6
if (local.SAFAMILY == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&local;
sport = htons(sa6->sin6_port);
} else
#endif
{
struct sockaddr_in *sa = (struct sockaddr_in*)&local;
sport = htons(sa->sin_port);
}

return sport;
}


int rtp_setup(SOCKADDR *remote, int cport, int tport) {
if (running)
die("rtp_setup called with active stream!");

debug(1, "rtp_setup: cport=%d tport=%d\n", cport, tport);

// we do our own timing and ignore the timing port.
// an audio perfectionist may wish to learn the protocol.

memcpy(&rtp_client, remote, sizeof(rtp_client));
#ifdef AF_INET6
if (rtp_client.SAFAMILY == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&rtp_client;
sa6->sin6_port = htons(cport);
} else
#endif
{
struct sockaddr_in *sa = (struct sockaddr_in*)&rtp_client;
sa->sin_port = htons(cport);
}

int sport = bind_port(remote);

debug(1, "rtp listening on port %d\n", sport);

please_shutdown = 0;
pthread_create(&rtp_thread, NULL, &rtp_receiver, NULL);

running = 1;
return sport;
}

void rtp_shutdown(void) {
if (!running)
die("rtp_shutdown called without active stream!");

debug(2, "shutting down RTP thread\n");
please_shutdown = 1;
pthread_kill(rtp_thread, SIGUSR1);
void *retval;
pthread_join(rtp_thread, &retval);
running = 0;
}

void rtp_request_resend(seq_t first, seq_t last) {
if (!running)
die("rtp_request_resend called without active stream!");

debug(1, "requesting resend on %d packets (%04X:%04X)\n",
seq_diff(first,last) + 1, first, last);

char req[8]; // *not* a standard RTCP NACK
req[0] = 0x80;
req[1] = 0x55|0x80; // Apple 'resend'
*(unsigned short *)(req+2) = htons(1); // our seqnum
*(unsigned short *)(req+4) = htons(first); // missed seqnum
*(unsigned short *)(req+6) = htons(last-first+1); // count

sendto(sock, req, sizeof(req), 0, (struct sockaddr*)&rtp_client, sizeof(rtp_client));
}
@@ -0,0 +1,10 @@
#ifndef _RTP_H
#define _RTP_H

#include <sys/socket.h>

int rtp_setup(SOCKADDR *remote, int controlport, int timingport);
void rtp_shutdown(void);
void rtp_request_resend(seq_t first, seq_t last);

#endif // _RTP_H