chiark / gitweb /
more thorough kvp.c testing
[disorder] / server / speaker-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 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 /** @file server/speaker-coreaudio.c
21  * @brief Support for @ref BACKEND_COREAUDIO
22  *
23  * Core Audio likes to make callbacks from a separate player thread
24  * which then fill in the required number of bytes of audio.  We fit
25  * this into the existing architecture by means of a pipe between the
26  * threads.
27  *
28  * We currently only support 16-bit 44100Hz stereo (and enforce this
29  * in @ref lib/configuration.c.)  There are some nasty bodges in this
30  * code which depend on this and on further assumptions still...
31  *
32  * @todo support @ref config::device
33  */
34
35 #include <config.h>
36
37 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
38
39 #include "types.h"
40
41 #include <poll.h>
42 #include <sys/socket.h>
43 #include <unistd.h>
44 #include <CoreAudio/AudioHardware.h>
45
46 #include "configuration.h"
47 #include "syscalls.h"
48 #include "log.h"
49 #include "speaker-protocol.h"
50 #include "speaker.h"
51
52 /** @brief Core Audio Device ID */
53 static AudioDeviceID adid;
54
55 /** @brief Pipe between main and player threads
56  *
57  * We'll write samples to pfd[1] and read them from pfd[0].
58  */
59 static int pfd[2];
60
61 /** @brief Slot number in poll array */
62 static int pfd_slot;
63
64 /** @brief Callback from Core Audio */
65 static OSStatus adioproc
66     (AudioDeviceID attribute((unused)) inDevice,
67      const AudioTimeStamp attribute((unused)) *inNow,
68      const AudioBufferList attribute((unused)) *inInputData,
69      const AudioTimeStamp attribute((unused)) *inInputTime,
70      AudioBufferList *outOutputData,
71      const AudioTimeStamp attribute((unused)) *inOutputTime,
72      void attribute((unused)) *inClientData) {
73   UInt32 nbuffers = outOutputData->mNumberBuffers;
74   AudioBuffer *ab = outOutputData->mBuffers;
75
76   while(nbuffers > 0) {
77     float *samplesOut = ab->mData;
78     size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
79     int16_t input[1024], *ptr;
80     size_t bytes;
81     ssize_t bytes_read;
82     size_t samples;
83
84     while(samplesOutLeft > 0) {
85       /* Read some more data */
86       bytes = samplesOutLeft * sizeof (int16_t);
87       if(bytes > sizeof input)
88         bytes = sizeof input;
89       
90       bytes_read = read(pfd[0], input, bytes);
91       if(bytes_read < 0)
92         switch(errno) {
93         case EINTR:
94           continue;             /* just try again */
95         case EAGAIN:
96           return 0;             /* underrun - just play 0s */
97         default:
98           fatal(errno, "read error in core audio thread");
99         }
100       assert(bytes_read % 4 == 0); /* TODO horrible bodge! */
101       samples = bytes_read / sizeof (int16_t);
102       assert(samples <= samplesOutLeft);
103       ptr = input;
104       samplesOutLeft -= samples;
105       while(samples-- > 0)
106         *samplesOut++ = *ptr++ * (0.5 / 32767);
107     }
108     ++ab;
109     --nbuffers;
110   }
111   return 0;
112 }
113
114 /** @brief Core Audio backend initialization */
115 static void coreaudio_init(void) {
116   OSStatus status;
117   UInt32 propertySize;
118   AudioStreamBasicDescription asbd;
119
120   propertySize = sizeof adid;
121   status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
122                                     &propertySize, &adid);
123   if(status)
124     fatal(0, "AudioHardwareGetProperty: %d", (int)status);
125   if(adid == kAudioDeviceUnknown)
126     fatal(0, "no output device");
127   propertySize = sizeof asbd;
128   status = AudioDeviceGetProperty(adid, 0, false,
129                                   kAudioDevicePropertyStreamFormat,
130                                   &propertySize, &asbd);
131   if(status)
132     fatal(0, "AudioHardwareGetProperty: %d", (int)status);
133   D(("mSampleRate       %f", asbd.mSampleRate));
134   D(("mFormatID         %08lx", asbd.mFormatID));
135   D(("mFormatFlags      %08lx", asbd.mFormatFlags));
136   D(("mBytesPerPacket   %08lx", asbd.mBytesPerPacket));
137   D(("mFramesPerPacket  %08lx", asbd.mFramesPerPacket));
138   D(("mBytesPerFrame    %08lx", asbd.mBytesPerFrame));
139   D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
140   D(("mBitsPerChannel   %08lx", asbd.mBitsPerChannel));
141   D(("mReserved         %08lx", asbd.mReserved));
142   if(asbd.mFormatID != kAudioFormatLinearPCM)
143     fatal(0, "audio device does not support kAudioFormatLinearPCM");
144   status = AudioDeviceAddIOProc(adid, adioproc, 0);
145   if(status)
146     fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
147   if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfd) < 0)
148     fatal(errno, "error calling socketpair");
149   nonblock(pfd[0]);
150   nonblock(pfd[1]);
151   info("selected Core Audio backend");
152 }
153
154 /** @brief Core Audio deactivation */
155 static void coreaudio_deactivate(void) {
156   const OSStatus status = AudioDeviceStop(adid, adioproc);
157   if(status) {
158     error(0, "AudioDeviceStop: %d", (int)status);
159     device_state = device_error;
160   } else
161     device_state = device_closed;
162 }
163
164 /** @brief Core Audio backend activation */
165 static void coreaudio_activate(void) {
166   const OSStatus status = AudioDeviceStart(adid, adioproc);
167
168   if(status) {
169     error(0, "AudioDeviceStart: %d", (int)status);
170     device_state = device_error;  
171   }
172   device_state = device_open;
173 }
174
175 /** @brief Play via Core Audio */
176 static size_t coreaudio_play(size_t frames) {
177   static size_t leftover;
178
179   size_t bytes = frames * bpf + leftover;
180   ssize_t bytes_written;
181
182   if(leftover)
183     /* There is a partial frame left over from an earlier write.  Try
184      * and finish that off before doing anything else. */
185     bytes = leftover;
186   bytes_written = write(pfd[1], playing->buffer + playing->start, bytes);
187   if(bytes_written < 0)
188     switch(errno) {
189     case EINTR:                 /* interrupted */
190     case EAGAIN:                /* buffer full */
191       return 0;                 /* try later */
192     default:
193       fatal(errno, "error writing to core audio player thread");
194     }
195   if(leftover) {
196     /* We were dealing the leftover bytes of a partial frame */
197     leftover -= bytes_written;
198     return !leftover;
199   }
200   leftover = bytes_written % bpf;
201   return bytes_written / bpf;
202 }
203
204 /** @brief Fill in poll fd array for Core Audio */
205 static void coreaudio_beforepoll(int attribute((unused)) *timeoutp) {
206   pfd_slot = addfd(pfd[1], POLLOUT);
207 }
208
209 /** @brief Process poll() results for Core Audio */
210 static int coreaudio_ready(void) {
211   return !!(fds[pfd_slot].revents & (POLLOUT|POLLERR));
212 }
213
214 /** @brief Backend definition for Core Audio */
215 const struct speaker_backend coreaudio_backend = {
216   BACKEND_COREAUDIO,
217   0,
218   coreaudio_init,
219   coreaudio_activate,
220   coreaudio_play,
221   coreaudio_deactivate,
222   coreaudio_beforepoll,
223   coreaudio_ready
224 };
225
226 #endif
227
228 /*
229 Local Variables:
230 c-basic-offset:2
231 comment-column:40
232 fill-column:79
233 indent-tabs-mode:nil
234 End:
235 */