2 * This file is part of DisOrder.
3 * Copyright (C) 2007 Richard Kettlewell
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.
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.
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
27 #include <sys/socket.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
35 #include "configuration.h"
41 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
42 # include <CoreAudio/AudioHardware.h>
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 */
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 */
63 static unsigned long nsamples
; /* total samples available */
65 static struct frame
*frames
; /* received frames in ascending order
67 static pthread_mutex_t lock
= PTHREAD_MUTEX_INITIALIZER
;
68 /* lock protecting frame list */
70 static pthread_cond_t cond
= PTHREAD_CONDVAR_INITIALIZER
;
71 /* signalled whenever we add a new frame */
73 static const struct option options
[] = {
74 { "help", no_argument
, 0, 'h' },
75 { "version", no_argument
, 0, 'V' },
76 { "debug", no_argument
, 0, 'd' },
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;
85 /* Background thread that reads frames over the network and add them to the
87 static listen_thread(void attribute((unused
)) *arg
) {
88 struct frame
*f
= 0, **ff
;
91 struct rtp_header header
;
92 uint8_t bytes
[sizeof(uint16_t) * MAXSAMPLES
+ sizeof (struct rtp_header
)];
94 const uint16_t *const samples
= (uint16_t *)(packet
.bytes
95 + sizeof (struct rtp_header
));
99 f
= xmalloc(sizeof *f
);
100 n
= read(rtpfd
, packet
.bytes
, sizeof packet
.bytes
);
106 fatal(errno
, "error reading from socket");
109 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
110 /* Convert to target format */
111 switch(packet
.header
.mtp
& 0x7F) {
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);
117 /* TODO support other RFC3551 media types (when the speaker does) */
119 fatal(0, "unsupported RTP payload type %d",
120 packet
.header
.mpt
& 0x7F);
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
)
133 nsamples
+= f
->nsamples
;
134 pthread_cond_broadcast(&cond
);
135 pthread_mutex_unlock(&lock
);
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
;
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
;
164 pthread_cond_broadcast(&cond
);
167 if(samplesOutLeft
== 0) {
170 samplesOut
= ab
->data
;
171 samplesOutLeft
= ab
->mDataByteSize
/ sizeof (float);
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
;
184 pthread_mutex_unlock(&lock
);
189 void play_rtp(void) {
192 /* We receive and convert audio data in a background thread */
193 pthread_create(<
, 0, listen_thread
, 0);
195 assert(!"implemented");
196 #elif HAVE_COREAUDIO_AUDIOHARDWARE_H
201 AudioStreamBasicDescription asbd
;
203 /* If this looks suspiciously like libao's macosx driver there's an
204 * excellent reason for that... */
206 /* TODO report errors as strings not numbers */
207 propertySize
= sizeof adid
;
208 status
= AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice
,
209 &propertySize
, &adid
);
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
);
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);
233 fatal(0, "AudioDeviceAddIOProc: %d", (int)status
);
234 pthread_mutex_lock(&lock
);
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
);
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
);
249 fatal(0, "AudioDeviceStop: %d", (int)status
);
254 # error No known audio API
258 /* display usage message and terminate */
259 static void help(void) {
261 " disorder-playrtp [OPTIONS] ADDRESS [PORT]\n"
263 " --help, -h Display usage message\n"
264 " --version, -V Display version number\n"
265 " --debug, -d Turn on debugging\n");
270 /* display version number and terminate */
271 static void version(void) {
272 xprintf("disorder-playrtp version %s\n", disorder_version_string
);
277 int main(int argc
, char **argv
) {
279 struct addrinfo
*res
;
280 struct stringlist sl
;
281 const char *sockname
;
283 static const struct addrinfo prefbind
= {
295 if(!setlocale(LC_CTYPE
, "")) fatal(errno
, "error calling setlocale");
296 while((n
= getopt_long(argc
, argv
, "hVd", options
, 0)) >= 0) {
300 case 'd': debugging
= 1; break;
301 default: fatal(0, "invalid option");
306 if(argc
< 1 || argc
> 2)
307 fatal(0, "usage: disorder-playrtp [OPTIONS] ADDRESS [PORT]");
310 /* Listen for inbound audio data */
311 if(!(res
= get_address(&sl
, &pref
, &sockname
)))
313 if((rtpfd
= socket(res
->ai_family
,
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
);