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);