Support arbitrary Core Audio devices.
[disorder] / clients / playrtp-coreaudio.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 3 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,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU 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, see <http://www.gnu.org/licenses/>.
17 */
18 /** @file clients/playrtp-coreaudio.c
19 * @brief RTP player - Core Audio support
20 */
21
22 #include "common.h"
23
24 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
25 #include <pthread.h>
26
27 #include "mem.h"
28 #include "log.h"
29 #include "vector.h"
30 #include "heap.h"
31 #include "playrtp.h"
32 #include "coreaudio.h"
33
34 /** @brief Callback from Core Audio */
35 static OSStatus adioproc
36 (AudioDeviceID attribute((unused)) inDevice,
37 const AudioTimeStamp attribute((unused)) *inNow,
38 const AudioBufferList attribute((unused)) *inInputData,
39 const AudioTimeStamp attribute((unused)) *inInputTime,
40 AudioBufferList *outOutputData,
41 const AudioTimeStamp attribute((unused)) *inOutputTime,
42 void attribute((unused)) *inClientData) {
43 UInt32 nbuffers = outOutputData->mNumberBuffers;
44 AudioBuffer *ab = outOutputData->mBuffers;
45 uint32_t samples_available;
46
47 pthread_mutex_lock(&lock);
48 while(nbuffers > 0) {
49 float *samplesOut = ab->mData;
50 size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
51
52 while(samplesOutLeft > 0) {
53 const struct packet *p = playrtp_next_packet();
54 if(p && contains(p, next_timestamp)) {
55 /* This packet is ready to play */
56 const uint32_t packet_end = p->timestamp + p->nsamples;
57 const uint32_t offset = next_timestamp - p->timestamp;
58 const uint16_t *ptr = (void *)(p->samples_raw + offset);
59
60 samples_available = packet_end - next_timestamp;
61 if(samples_available > samplesOutLeft)
62 samples_available = samplesOutLeft;
63 next_timestamp += samples_available;
64 samplesOutLeft -= samples_available;
65 if(dump_buffer) {
66 size_t n;
67
68 for(n = 0; n < samples_available; ++n) {
69 dump_buffer[dump_index++] = (int16_t)ntohs(ptr[n]);
70 dump_index %= dump_size;
71 }
72 }
73 while(samples_available-- > 0)
74 *samplesOut++ = (int16_t)ntohs(*ptr++) * (0.5 / 32767);
75 /* We don't bother junking the packet - that'll be dealt with next time
76 * round */
77 } else {
78 /* No packet is ready to play (and there might be no packet at all) */
79 samples_available = p ? p->timestamp - next_timestamp
80 : samplesOutLeft;
81 if(samples_available > samplesOutLeft)
82 samples_available = samplesOutLeft;
83 //info("infill by %"PRIu32, samples_available);
84 /* Conveniently the buffer is 0 to start with */
85 next_timestamp += samples_available;
86 samplesOut += samples_available;
87 samplesOutLeft -= samples_available;
88 if(dump_buffer) {
89 size_t n;
90
91 for(n = 0; n < samples_available; ++n) {
92 dump_buffer[dump_index++] = 0;
93 dump_index %= dump_size;
94 }
95 }
96 }
97 }
98 ++ab;
99 --nbuffers;
100 }
101 pthread_mutex_unlock(&lock);
102 return 0;
103 }
104
105 void playrtp_coreaudio(void) {
106 OSStatus status;
107 UInt32 propertySize;
108 AudioDeviceID adid;
109 AudioStreamBasicDescription asbd;
110
111 /* If this looks suspiciously like libao's macosx driver there's an
112 * excellent reason for that... */
113
114 /* TODO report errors as strings not numbers */
115 /* Identify the device to use */
116 adid = coreaudio_getdevice(device);
117 propertySize = sizeof asbd;
118 status = AudioDeviceGetProperty(adid, 0, false,
119 kAudioDevicePropertyStreamFormat,
120 &propertySize, &asbd);
121 if(status)
122 fatal(0, "AudioHardwareGetProperty: %d", (int)status);
123 D(("mSampleRate %f", asbd.mSampleRate));
124 D(("mFormatID %08lx", asbd.mFormatID));
125 D(("mFormatFlags %08lx", asbd.mFormatFlags));
126 D(("mBytesPerPacket %08lx", asbd.mBytesPerPacket));
127 D(("mFramesPerPacket %08lx", asbd.mFramesPerPacket));
128 D(("mBytesPerFrame %08lx", asbd.mBytesPerFrame));
129 D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
130 D(("mBitsPerChannel %08lx", asbd.mBitsPerChannel));
131 D(("mReserved %08lx", asbd.mReserved));
132 if(asbd.mFormatID != kAudioFormatLinearPCM)
133 fatal(0, "audio device does not support kAudioFormatLinearPCM");
134 status = AudioDeviceAddIOProc(adid, adioproc, 0);
135 if(status)
136 fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
137 pthread_mutex_lock(&lock);
138 for(;;) {
139 /* Wait for the buffer to fill up a bit */
140 playrtp_fill_buffer();
141 /* Start playing now */
142 info("Playing...");
143 next_timestamp = pheap_first(&packets)->timestamp;
144 active = 1;
145 status = AudioDeviceStart(adid, adioproc);
146 if(status)
147 fatal(0, "AudioDeviceStart: %d", (int)status);
148 /* Wait until the buffer empties out */
149 while(nsamples >= minbuffer
150 || (nsamples > 0
151 && contains(pheap_first(&packets), next_timestamp)))
152 pthread_cond_wait(&cond, &lock);
153 /* Stop playing for a bit until the buffer re-fills */
154 status = AudioDeviceStop(adid, adioproc);
155 if(status)
156 fatal(0, "AudioDeviceStop: %d", (int)status);
157 active = 0;
158 /* Go back round */
159 }
160 }
161
162 #endif
163
164 /*
165 Local Variables:
166 c-basic-offset:2
167 comment-column:40
168 fill-column:79
169 indent-tabs-mode:nil
170 End:
171 */