chiark / gitweb /
Install disorderd under launchd in Mac OS X.
[disorder] / clients / playrtp.c
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
45 static 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
53 struct 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
63 static unsigned long nsamples;          /* total samples available */
64
65 static struct frame *frames;            /* received frames in ascending order
66                                          * of timestamp */
67 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
68 /* lock protecting frame list */
69
70 static pthread_cond_t cond = PTHREAD_CONDVAR_INITIALIZER;
71 /* signalled whenever we add a new frame */
72
73 static 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 */
81 static 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 */
87 static 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
141 static 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
189 void 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 */
259 static 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 */
271 static void version(void) {
272   xprintf("disorder-playrtp version %s\n", disorder_version_string);
273   xfclose(stdout);
274   exit(0);
275 }
276
277 int 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 /*
324 Local Variables:
325 c-basic-offset:2
326 comment-column:40
327 fill-column:79
328 indent-tabs-mode:nil
329 End:
330 */