chiark / gitweb /
Read user.tmpl after macros.tmpl
[disorder] / server / speaker-coreaudio.c
CommitLineData
937be4c0
RK
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 */
53static 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 */
59static int pfd[2];
60
61/** @brief Slot number in poll array */
62static int pfd_slot;
63
64/** @brief Callback from Core Audio */
65static 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 */
115static 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 */
155static 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 */
165static 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 */
176static 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 */
e84fb5f0 205static void coreaudio_beforepoll(int attribute((unused)) *timeoutp) {
937be4c0
RK
206 pfd_slot = addfd(pfd[1], POLLOUT);
207}
208
209/** @brief Process poll() results for Core Audio */
210static int coreaudio_ready(void) {
211 return !!(fds[pfd_slot].revents & (POLLOUT|POLLERR));
212}
213
214/** @brief Backend definition for Core Audio */
215const 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/*
229Local Variables:
230c-basic-offset:2
231comment-column:40
232fill-column:79
233indent-tabs-mode:nil
234End:
235*/