chiark / gitweb /
Install disorderd under launchd in Mac OS X.
[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>
32
33#include "log.h"
34#include "mem.h"
35#include "configuration.h"
36#include "addr.h"
37#include "syscalls.h"
38#include "rtp.h"
39#include "debug.h"
40
41#if HAVE_COREAUDIO_AUDIOHARDWARE_H
42# include <CoreAudio/AudioHardware.h>
43#endif
44
45static int rtpfd;
46
47#define MAXSAMPLES 2048 /* max samples/frame we'll support */
48/* NB two channels = two samples in this program! */
49#define MINBUFFER 8820 /* when to stop playing */
50#define READAHEAD 88200 /* how far to read ahead */
51#define MAXBUFFER (3 * 88200) /* maximum buffer contents */
52
53struct frame {
54 struct frame *next; /* another frame */
55 int nsamples; /* number of samples */
56 int nused; /* number of samples used so far */
57 uint32_t timestamp; /* timestamp from packet */
58#if HAVE_COREAUDIO_AUDIOHARDWARE_H
59 float samples[MAXSAMPLES]; /* converted sample data */
60#endif
61};
62
63static unsigned long nsamples; /* total samples available */
64
65static struct frame *frames; /* received frames in ascending order
66 * of timestamp */
67static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
68/* lock protecting frame list */
69
70static pthread_cond_t cond = PTHREAD_CONDVAR_INITIALIZER;
71/* signalled whenever we add a new frame */
72
73static const struct option options[] = {
74 { "help", no_argument, 0, 'h' },
75 { "version", no_argument, 0, 'V' },
76 { "debug", no_argument, 0, 'd' },
77 { 0, 0, 0, 0 }
78};
79
80/* Return true iff a > b in sequence-space arithmetic */
81static inline int gt(const struct frame *a, const struct frame *b) {
82 return (uint32_t)(a->timestamp - b->timestamp) < 0x80000000;
83}
84
85/* Background thread that reads frames over the network and add them to the
86 * list */
87static listen_thread(void attribute((unused)) *arg) {
88 struct frame *f = 0, **ff;
89 int n, i;
90 union {
91 struct rtp_header header;
92 uint8_t bytes[sizeof(uint16_t) * MAXSAMPLES + sizeof (struct rtp_header)];
93 } packet;
94 const uint16_t *const samples = (uint16_t *)(packet.bytes
95 + sizeof (struct rtp_header));
96
97 for(;;) {
98 if(!f)
99 f = xmalloc(sizeof *f);
100 n = read(rtpfd, packet.bytes, sizeof packet.bytes);
101 if(n < 0) {
102 switch(errno) {
103 case EINTR:
104 continue;
105 default:
106 fatal(errno, "error reading from socket");
107 }
108 }
109#if HAVE_COREAUDIO_AUDIOHARDWARE_H
110 /* Convert to target format */
111 switch(packet.header.mtp & 0x7F) {
112 case 10:
113 f->nsamples = (n - sizeof (struct rtp_header)) / sizeof(uint16_t);
114 for(i = 0; i < f->nsamples; ++i)
115 f->samples[i] = (int16_t)ntohs(samples[i]) * (0.5f / 32767);
116 break;
117 /* TODO support other RFC3551 media types (when the speaker does) */
118 default:
119 fatal(0, "unsupported RTP payload type %d",
120 packet.header.mpt & 0x7F);
121 }
122#endif
123 f->used = 0;
124 f->timestamp = ntohl(packet.header.timestamp);
125 pthread_mutex_lock(&lock);
126 /* Stop reading if we've reached the maximum */
127 while(nsamples >= MAXBUFFER)
128 pthread_cond_wait(&cond, &lock);
129 for(ff = &frames; *ff && !gt(*ff, f); ff = &(*ff)->next)
130 ;
131 f->next = *ff;
132 *ff = f;
133 nsamples += f->nsamples;
134 pthread_cond_broadcast(&cond);
135 pthread_mutex_unlock(&lock);
136 f = 0;
137 }
138}
139
140#if HAVE_COREAUDIO_AUDIOHARDWARE_H
141static OSStatus adioproc(AudioDeviceID inDevice,
142 const AudioTimeStamp *inNow,
143 const AudioBufferList *inInputData,
144 const AudioTimeStamp *inInputTime,
145 AudioBufferList *outOutputData,
146 const AudioTimeStamp *inOutputTime,
147 void *inClientData) {
148 UInt32 nbuffers = outOutputData->mNumberBuffers;
149 AudioBuffer *ab = outOutputData->mBuffers;
150 float *samplesOut; /* where to write samples to */
151 size_t samplesOutLeft; /* space left */
152 size_t samplesInLeft;
153 size_t samplesToCopy;
154
155 pthread_mutex_lock(&lock);
156 samplesOut = ab->data;
157 samplesOutLeft = ab->mDataByteSize / sizeof (float);
158 while(frames && nbuffers > 0) {
159 if(frames->used == frames->nsamples) {
160 /* TODO if we dropped a packet then we should introduce a gap here */
161 struct frame *const f = frames;
162 frames = f->next;
163 free(f);
164 pthread_cond_broadcast(&cond);
165 continue;
166 }
167 if(samplesOutLeft == 0) {
168 --nbuffers;
169 ++ab;
170 samplesOut = ab->data;
171 samplesOutLeft = ab->mDataByteSize / sizeof (float);
172 continue;
173 }
174 /* Now: (1) there is some data left to read
175 * (2) there is some space to put it */
176 samplesInLeft = frames->nsamples - frames->used;
177 samplesToCopy = (samplesInLeft < samplesOutLeft
178 ? samplesInLeft : samplesOutLeft);
179 memcpy(samplesOut, frame->samples + frames->used, samplesToCopy);
180 frames->used += samplesToCopy;
181 samplesOut += samplesToCopy;
182 samesOutLeft -= samplesToCopy;
183 }
184 pthread_mutex_unlock(&lock);
185 return 0;
186}
187#endif
188
189void play_rtp(void) {
190 pthread_t lt;
191
192 /* We receive and convert audio data in a background thread */
193 pthread_create(&lt, 0, listen_thread, 0);
194#if API_ALSA
195 assert(!"implemented");
196#elif HAVE_COREAUDIO_AUDIOHARDWARE_H
197 {
198 OSStatus status;
199 UInt32 propertySize;
200 AudioDeviceID adid;
201 AudioStreamBasicDescription asbd;
202
203 /* If this looks suspiciously like libao's macosx driver there's an
204 * excellent reason for that... */
205
206 /* TODO report errors as strings not numbers */
207 propertySize = sizeof adid;
208 status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
209 &propertySize, &adid);
210 if(status)
211 fatal(0, "AudioHardwareGetProperty: %d", (int)status);
212 if(adid == kAudioDeviceUnknown)
213 fatal(0, "no output device");
214 propertySize = sizeof asbd;
215 status = AudioDeviceGetProperty(adid, 0, false,
216 kAudioDevicePropertyStreamFormat,
217 &propertySize, &asbd);
218 if(status)
219 fatal(0, "AudioHardwareGetProperty: %d", (int)status);
220 D(("mSampleRate %f", asbd.mSampleRate));
221 D(("mFormatID %08"PRIx32, asbd.mFormatID));
222 D(("mFormatFlags %08"PRIx32, asbd.mFormatFlags));
223 D(("mBytesPerPacket %08"PRIx32, asbd.mBytesPerPacket));
224 D(("mFramesPerPacket %08"PRIx32, asbd.mFramesPerPacket));
225 D(("mBytesPerFrame %08"PRIx32, asbd.mBytesPerFrame));
226 D(("mChannelsPerFrame %08"PRIx32, asbd.mChannelsPerFrame));
227 D(("mBitsPerChannel %08"PRIx32, asbd.mBitsPerChannel));
228 D(("mReserved %08"PRIx32, asbd.mReserved));
229 if(asbd.mFormatID != kAudioFormatLinearPCM)
230 fatal(0, "audio device does not support kAudioFormatLinearPCM");
231 status = AudioDeviceAddIOProc(adid, adioproc, 0);
232 if(status)
233 fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
234 pthread_mutex_lock(&lock);
235 for(;;) {
236 /* Wait for the buffer to fill up a bit */
237 while(nsamples < READAHEAD)
238 pthread_cond_wait(&cond, &lock);
239 /* Start playing now */
240 status = AudioDeviceStart(adid, adioproc);
241 if(status)
242 fatal(0, "AudioDeviceStart: %d", (int)status);
243 /* Wait until the buffer empties out */
244 while(nsamples >= MINBUFFER)
245 pthread_cond_wait(&cond, &lock);
246 /* Stop playing for a bit until the buffer re-fills */
247 status = AudioDeviceStop(adid, adioproc);
248 if(status)
249 fatal(0, "AudioDeviceStop: %d", (int)status);
250 /* Go back round */
251 }
252 }
253#else
254# error No known audio API
255#endif
256}
257
258/* display usage message and terminate */
259static void help(void) {
260 xprintf("Usage:\n"
261 " disorder-playrtp [OPTIONS] ADDRESS [PORT]\n"
262 "Options:\n"
263 " --help, -h Display usage message\n"
264 " --version, -V Display version number\n"
265 " --debug, -d Turn on debugging\n");
266 xfclose(stdout);
267 exit(0);
268}
269
270/* display version number and terminate */
271static void version(void) {
272 xprintf("disorder-playrtp version %s\n", disorder_version_string);
273 xfclose(stdout);
274 exit(0);
275}
276
277int main(int argc, char **argv) {
278 int n;
279 struct addrinfo *res;
280 struct stringlist sl;
281 const char *sockname;
282
283 static const struct addrinfo prefbind = {
284 AI_PASSIVE,
285 PF_INET,
286 SOCK_DGRAM,
287 IPPROTO_UDP,
288 0,
289 0,
290 0,
291 0
292 };
293
294 mem_init();
295 if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
296 while((n = getopt_long(argc, argv, "hVd", options, 0)) >= 0) {
297 switch(n) {
298 case 'h': help();
299 case 'V': version();
300 case 'd': debugging = 1; break;
301 default: fatal(0, "invalid option");
302 }
303 }
304 argc -= optind;
305 argv += optind;
306 if(argc < 1 || argc > 2)
307 fatal(0, "usage: disorder-playrtp [OPTIONS] ADDRESS [PORT]");
308 sl.n = argc;
309 sl.s = argv;
310 /* Listen for inbound audio data */
311 if(!(res = get_address(&sl, &pref, &sockname)))
312 exit(1);
313 if((rtpfd = socket(res->ai_family,
314 res->ai_socktype,
315 res->ai_protocol)) < 0)
316 fatal(errno, "error creating socket");
317 if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0)
318 fatal(errno, "error binding socket to %s", sockname);
319 play_rtp();
320 return 0;
321}
322
323/*
324Local Variables:
325c-basic-offset:2
326comment-column:40
327fill-column:79
328indent-tabs-mode:nil
329End:
330*/