| @@ -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 |
| @@ -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 |