chiark / gitweb /
drop 0s at end of rtp packets
[disorder] / clients / playrtp.c
CommitLineData
e83d0967
RK
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2007 Richard Kettlewell
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 * USA
19 */
20
21#include <config.h>
22#include "types.h"
23
24#include <getopt.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <sys/socket.h>
28#include <sys/types.h>
29#include <sys/socket.h>
30#include <netdb.h>
31#include <pthread.h>
0b75463f 32#include <locale.h>
e83d0967
RK
33
34#include "log.h"
35#include "mem.h"
36#include "configuration.h"
37#include "addr.h"
38#include "syscalls.h"
39#include "rtp.h"
0b75463f 40#include "defs.h"
e83d0967
RK
41
42#if HAVE_COREAUDIO_AUDIOHARDWARE_H
43# include <CoreAudio/AudioHardware.h>
44#endif
0b75463f 45#if API_ALSA
46#include <alsa/asoundlib.h>
47#endif
e83d0967 48
1153fd23 49#define readahead linux_headers_are_borked
50
0b75463f 51/** @brief RTP socket */
e83d0967
RK
52static int rtpfd;
53
345ebe66
RK
54/** @brief Log output */
55static FILE *logfp;
56
0b75463f 57/** @brief Output device */
58static const char *device;
59
60/** @brief Maximum samples per packet we'll support
61 *
62 * NB that two channels = two samples in this program.
63 */
64#define MAXSAMPLES 2048
65
9086a105 66/** @brief Minimum low watermark
0b75463f 67 *
68 * We'll stop playing if there's only this many samples in the buffer. */
1153fd23 69static unsigned minbuffer = 2 * 44100 / 10; /* 0.2 seconds */
0b75463f 70
71/** @brief Maximum sample size
72 *
73 * The maximum supported size (in bytes) of one sample. */
74#define MAXSAMPLESIZE 2
75
9086a105 76/** @brief Buffer high watermark
1153fd23 77 *
78 * We'll only start playing when this many samples are available. */
8d0c14d7 79static unsigned readahead = 2 * 2 * 44100;
0b75463f 80
9086a105
RK
81/** @brief Maximum buffer size
82 *
83 * We'll stop reading from the network if we have this many samples. */
84static unsigned maxbuffer;
85
c0e41690 86/** @brief Number of samples to infill by in one go */
87#define INFILL_SAMPLES (44100 * 2) /* 1s */
88
0b75463f 89/** @brief Received packet
90 *
91 * Packets are recorded in an ordered linked list. */
92struct packet {
93 /** @brief Pointer to next packet
94 * The next packet might not be immediately next: if packets are dropped
95 * or mis-ordered there may be gaps at any given moment. */
96 struct packet *next;
97 /** @brief Number of samples in this packet */
c0e41690 98 uint32_t nsamples;
0b75463f 99 /** @brief Timestamp from RTP packet
100 *
101 * NB that "timestamps" are really sample counters.*/
102 uint32_t timestamp;
e83d0967 103#if HAVE_COREAUDIO_AUDIOHARDWARE_H
0b75463f 104 /** @brief Converted sample data */
105 float samples_float[MAXSAMPLES];
106#else
107 /** @brief Raw sample data */
108 unsigned char samples_raw[MAXSAMPLES * MAXSAMPLESIZE];
e83d0967
RK
109#endif
110};
111
0b75463f 112/** @brief Total number of samples available */
113static unsigned long nsamples;
114
115/** @brief Linked list of packets
116 *
9086a105
RK
117 * In ascending order of timestamp. Really this should be a heap for more
118 * efficient access. */
0b75463f 119static struct packet *packets;
120
121/** @brief Timestamp of next packet to play.
122 *
123 * This is set to the timestamp of the last packet, plus the number of
09ee2f0d 124 * samples it contained. Only valid if @ref active is nonzero.
0b75463f 125 */
126static uint32_t next_timestamp;
e83d0967 127
09ee2f0d 128/** @brief True if actively playing
129 *
130 * This is true when playing and false when just buffering. */
131static int active;
132
0b75463f 133/** @brief Lock protecting @ref packets */
e83d0967 134static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
e83d0967 135
0b75463f 136/** @brief Condition variable signalled whenever @ref packets is changed */
137static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
e83d0967
RK
138
139static const struct option options[] = {
140 { "help", no_argument, 0, 'h' },
141 { "version", no_argument, 0, 'V' },
142 { "debug", no_argument, 0, 'd' },
0b75463f 143 { "device", required_argument, 0, 'D' },
1153fd23 144 { "min", required_argument, 0, 'm' },
9086a105 145 { "max", required_argument, 0, 'x' },
1153fd23 146 { "buffer", required_argument, 0, 'b' },
e83d0967
RK
147 { 0, 0, 0, 0 }
148};
149
0b75463f 150/** @brief Return true iff a < b in sequence-space arithmetic */
09ee2f0d 151static inline int lt(uint32_t a, uint32_t b) {
152 return (uint32_t)(a - b) & 0x80000000;
e83d0967
RK
153}
154
c0e41690 155/** @brief Return true iff a >= b in sequence-space arithmetic */
156static inline int ge(uint32_t a, uint32_t b) {
157 return !lt(a, b);
158}
159
160/** @brief Return true iff a > b in sequence-space arithmetic */
161static inline int gt(uint32_t a, uint32_t b) {
162 return lt(b, a);
163}
164
165/** @brief Return true iff a <= b in sequence-space arithmetic */
166static inline int le(uint32_t a, uint32_t b) {
167 return !lt(b, a);
168}
169
9086a105
RK
170/** @brief Drop the packet at the head of the queue */
171static void drop_first_packet(void) {
172 struct packet *const p = packets;
173 packets = p->next;
174 nsamples -= p->nsamples;
175 free(p);
176 pthread_cond_broadcast(&cond);
177}
178
09ee2f0d 179/** @brief Background thread collecting samples
0b75463f 180 *
181 * This function collects samples, perhaps converts them to the target format,
182 * and adds them to the packet list. */
183static void *listen_thread(void attribute((unused)) *arg) {
09ee2f0d 184 struct packet *p = 0, **pp;
0b75463f 185 int n;
e83d0967
RK
186 union {
187 struct rtp_header header;
188 uint8_t bytes[sizeof(uint16_t) * MAXSAMPLES + sizeof (struct rtp_header)];
189 } packet;
190 const uint16_t *const samples = (uint16_t *)(packet.bytes
191 + sizeof (struct rtp_header));
192
193 for(;;) {
09ee2f0d 194 if(!p)
195 p = xmalloc(sizeof *p);
e83d0967
RK
196 n = read(rtpfd, packet.bytes, sizeof packet.bytes);
197 if(n < 0) {
198 switch(errno) {
199 case EINTR:
200 continue;
201 default:
202 fatal(errno, "error reading from socket");
203 }
204 }
0b75463f 205 /* Ignore too-short packets */
345ebe66
RK
206 if((size_t)n <= sizeof (struct rtp_header)) {
207 info("ignored a short packet");
0b75463f 208 continue;
345ebe66 209 }
09ee2f0d 210 p->timestamp = ntohl(packet.header.timestamp);
211 /* Ignore packets in the past */
c0e41690 212 if(active && lt(p->timestamp, next_timestamp)) {
213 info("dropping old packet, timestamp=%"PRIx32" < %"PRIx32,
214 p->timestamp, next_timestamp);
09ee2f0d 215 continue;
c0e41690 216 }
e83d0967 217 /* Convert to target format */
0b75463f 218 switch(packet.header.mpt & 0x7F) {
e83d0967 219 case 10:
09ee2f0d 220 p->nsamples = (n - sizeof (struct rtp_header)) / sizeof(uint16_t);
0b75463f 221#if HAVE_COREAUDIO_AUDIOHARDWARE_H
222 /* Convert to what Core Audio expects */
9086a105
RK
223 {
224 size_t i;
225
8dcb5ff0 226 for(i = 0; i < p->nsamples; ++i)
9086a105
RK
227 p->samples_float[i] = (int16_t)ntohs(samples[i]) * (0.5f / 32767);
228 }
0b75463f 229#else
230 /* ALSA can do any necessary conversion itself (though it might be better
231 * to do any necessary conversion in the background) */
09ee2f0d 232 memcpy(p->samples_raw, samples, n - sizeof (struct rtp_header));
0b75463f 233#endif
e83d0967
RK
234 break;
235 /* TODO support other RFC3551 media types (when the speaker does) */
236 default:
0b75463f 237 fatal(0, "unsupported RTP payload type %d",
e83d0967
RK
238 packet.header.mpt & 0x7F);
239 }
345ebe66
RK
240 if(logfp)
241 fprintf(logfp, "sequence %u timestamp %"PRIx32" length %"PRIx32" end %"PRIx32"\n",
242 ntohs(packet.header.seq),
243 p->timestamp, p->nsamples, p->timestamp + p->nsamples);
e83d0967 244 pthread_mutex_lock(&lock);
0b75463f 245 /* Stop reading if we've reached the maximum.
246 *
247 * This is rather unsatisfactory: it means that if packets get heavily
248 * out of order then we guarantee dropouts. But for now... */
345ebe66
RK
249 if(nsamples >= maxbuffer) {
250 info("buffer full");
251 while(nsamples >= maxbuffer)
252 pthread_cond_wait(&cond, &lock);
253 }
09ee2f0d 254 for(pp = &packets;
255 *pp && lt((*pp)->timestamp, p->timestamp);
256 pp = &(*pp)->next)
e83d0967 257 ;
09ee2f0d 258 /* So now either !*pp or *pp >= p */
259 if(*pp && p->timestamp == (*pp)->timestamp) {
260 /* *pp == p; a duplicate. Ideally we avoid the translation step here,
0b75463f 261 * but we'll worry about that another time. */
9ae1516d 262 info("dropped a duplicated");
0b75463f 263 } else {
9ae1516d 264 if(*pp)
265 info("receiving packets out of order");
09ee2f0d 266 p->next = *pp;
267 *pp = p;
268 nsamples += p->nsamples;
0b75463f 269 pthread_cond_broadcast(&cond);
09ee2f0d 270 p = 0; /* we've consumed this packet */
0b75463f 271 }
e83d0967 272 pthread_mutex_unlock(&lock);
e83d0967
RK
273 }
274}
275
276#if HAVE_COREAUDIO_AUDIOHARDWARE_H
09ee2f0d 277/** @brief Callback from Core Audio */
9086a105
RK
278static OSStatus adioproc
279 (AudioDeviceID attribute((unused)) inDevice,
280 const AudioTimeStamp attribute((unused)) *inNow,
281 const AudioBufferList attribute((unused)) *inInputData,
282 const AudioTimeStamp attribute((unused)) *inInputTime,
283 AudioBufferList *outOutputData,
284 const AudioTimeStamp attribute((unused)) *inOutputTime,
285 void attribute((unused)) *inClientData) {
e83d0967
RK
286 UInt32 nbuffers = outOutputData->mNumberBuffers;
287 AudioBuffer *ab = outOutputData->mBuffers;
e83d0967 288
0b75463f 289 pthread_mutex_lock(&lock);
9086a105
RK
290 while(nbuffers > 0) {
291 float *samplesOut = ab->mData;
292 size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
293
294 while(samplesOutLeft > 0) {
295 if(packets) {
296 /* There's a packet */
297 const uint32_t packet_start = packets->timestamp;
298 const uint32_t packet_end = packets->timestamp + packets->nsamples;
299
300 if(le(packet_end, next_timestamp)) {
301 /* This packet is in the past */
302 info("dropping buffered past packet %"PRIx32" < %"PRIx32,
8dcb5ff0
RK
303 packet_start, next_timestamp);
304 drop_first_packet();
9086a105
RK
305 continue;
306 }
307 if(ge(next_timestamp, packet_start)
308 && lt(next_timestamp, packet_end)) {
309 /* This packet is suitable */
8dcb5ff0 310 const uint32_t offset = next_timestamp - packet_start;
9086a105
RK
311 uint32_t samples_available = packet_end - next_timestamp;
312 if(samples_available > samplesOutLeft)
313 samples_available = samplesOutLeft;
314 memcpy(samplesOut,
315 packets->samples_float + offset,
316 samples_available * sizeof(float));
317 samplesOut += samples_available;
318 next_timestamp += samples_available;
8dcb5ff0 319 samplesOutLeft -= samples_available;
9086a105
RK
320 if(ge(next_timestamp, packet_end))
321 drop_first_packet();
322 continue;
323 }
324 }
325 /* We didn't find a suitable packet (though there might still be
326 * unsuitable ones). We infill with 0s. */
327 if(packets) {
328 /* There is a next packet, only infill up to that point */
329 uint32_t samples_available = packets->timestamp - next_timestamp;
330
331 if(samples_available > samplesOutLeft)
332 samples_available = samplesOutLeft;
8dcb5ff0 333 info("infill by %"PRIu32, samples_available);
9086a105
RK
334 /* Convniently the buffer is 0 to start with */
335 next_timestamp += samples_available;
336 samplesOut += samples_available;
337 samplesOutLeft -= samples_available;
9086a105
RK
338 } else {
339 /* There's no next packet at all */
8dcb5ff0 340 info("infilled by %zu", samplesOutLeft);
9086a105
RK
341 next_timestamp += samplesOutLeft;
342 samplesOut += samplesOutLeft;
343 samplesOutLeft = 0;
9086a105 344 }
e83d0967 345 }
9086a105
RK
346 ++ab;
347 --nbuffers;
e83d0967
RK
348 }
349 pthread_mutex_unlock(&lock);
350 return 0;
351}
352#endif
353
09ee2f0d 354/** @brief Play an RTP stream
355 *
356 * This is the guts of the program. It is responsible for:
357 * - starting the listening thread
358 * - opening the audio device
359 * - reading ahead to build up a buffer
360 * - arranging for audio to be played
361 * - detecting when the buffer has got too small and re-buffering
362 */
0b75463f 363static void play_rtp(void) {
364 pthread_t ltid;
e83d0967
RK
365
366 /* We receive and convert audio data in a background thread */
0b75463f 367 pthread_create(&ltid, 0, listen_thread, 0);
e83d0967 368#if API_ALSA
0b75463f 369 {
370 snd_pcm_t *pcm;
371 snd_pcm_hw_params_t *hwparams;
372 snd_pcm_sw_params_t *swparams;
373 /* Only support one format for now */
374 const int sample_format = SND_PCM_FORMAT_S16_BE;
375 unsigned rate = 44100;
376 const int channels = 2;
377 const int samplesize = channels * sizeof(uint16_t);
378 snd_pcm_uframes_t pcm_bufsize = MAXSAMPLES * samplesize * 3;
379 /* If we can write more than this many samples we'll get a wakeup */
380 const int avail_min = 256;
381 snd_pcm_sframes_t frames_written;
382 size_t samples_written;
383 int prepared = 1;
384 int err;
c0e41690 385 int infilling = 0, escape = 0;
386 time_t logged, now;
387 uint32_t packet_start, packet_end;
0b75463f 388
389 /* Open ALSA */
390 if((err = snd_pcm_open(&pcm,
391 device ? device : "default",
392 SND_PCM_STREAM_PLAYBACK,
393 SND_PCM_NONBLOCK)))
394 fatal(0, "error from snd_pcm_open: %d", err);
395 /* Set up 'hardware' parameters */
396 snd_pcm_hw_params_alloca(&hwparams);
397 if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
398 fatal(0, "error from snd_pcm_hw_params_any: %d", err);
399 if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
400 SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
401 fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
402 if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
403 sample_format)) < 0)
404 fatal(0, "error from snd_pcm_hw_params_set_format (%d): %d",
405 sample_format, err);
406 if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0)
407 fatal(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
408 rate, err);
409 if((err = snd_pcm_hw_params_set_channels(pcm, hwparams,
410 channels)) < 0)
411 fatal(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
412 channels, err);
413 if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams,
414 &pcm_bufsize)) < 0)
415 fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%d): %d",
416 MAXSAMPLES * samplesize * 3, err);
417 if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
418 fatal(0, "error calling snd_pcm_hw_params: %d", err);
419 /* Set up 'software' parameters */
420 snd_pcm_sw_params_alloca(&swparams);
421 if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0)
422 fatal(0, "error calling snd_pcm_sw_params_current: %d", err);
423 if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0)
424 fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d",
425 avail_min, err);
426 if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
427 fatal(0, "error calling snd_pcm_sw_params: %d", err);
428
429 /* Ready to go */
430
c0e41690 431 time(&logged);
0b75463f 432 pthread_mutex_lock(&lock);
433 for(;;) {
434 /* Wait for the buffer to fill up a bit */
8d0c14d7 435 logged = now;
436 info("%lu samples in buffer (%lus)", nsamples,
437 nsamples / (44100 * 2));
ed13cbc8 438 info("Buffering...");
1153fd23 439 while(nsamples < readahead)
0b75463f 440 pthread_cond_wait(&cond, &lock);
441 if(!prepared) {
442 if((err = snd_pcm_prepare(pcm)))
443 fatal(0, "error calling snd_pcm_prepare: %d", err);
444 prepared = 1;
445 }
09ee2f0d 446 /* Start at the first available packet */
447 next_timestamp = packets->timestamp;
448 active = 1;
ed13cbc8 449 infilling = 0;
c0e41690 450 escape = 0;
8d0c14d7 451 logged = now;
452 info("%lu samples in buffer (%lus)", nsamples,
453 nsamples / (44100 * 2));
ed13cbc8 454 info("Playing...");
0b75463f 455 /* Wait until the buffer empties out */
c0e41690 456 while(nsamples >= minbuffer && !escape) {
457 time(&now);
458 if(now > logged + 10) {
459 logged = now;
460 info("%lu samples in buffer (%lus)", nsamples,
461 nsamples / (44100 * 2));
462 }
463 if(packets
464 && ge(next_timestamp, packets->timestamp + packets->nsamples)) {
c0e41690 465 info("dropping buffered past packet %"PRIx32" < %"PRIx32,
466 packets->timestamp, next_timestamp);
9086a105 467 drop_first_packet();
c0e41690 468 continue;
469 }
0b75463f 470 /* Wait for ALSA to ask us for more data */
471 pthread_mutex_unlock(&lock);
9ae1516d 472 write(2, ".", 1); /* TODO remove me sometime */
473 switch(err = snd_pcm_wait(pcm, -1)) {
474 case 0:
475 info("snd_pcm_wait timed out");
476 break;
477 case 1:
478 break;
479 default:
480 fatal(0, "snd_pcm_wait returned %d", err);
481 }
0b75463f 482 pthread_mutex_lock(&lock);
09ee2f0d 483 /* ALSA is ready for more data */
c0e41690 484 packet_start = packets->timestamp;
485 packet_end = packets->timestamp + packets->nsamples;
486 if(ge(next_timestamp, packet_start)
487 && lt(next_timestamp, packet_end)) {
488 /* The target timestamp is somewhere in this packet */
489 const uint32_t offset = next_timestamp - packets->timestamp;
490 const uint32_t samples_available = (packets->timestamp + packets->nsamples) - next_timestamp;
0b75463f 491 const size_t frames_available = samples_available / 2;
492
493 frames_written = snd_pcm_writei(pcm,
c0e41690 494 packets->samples_raw + offset,
0b75463f 495 frames_available);
1153fd23 496 if(frames_written < 0) {
c0e41690 497 switch(frames_written) {
498 case -EAGAIN:
499 info("snd_pcm_wait() returned but we got -EAGAIN!");
500 break;
501 case -EPIPE:
502 error(0, "error calling snd_pcm_writei: %ld",
503 (long)frames_written);
504 escape = 1;
505 break;
506 default:
1153fd23 507 fatal(0, "error calling snd_pcm_writei: %ld",
508 (long)frames_written);
c0e41690 509 }
1153fd23 510 } else {
511 samples_written = frames_written * 2;
1153fd23 512 next_timestamp += samples_written;
9086a105
RK
513 if(ge(next_timestamp, packet_end))
514 drop_first_packet();
1153fd23 515 infilling = 0;
0b75463f 516 }
517 } else {
518 /* We don't have anything to play! We'd better play some 0s. */
c0e41690 519 static const uint16_t zeros[INFILL_SAMPLES];
520 size_t samples_available = INFILL_SAMPLES, frames_available;
ed13cbc8 521
c0e41690 522 /* If the maximum infill would take us past the start of the next
523 * packet then we truncate the infill to the right amount. */
524 if(lt(packets->timestamp,
525 next_timestamp + samples_available))
0b75463f 526 samples_available = packets->timestamp - next_timestamp;
c0e41690 527 if((int)samples_available < 0) {
528 info("packets->timestamp: %"PRIx32" next_timestamp: %"PRIx32" next+max: %"PRIx32" available: %"PRIx32,
529 packets->timestamp, next_timestamp,
530 next_timestamp + INFILL_SAMPLES, samples_available);
531 }
0b75463f 532 frames_available = samples_available / 2;
c0e41690 533 if(!infilling) {
8d0c14d7 534 info("Infilling %d samples, next=%"PRIx32" packet=[%"PRIx32",%"PRIx32"]",
535 samples_available, next_timestamp,
536 packets->timestamp, packets->timestamp + packets->nsamples);
c0e41690 537 //infilling++;
538 }
0b75463f 539 frames_written = snd_pcm_writei(pcm,
540 zeros,
541 frames_available);
1153fd23 542 if(frames_written < 0) {
c0e41690 543 switch(frames_written) {
544 case -EAGAIN:
545 info("snd_pcm_wait() returned but we got -EAGAIN!");
546 break;
547 case -EPIPE:
548 error(0, "error calling snd_pcm_writei: %ld",
549 (long)frames_written);
550 escape = 1;
551 break;
552 default:
1153fd23 553 fatal(0, "error calling snd_pcm_writei: %ld",
554 (long)frames_written);
c0e41690 555 }
74a94bd0 556 } else {
557 samples_written = frames_written * 2;
1153fd23 558 next_timestamp += samples_written;
74a94bd0 559 }
0b75463f 560 }
561 }
09ee2f0d 562 active = 0;
0b75463f 563 /* We stop playing for a bit until the buffer re-fills */
564 pthread_mutex_unlock(&lock);
ed13cbc8 565 if((err = snd_pcm_nonblock(pcm, 0)))
566 fatal(0, "error calling snd_pcm_nonblock: %d", err);
c0e41690 567 if(escape) {
568 if((err = snd_pcm_drop(pcm)))
569 fatal(0, "error calling snd_pcm_drop: %d", err);
570 escape = 0;
571 } else
572 if((err = snd_pcm_drain(pcm)))
573 fatal(0, "error calling snd_pcm_drain: %d", err);
ed13cbc8 574 if((err = snd_pcm_nonblock(pcm, 1)))
575 fatal(0, "error calling snd_pcm_nonblock: %d", err);
0b75463f 576 prepared = 0;
577 pthread_mutex_lock(&lock);
578 }
579
580 }
e83d0967
RK
581#elif HAVE_COREAUDIO_AUDIOHARDWARE_H
582 {
583 OSStatus status;
584 UInt32 propertySize;
585 AudioDeviceID adid;
586 AudioStreamBasicDescription asbd;
587
588 /* If this looks suspiciously like libao's macosx driver there's an
589 * excellent reason for that... */
590
591 /* TODO report errors as strings not numbers */
592 propertySize = sizeof adid;
593 status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
594 &propertySize, &adid);
595 if(status)
596 fatal(0, "AudioHardwareGetProperty: %d", (int)status);
597 if(adid == kAudioDeviceUnknown)
598 fatal(0, "no output device");
599 propertySize = sizeof asbd;
600 status = AudioDeviceGetProperty(adid, 0, false,
601 kAudioDevicePropertyStreamFormat,
602 &propertySize, &asbd);
603 if(status)
604 fatal(0, "AudioHardwareGetProperty: %d", (int)status);
605 D(("mSampleRate %f", asbd.mSampleRate));
9086a105
RK
606 D(("mFormatID %08lx", asbd.mFormatID));
607 D(("mFormatFlags %08lx", asbd.mFormatFlags));
608 D(("mBytesPerPacket %08lx", asbd.mBytesPerPacket));
609 D(("mFramesPerPacket %08lx", asbd.mFramesPerPacket));
610 D(("mBytesPerFrame %08lx", asbd.mBytesPerFrame));
611 D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
612 D(("mBitsPerChannel %08lx", asbd.mBitsPerChannel));
613 D(("mReserved %08lx", asbd.mReserved));
e83d0967
RK
614 if(asbd.mFormatID != kAudioFormatLinearPCM)
615 fatal(0, "audio device does not support kAudioFormatLinearPCM");
616 status = AudioDeviceAddIOProc(adid, adioproc, 0);
617 if(status)
618 fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
619 pthread_mutex_lock(&lock);
620 for(;;) {
621 /* Wait for the buffer to fill up a bit */
8dcb5ff0 622 info("Buffering...");
1153fd23 623 while(nsamples < readahead)
e83d0967
RK
624 pthread_cond_wait(&cond, &lock);
625 /* Start playing now */
8dcb5ff0
RK
626 info("Playing...");
627 next_timestamp = packets->timestamp;
628 active = 1;
e83d0967
RK
629 status = AudioDeviceStart(adid, adioproc);
630 if(status)
631 fatal(0, "AudioDeviceStart: %d", (int)status);
632 /* Wait until the buffer empties out */
1153fd23 633 while(nsamples >= minbuffer)
e83d0967
RK
634 pthread_cond_wait(&cond, &lock);
635 /* Stop playing for a bit until the buffer re-fills */
636 status = AudioDeviceStop(adid, adioproc);
637 if(status)
638 fatal(0, "AudioDeviceStop: %d", (int)status);
8dcb5ff0 639 active = 0;
e83d0967
RK
640 /* Go back round */
641 }
642 }
643#else
644# error No known audio API
645#endif
646}
647
648/* display usage message and terminate */
649static void help(void) {
650 xprintf("Usage:\n"
651 " disorder-playrtp [OPTIONS] ADDRESS [PORT]\n"
652 "Options:\n"
1153fd23 653 " --device, -D DEVICE Output device\n"
654 " --min, -m FRAMES Buffer low water mark\n"
9086a105
RK
655 " --buffer, -b FRAMES Buffer high water mark\n"
656 " --max, -x FRAMES Buffer maximum size\n"
657 " --help, -h Display usage message\n"
658 " --version, -V Display version number\n"
659 );
e83d0967
RK
660 xfclose(stdout);
661 exit(0);
662}
663
664/* display version number and terminate */
665static void version(void) {
666 xprintf("disorder-playrtp version %s\n", disorder_version_string);
667 xfclose(stdout);
668 exit(0);
669}
670
671int main(int argc, char **argv) {
672 int n;
673 struct addrinfo *res;
674 struct stringlist sl;
0b75463f 675 char *sockname;
e83d0967 676
0b75463f 677 static const struct addrinfo prefs = {
e83d0967
RK
678 AI_PASSIVE,
679 PF_INET,
680 SOCK_DGRAM,
681 IPPROTO_UDP,
682 0,
683 0,
684 0,
685 0
686 };
687
688 mem_init();
689 if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
345ebe66 690 while((n = getopt_long(argc, argv, "hVdD:m:b:x:L:", options, 0)) >= 0) {
e83d0967
RK
691 switch(n) {
692 case 'h': help();
693 case 'V': version();
694 case 'd': debugging = 1; break;
0b75463f 695 case 'D': device = optarg; break;
1153fd23 696 case 'm': minbuffer = 2 * atol(optarg); break;
697 case 'b': readahead = 2 * atol(optarg); break;
9086a105 698 case 'x': maxbuffer = 2 * atol(optarg); break;
345ebe66 699 case 'L': logfp = fopen(optarg, "w"); break;
e83d0967
RK
700 default: fatal(0, "invalid option");
701 }
702 }
9086a105
RK
703 if(!maxbuffer)
704 maxbuffer = 4 * readahead;
e83d0967
RK
705 argc -= optind;
706 argv += optind;
707 if(argc < 1 || argc > 2)
708 fatal(0, "usage: disorder-playrtp [OPTIONS] ADDRESS [PORT]");
709 sl.n = argc;
710 sl.s = argv;
711 /* Listen for inbound audio data */
0b75463f 712 if(!(res = get_address(&sl, &prefs, &sockname)))
e83d0967
RK
713 exit(1);
714 if((rtpfd = socket(res->ai_family,
715 res->ai_socktype,
716 res->ai_protocol)) < 0)
717 fatal(errno, "error creating socket");
718 if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0)
719 fatal(errno, "error binding socket to %s", sockname);
720 play_rtp();
721 return 0;
722}
723
724/*
725Local Variables:
726c-basic-offset:2
727comment-column:40
728fill-column:79
729indent-tabs-mode:nil
730End:
731*/