Commit | Line | Data |
---|---|---|
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 | ||
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(<, 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 | */ |