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>
49 char ao_devicename
[56];
51 } shairplay_options_t
;
61 } shairplay_session_t
;
70 signal_handler(int sig
)
82 struct sigaction sigact
;
84 sigact
.sa_handler
= signal_handler
;
85 sigemptyset(&sigact
.sa_mask
);
87 sigaction(SIGINT
, &sigact
, NULL
);
88 sigaction(SIGTERM
, &sigact
, NULL
);
95 parse_hwaddr(const char *str
, char *hwaddr
, int hwaddrlen
)
100 if (strlen(str
) != slen
) {
103 for (i
=0; i
<slen
; i
++) {
104 if (str
[i
] == ':' && (i
%3 == 2)) {
107 if (str
[i
] >= '0' && str
[i
] <= '9') {
110 if (str
[i
] >= 'a' && str
[i
] <= 'f') {
115 for (i
=0; i
<hwaddrlen
; i
++) {
116 hwaddr
[i
] = (char) strtol(str
+(i
*3), NULL
, 16);
122 audio_open_device(shairplay_options_t
*opt
, int bits
, int channels
, int samplerate
)
124 ao_device
*device
= NULL
;
125 ao_option
*ao_options
= NULL
;
126 ao_sample_format format
;
129 /* Get the libao driver ID */
130 if (strlen(opt
->ao_driver
)) {
131 driver_id
= ao_driver_id(opt
->ao_driver
);
133 driver_id
= ao_default_driver_id();
136 /* Add all available libao options */
137 if (strlen(opt
->ao_devicename
)) {
138 ao_append_option(&ao_options
, "dev", opt
->ao_devicename
);
140 if (strlen(opt
->ao_deviceid
)) {
141 ao_append_option(&ao_options
, "id", opt
->ao_deviceid
);
144 /* Set audio format */
145 memset(&format
, 0, sizeof(format
));
147 format
.channels
= channels
;
148 format
.rate
= samplerate
;
149 format
.byte_format
= AO_FMT_NATIVE
;
151 /* Try opening the actual device */
152 device
= ao_open_live(driver_id
, &format
, ao_options
);
153 ao_free_options(ao_options
);
158 audio_init(void *cls
, int bits
, int channels
, int samplerate
)
160 shairplay_options_t
*options
= cls
;
161 shairplay_session_t
*session
;
163 session
= calloc(1, sizeof(shairplay_session_t
));
166 session
->device
= audio_open_device(options
, bits
, channels
, samplerate
);
167 if (session
->device
== NULL
) {
168 printf("Error opening device %d\n", errno
);
170 assert(session
->device
);
172 session
->buffering
= 1;
173 session
->volume
= 1.0f
;
178 audio_output(shairplay_session_t
*session
, const void *buffer
, int buflen
)
184 tmpbuflen
= (buflen
> sizeof(tmpbuf
)) ? sizeof(tmpbuf
) : buflen
;
185 memcpy(tmpbuf
, buffer
, tmpbuflen
);
186 if (ao_is_big_endian()) {
187 for (i
=0; i
<tmpbuflen
/2; i
++) {
188 char tmpch
= tmpbuf
[i
*2];
189 tmpbuf
[i
*2] = tmpbuf
[i
*2+1];
190 tmpbuf
[i
*2+1] = tmpch
;
193 shortbuf
= (short *)tmpbuf
;
194 for (i
=0; i
<tmpbuflen
/2; i
++) {
195 shortbuf
[i
] = shortbuf
[i
] * session
->volume
;
197 ao_play(session
->device
, tmpbuf
, tmpbuflen
);
202 audio_process(void *cls
, void *opaque
, const void *buffer
, int buflen
)
204 shairplay_session_t
*session
= opaque
;
207 if (session
->buffering
) {
208 printf("Buffering...\n");
209 if (session
->buflen
+buflen
< sizeof(session
->buffer
)) {
210 memcpy(session
->buffer
+session
->buflen
, buffer
, buflen
);
211 session
->buflen
+= buflen
;
214 session
->buffering
= 0;
215 printf("Finished buffering...\n");
218 while (processed
< session
->buflen
) {
219 processed
+= audio_output(session
,
220 session
->buffer
+processed
,
221 session
->buflen
-processed
);
227 while (processed
< buflen
) {
228 processed
+= audio_output(session
,
235 audio_destroy(void *cls
, void *opaque
)
237 shairplay_session_t
*session
= opaque
;
239 ao_close(session
->device
);
244 audio_set_volume(void *cls
, void *opaque
, float volume
)
246 shairplay_session_t
*session
= opaque
;
247 session
->volume
= pow(10.0, 0.05*volume
);
251 parse_options(shairplay_options_t
*opt
, int argc
, char *argv
[])
253 const char default_hwaddr
[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 };
255 char *path
= argv
[0];
258 /* Set default values for apname and port */
259 strncpy(opt
->apname
, "Shairplay", sizeof(opt
->apname
)-1);
261 memcpy(opt
->hwaddr
, default_hwaddr
, sizeof(opt
->hwaddr
));
263 while ((arg
= *++argv
)) {
264 if (!strcmp(arg
, "-a")) {
265 strncpy(opt
->apname
, *++argv
, sizeof(opt
->apname
)-1);
266 } else if (!strncmp(arg
, "--apname=", 9)) {
267 strncpy(opt
->apname
, arg
+9, sizeof(opt
->apname
)-1);
268 } else if (!strcmp(arg
, "-p")) {
269 strncpy(opt
->password
, *++argv
, sizeof(opt
->password
)-1);
270 } else if (!strncmp(arg
, "--password=", 11)) {
271 strncpy(opt
->password
, arg
+11, sizeof(opt
->password
)-1);
272 } else if (!strcmp(arg
, "-o")) {
273 opt
->port
= atoi(*++argv
);
274 } else if (!strncmp(arg
, "--server_port=", 14)) {
275 opt
->port
= atoi(arg
+14);
276 } else if (!strncmp(arg
, "--hwaddr=", 9)) {
277 if (parse_hwaddr(arg
+9, opt
->hwaddr
, sizeof(opt
->hwaddr
))) {
278 fprintf(stderr
, "Invalid format given for hwaddr, aborting...\n");
279 fprintf(stderr
, "Please use hwaddr format: 01:45:89:ab:cd:ef\n");
282 } else if (!strncmp(arg
, "--ao_driver=", 12)) {
283 strncpy(opt
->ao_driver
, arg
+12, sizeof(opt
->ao_driver
)-1);
284 } else if (!strncmp(arg
, "--ao_devicename=", 16)) {
285 strncpy(opt
->ao_devicename
, arg
+16, sizeof(opt
->ao_devicename
)-1);
286 } else if (!strncmp(arg
, "--ao_deviceid=", 14)) {
287 strncpy(opt
->ao_deviceid
, arg
+14, sizeof(opt
->ao_deviceid
)-1);
288 } else if (!strcmp(arg
, "-h") || !strcmp(arg
, "--help")) {
289 fprintf(stderr
, "Shairplay version %s\n", VERSION
);
290 fprintf(stderr
, "Usage: %s [OPTION...]\n", path
);
291 fprintf(stderr
, "\n");
292 fprintf(stderr
, " -a, --apname=AirPort Sets Airport name\n");
293 fprintf(stderr
, " -p, --password=secret Sets password\n");
294 fprintf(stderr
, " -o, --server_port=5000 Sets port for RAOP service\n");
295 fprintf(stderr
, " --hwaddr=address Sets the MAC address, useful if running multiple instances\n");
296 fprintf(stderr
, " --ao_driver=driver Sets the ao driver (optional)\n");
297 fprintf(stderr
, " --ao_devicename=devicename Sets the ao device name (optional)\n");
298 fprintf(stderr
, " --ao_deviceid=id Sets the ao device id (optional)\n");
299 fprintf(stderr
, " -h, --help This help\n");
300 fprintf(stderr
, "\n");
309 main(int argc
, char *argv
[])
311 shairplay_options_t options
;
312 ao_device
*device
= NULL
;
316 raop_callbacks_t raop_cbs
;
317 char *password
= NULL
;
325 memset(&options
, 0, sizeof(options
));
326 if (parse_options(&options
, argc
, argv
)) {
332 device
= audio_open_device(&options
, 16, 2, 44100);
333 if (device
== NULL
) {
334 fprintf(stderr
, "Error opening audio device %d\n", errno
);
335 fprintf(stderr
, "Please check your libao settings and try again\n");
342 memset(&raop_cbs
, 0, sizeof(raop_cbs
));
343 raop_cbs
.cls
= &options
;
344 raop_cbs
.audio_init
= audio_init
;
345 raop_cbs
.audio_process
= audio_process
;
346 raop_cbs
.audio_destroy
= audio_destroy
;
347 raop_cbs
.audio_set_volume
= audio_set_volume
;
349 raop
= raop_init_from_keyfile(10, &raop_cbs
, "airport.key", NULL
);
351 fprintf(stderr
, "Could not initialize the RAOP service\n");
355 if (strlen(options
.password
)) {
356 password
= options
.password
;
358 raop_set_log_level(raop
, RAOP_LOG_DEBUG
);
359 raop_start(raop
, &options
.port
, options
.hwaddr
, sizeof(options
.hwaddr
), password
);
362 dnssd
= dnssd_init(&error
);
364 fprintf(stderr
, "ERROR: Could not initialize dnssd library!\n");
365 fprintf(stderr
, "------------------------------------------\n");
366 fprintf(stderr
, "You could try the following resolutions based on your OS:\n");
367 fprintf(stderr
, "Windows: Try installing http://support.apple.com/kb/DL999\n");
368 fprintf(stderr
, "Debian/Ubuntu: Try installing libavahi-compat-libdnssd-dev package\n");
373 dnssd_register_raop(dnssd
, options
.apname
, options
.port
, options
.hwaddr
, sizeof(options
.hwaddr
), 0);
384 dnssd_unregister_raop(dnssd
);
385 dnssd_destroy(dnssd
);