2 * Copyright (C) 2012-2013 Juho Vähä-Herttua
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 #include <shairplay/dnssd.h>
36 #include <shairplay/raop.h>
48 char ao_devicename
[56];
50 } shairplay_options_t
;
60 } shairplay_session_t
;
69 signal_handler(int sig
)
81 struct sigaction sigact
;
83 sigact
.sa_handler
= signal_handler
;
84 sigemptyset(&sigact
.sa_mask
);
86 sigaction(SIGINT
, &sigact
, NULL
);
87 sigaction(SIGTERM
, &sigact
, NULL
);
94 audio_open_device(shairplay_options_t
*opt
, int bits
, int channels
, int samplerate
)
96 ao_device
*device
= NULL
;
97 ao_option
*ao_options
= NULL
;
98 ao_sample_format format
;
101 /* Get the libao driver ID */
102 if (strlen(opt
->ao_driver
)) {
103 driver_id
= ao_driver_id(opt
->ao_driver
);
105 driver_id
= ao_default_driver_id();
108 /* Add all available libao options */
109 if (strlen(opt
->ao_devicename
)) {
110 ao_append_option(&ao_options
, "dev", opt
->ao_devicename
);
112 if (strlen(opt
->ao_deviceid
)) {
113 ao_append_option(&ao_options
, "id", opt
->ao_deviceid
);
116 /* Set audio format */
117 memset(&format
, 0, sizeof(format
));
119 format
.channels
= channels
;
120 format
.rate
= samplerate
;
121 format
.byte_format
= AO_FMT_NATIVE
;
123 /* Try opening the actual device */
124 device
= ao_open_live(driver_id
, &format
, ao_options
);
125 ao_free_options(ao_options
);
130 audio_init(void *cls
, int bits
, int channels
, int samplerate
)
132 shairplay_options_t
*options
= cls
;
133 shairplay_session_t
*session
;
135 session
= calloc(1, sizeof(shairplay_session_t
));
138 session
->device
= audio_open_device(options
, bits
, channels
, samplerate
);
139 if (session
->device
== NULL
) {
140 printf("Error opening device %d\n", errno
);
142 assert(session
->device
);
144 session
->buffering
= 1;
145 session
->volume
= 1.0f
;
150 audio_output(shairplay_session_t
*session
, const void *buffer
, int buflen
)
156 tmpbuflen
= (buflen
> sizeof(tmpbuf
)) ? sizeof(tmpbuf
) : buflen
;
157 memcpy(tmpbuf
, buffer
, tmpbuflen
);
158 if (ao_is_big_endian()) {
159 for (i
=0; i
<tmpbuflen
/2; i
++) {
160 char tmpch
= tmpbuf
[i
*2];
161 tmpbuf
[i
*2] = tmpbuf
[i
*2+1];
162 tmpbuf
[i
*2+1] = tmpch
;
165 shortbuf
= (short *)tmpbuf
;
166 for (i
=0; i
<tmpbuflen
/2; i
++) {
167 shortbuf
[i
] = shortbuf
[i
] * session
->volume
;
169 ao_play(session
->device
, tmpbuf
, tmpbuflen
);
174 audio_process(void *cls
, void *opaque
, const void *buffer
, int buflen
)
176 shairplay_session_t
*session
= opaque
;
179 if (session
->buffering
) {
180 printf("Buffering...\n");
181 if (session
->buflen
+buflen
< sizeof(session
->buffer
)) {
182 memcpy(session
->buffer
+session
->buflen
, buffer
, buflen
);
183 session
->buflen
+= buflen
;
186 session
->buffering
= 0;
187 printf("Finished buffering...\n");
190 while (processed
< session
->buflen
) {
191 processed
+= audio_output(session
,
192 session
->buffer
+processed
,
193 session
->buflen
-processed
);
199 while (processed
< buflen
) {
200 processed
+= audio_output(session
,
207 audio_destroy(void *cls
, void *opaque
)
209 shairplay_session_t
*session
= opaque
;
211 ao_close(session
->device
);
216 audio_set_volume(void *cls
, void *opaque
, float volume
)
218 shairplay_session_t
*session
= opaque
;
219 session
->volume
= pow(10.0, 0.05*volume
);
223 parse_options(shairplay_options_t
*opt
, int argc
, char *argv
[])
225 char *path
= argv
[0];
228 /* Set default values for apname and port */
229 strncpy(opt
->apname
, "Shairplay", sizeof(opt
->apname
)-1);
232 while ((arg
= *++argv
)) {
233 if (!strcmp(arg
, "-a")) {
234 strncpy(opt
->apname
, *++argv
, sizeof(opt
->apname
)-1);
235 } else if (!strncmp(arg
, "--apname=", 9)) {
236 strncpy(opt
->apname
, arg
+9, sizeof(opt
->apname
)-1);
237 } else if (!strcmp(arg
, "-p")) {
238 strncpy(opt
->password
, *++argv
, sizeof(opt
->password
)-1);
239 } else if (!strncmp(arg
, "--password=", 11)) {
240 strncpy(opt
->password
, arg
+11, sizeof(opt
->password
)-1);
241 } else if (!strcmp(arg
, "-o")) {
242 opt
->port
= atoi(*++argv
);
243 } else if (!strncmp(arg
, "--server_port=", 14)) {
244 opt
->port
= atoi(arg
+14);
245 } else if (!strncmp(arg
, "--ao_driver=", 12)) {
246 strncpy(opt
->ao_driver
, arg
+12, sizeof(opt
->ao_driver
)-1);
247 } else if (!strncmp(arg
, "--ao_devicename=", 16)) {
248 strncpy(opt
->ao_devicename
, arg
+16, sizeof(opt
->ao_devicename
)-1);
249 } else if (!strncmp(arg
, "--ao_deviceid=", 14)) {
250 strncpy(opt
->ao_deviceid
, arg
+14, sizeof(opt
->ao_deviceid
)-1);
251 } else if (!strcmp(arg
, "-h") || !strcmp(arg
, "--help")) {
252 fprintf(stderr
, "Shairplay version %s\n", VERSION
);
253 fprintf(stderr
, "Usage: %s [OPTION...]\n", path
);
254 fprintf(stderr
, "\n");
255 fprintf(stderr
, " -a, --apname=AirPort Sets Airport name\n");
256 fprintf(stderr
, " -p, --password=secret Sets password\n");
257 fprintf(stderr
, " -o, --server_port=5000 Sets port for RAOP service\n");
258 fprintf(stderr
, " --ao_driver=driver Sets the ao driver (optional)\n");
259 fprintf(stderr
, " --ao_devicename=devicename Sets the ao device name (optional)\n");
260 fprintf(stderr
, " --ao_deviceid=id Sets the ao device id (optional)\n");
261 fprintf(stderr
, " -h, --help This help\n");
262 fprintf(stderr
, "\n");
271 main(int argc
, char *argv
[])
273 const char hwaddr
[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 };
275 shairplay_options_t options
;
276 ao_device
*device
= NULL
;
280 raop_callbacks_t raop_cbs
;
281 char *password
= NULL
;
289 memset(&options
, 0, sizeof(options
));
290 if (parse_options(&options
, argc
, argv
)) {
296 device
= audio_open_device(&options
, 16, 2, 44100);
297 if (device
== NULL
) {
298 fprintf(stderr
, "Error opening audio device %d\n", errno
);
299 fprintf(stderr
, "Please check your libao settings and try again\n");
306 memset(&raop_cbs
, 0, sizeof(raop_cbs
));
307 raop_cbs
.cls
= &options
;
308 raop_cbs
.audio_init
= audio_init
;
309 raop_cbs
.audio_process
= audio_process
;
310 raop_cbs
.audio_destroy
= audio_destroy
;
311 raop_cbs
.audio_set_volume
= audio_set_volume
;
313 raop
= raop_init_from_keyfile(10, &raop_cbs
, "airport.key", NULL
);
315 fprintf(stderr
, "Could not initialize the RAOP service\n");
319 if (strlen(options
.password
)) {
320 password
= options
.password
;
322 raop_set_log_level(raop
, RAOP_LOG_DEBUG
);
323 raop_start(raop
, &options
.port
, hwaddr
, sizeof(hwaddr
), password
);
326 dnssd
= dnssd_init(&error
);
328 fprintf(stderr
, "ERROR: Could not initialize dnssd library!\n");
329 fprintf(stderr
, "------------------------------------------\n");
330 fprintf(stderr
, "You could try the following resolutions based on your OS:\n");
331 fprintf(stderr
, "Windows: Try installing http://support.apple.com/kb/DL999\n");
332 fprintf(stderr
, "Debian/Ubuntu: Try installing libavahi-compat-libdnssd-dev package\n");
337 dnssd_register_raop(dnssd
, options
.apname
, options
.port
, hwaddr
, sizeof(hwaddr
), 0);
348 dnssd_unregister_raop(dnssd
);
349 dnssd_destroy(dnssd
);