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