Change the Ubuntu libdns_sd package name to be correct
[deb_shairplay.git] / src / shairplay.c
CommitLineData
ad58f7a2
JVH
1#include <stdlib.h>
2#include <stdio.h>
3#include <math.h>
4#include <string.h>
5#include <unistd.h>
6#include <assert.h>
7
8#ifdef WIN32
2c4250f1 9# include <windows.h>
ad58f7a2
JVH
10#endif
11
12#include <shairplay/dnssd.h>
13#include <shairplay/raop.h>
14
15#include <ao/ao.h>
16
17#include "config.h"
18
19typedef struct {
20 char apname[56];
21 char password[56];
22 unsigned short port;
23
24 char ao_driver[56];
25 char ao_devicename[56];
26 char ao_deviceid[16];
27} shairplay_options_t;
28
29typedef struct {
30 ao_device *device;
31
32 int buffering;
33 int buflen;
34 char buffer[8192];
35
36 float volume;
37} shairplay_session_t;
38
39
258e9fb8
JVH
40static int running;
41
2c4250f1
JVH
42#ifndef WIN32
43
44#include <signal.h>
45static void
46signal_handler(int sig)
47{
48 switch (sig) {
49 case SIGINT:
50 case SIGTERM:
51 running = 0;
52 break;
53 }
54}
55static void
56init_signals(void)
57{
58 struct sigaction sigact;
59
60 sigact.sa_handler = signal_handler;
61 sigemptyset(&sigact.sa_mask);
62 sigact.sa_flags = 0;
63 sigaction(SIGINT, &sigact, NULL);
64 sigaction(SIGTERM, &sigact, NULL);
65}
66
67#endif
68
258e9fb8 69
ad58f7a2
JVH
70static ao_device *
71audio_open_device(shairplay_options_t *opt, int bits, int channels, int samplerate)
72{
73 ao_device *device = NULL;
74 ao_option *ao_options = NULL;
75 ao_sample_format format;
76 int driver_id;
77
78 /* Get the libao driver ID */
79 if (strlen(opt->ao_driver)) {
80 driver_id = ao_driver_id(opt->ao_driver);
81 } else {
82 driver_id = ao_default_driver_id();
83 }
84
85 /* Add all available libao options */
86 ao_append_option(&ao_options, "client_name", opt->apname);
87 if (strlen(opt->ao_devicename)) {
88 ao_append_option(&ao_options, "dev", opt->ao_devicename);
89 }
90 if (strlen(opt->ao_deviceid)) {
91 ao_append_option(&ao_options, "id", opt->ao_deviceid);
92 }
93
94 /* Set audio format */
95 memset(&format, 0, sizeof(format));
96 format.bits = bits;
97 format.channels = channels;
98 format.rate = samplerate;
99 format.byte_format = AO_FMT_NATIVE;
100
101 /* Try opening the actual device */
102 device = ao_open_live(driver_id, &format, ao_options);
103 ao_free_options(ao_options);
104 return device;
105}
106
107static void *
108audio_init(void *cls, int bits, int channels, int samplerate)
109{
110 shairplay_options_t *options = cls;
111 shairplay_session_t *session;
112
113 session = calloc(1, sizeof(shairplay_session_t));
114 assert(session);
115
116 session->device = audio_open_device(options, bits, channels, samplerate);
117 if (session->device == NULL) {
118 printf("Error opening device %d\n", errno);
119 }
120 assert(session->device);
121
122 session->buffering = 1;
123 session->volume = 1.0f;
124 return session;
125}
126
127static int
128audio_output(shairplay_session_t *session, const void *buffer, int buflen)
129{
130 short *shortbuf;
131 char tmpbuf[4096];
132 int tmpbuflen, i;
133
134 tmpbuflen = (buflen > sizeof(tmpbuf)) ? sizeof(tmpbuf) : buflen;
135 memcpy(tmpbuf, buffer, tmpbuflen);
136 if (ao_is_big_endian()) {
137 for (i=0; i<tmpbuflen/2; i++) {
138 char tmpch = tmpbuf[i*2];
139 tmpbuf[i*2] = tmpbuf[i*2+1];
140 tmpbuf[i*2+1] = tmpch;
141 }
142 }
143 shortbuf = (short *)tmpbuf;
144 for (i=0; i<tmpbuflen/2; i++) {
145 shortbuf[i] = shortbuf[i] * session->volume;
146 }
147 ao_play(session->device, tmpbuf, tmpbuflen);
148 return tmpbuflen;
149}
150
151static void
152audio_process(void *cls, void *opaque, const void *buffer, int buflen)
153{
154 shairplay_session_t *session = opaque;
155 int processed;
156
157 if (session->buffering) {
158 printf("Buffering...\n");
159 if (session->buflen+buflen < sizeof(session->buffer)) {
160 memcpy(session->buffer+session->buflen, buffer, buflen);
161 session->buflen += buflen;
162 return;
163 }
164 session->buffering = 0;
165 printf("Finished buffering...\n");
166
167 processed = 0;
168 while (processed < session->buflen) {
169 processed += audio_output(session,
170 session->buffer+processed,
171 session->buflen-processed);
172 }
173 session->buflen = 0;
174 }
175
176 processed = 0;
177 while (processed < buflen) {
178 processed += audio_output(session,
179 buffer+processed,
180 buflen-processed);
181 }
182}
183
184static void
185audio_destroy(void *cls, void *opaque)
186{
187 shairplay_session_t *session = opaque;
188
189 ao_close(session->device);
190 free(session);
191}
192
193static void
194audio_set_volume(void *cls, void *opaque, float volume)
195{
196 shairplay_session_t *session = opaque;
197 session->volume = pow(10.0, 0.05*volume);
198}
199
200static int
201parse_options(shairplay_options_t *opt, int argc, char *argv[])
202{
203 char *path = argv[0];
204 char *arg;
205
206 strcpy(opt->apname, "Shairplay");
207 opt->port = 5000;
208
209 while ((arg = *++argv)) {
210 if (!strcmp(arg, "-a")) {
211 strncpy(opt->apname, *++argv, sizeof(opt->apname)-1);
212 } else if (!strncmp(arg, "--apname=", 9)) {
213 strncpy(opt->apname, arg+9, sizeof(opt->apname)-1);
214 } else if (!strcmp(arg, "-p")) {
215 strncpy(opt->password, *++argv, sizeof(opt->password)-1);
216 } else if (!strncmp(arg, "--password=", 11)) {
217 strncpy(opt->password, arg+11, sizeof(opt->password)-1);
218 } else if (!strcmp(arg, "-o")) {
219 opt->port = atoi(*++argv);
220 } else if (!strncmp(arg, "--server_port=", 14)) {
221 opt->port = atoi(arg+14);
222 } else if (!strncmp(arg, "--ao_driver=", 12)) {
223 strncpy(opt->ao_driver, arg+12, sizeof(opt->ao_driver)-1);
224 } else if (!strncmp(arg, "--ao_devicename=", 16)) {
225 strncpy(opt->ao_devicename, arg+16, sizeof(opt->ao_devicename)-1);
226 } else if (!strncmp(arg, "--ao_deviceid=", 14)) {
227 strncpy(opt->ao_deviceid, arg+14, sizeof(opt->ao_deviceid)-1);
228 } else if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) {
229 fprintf(stderr, "Shairplay version %s\n", VERSION);
230 fprintf(stderr, "Usage: %s [OPTION...]\n", path);
231 fprintf(stderr, "\n");
232 fprintf(stderr, " -a, --apname=AirPort Sets Airport name\n");
233 fprintf(stderr, " -p, --password=secret Sets password\n");
234 fprintf(stderr, " -o, --server_port=5000 Sets port for RAOP service\n");
235 fprintf(stderr, " --ao_driver=driver Sets the ao driver (optional)\n");
236 fprintf(stderr, " --ao_devicename=devicename Sets the ao device name (optional)\n");
237 fprintf(stderr, " --ao_deviceid=id Sets the ao device id (optional)\n");
238 fprintf(stderr, " -h, --help This help\n");
239 fprintf(stderr, "\n");
240 return 1;
241 }
242 }
243
244 /* Set default values for apname and port */
245 if (!strlen(opt->apname)) {
246 strncpy(opt->apname, "Shairplay", sizeof(opt->apname)-1);
247 }
248 if (!opt->port) {
249 opt->port = 5000;
250 }
251 return 0;
252}
253
254int
255main(int argc, char *argv[])
256{
257 const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 };
258
259 shairplay_options_t options;
260 ao_device *device = NULL;
261
262 dnssd_t *dnssd;
263 raop_t *raop;
264 raop_callbacks_t raop_cbs;
265
61c2f5d5
JVH
266 int error;
267
2c4250f1
JVH
268#ifndef WIN32
269 init_signals();
270#endif
271
ad58f7a2
JVH
272 memset(&options, 0, sizeof(options));
273 if (parse_options(&options, argc, argv)) {
274 return 0;
275 }
276
277 ao_initialize();
278
279 device = audio_open_device(&options, 16, 2, 44100);
280 if (device == NULL) {
281 fprintf(stderr, "Error opening audio device %d\n", errno);
282 fprintf(stderr, "Please check your libao settings and try again\n");
283 return -1;
284 } else {
285 ao_close(device);
286 device = NULL;
287 }
288
289 memset(&raop_cbs, 0, sizeof(raop_cbs));
290 raop_cbs.cls = &options;
291 raop_cbs.audio_init = audio_init;
292 raop_cbs.audio_process = audio_process;
293 raop_cbs.audio_destroy = audio_destroy;
294 raop_cbs.audio_set_volume = audio_set_volume;
295
cf9b3c34 296 raop = raop_init_from_keyfile(10, &raop_cbs, "airport.key", NULL);
ad58f7a2
JVH
297 if (raop == NULL) {
298 fprintf(stderr, "Could not initialize the RAOP service\n");
299 return -1;
300 }
301
302 raop_set_log_level(raop, RAOP_LOG_DEBUG);
303 raop_start(raop, &options.port, hwaddr, sizeof(hwaddr), NULL);
304
61c2f5d5
JVH
305 error = 0;
306 dnssd = dnssd_init(&error);
307 if (error) {
308 fprintf(stderr, "ERROR: Could not initialize dnssd library!\n");
309 fprintf(stderr, "------------------------------------------\n");
310 fprintf(stderr, "You could try the following resolutions based on your OS:\n");
311 fprintf(stderr, "Windows: Try installing http://support.apple.com/kb/DL999\n");
f95c4d2d 312 fprintf(stderr, "Debian/Ubuntu: Try installing libavahi-compat-libdnssd-dev package\n");
61c2f5d5
JVH
313 raop_destroy(raop);
314 return -1;
315 }
316
ad58f7a2
JVH
317 dnssd_register_raop(dnssd, options.apname, options.port, hwaddr, sizeof(hwaddr), 0);
318
258e9fb8
JVH
319 running = 1;
320 while (running) {
ad58f7a2 321#ifndef WIN32
258e9fb8 322 sleep(1);
ad58f7a2 323#else
258e9fb8 324 Sleep(1000);
ad58f7a2 325#endif
258e9fb8 326 }
ad58f7a2
JVH
327
328 dnssd_unregister_raop(dnssd);
329 dnssd_destroy(dnssd);
330
331 raop_stop(raop);
332 raop_destroy(raop);
333
334 ao_shutdown();
335
336 return 0;
337}