chiark / gitweb /
Support arbitrary Core Audio devices.
[disorder] / server / speaker-coreaudio.c
CommitLineData
937be4c0
RK
1/*
2 * This file is part of DisOrder
3 * Copyright (C) 2007 Richard Kettlewell
4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
937be4c0 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
937be4c0
RK
8 * (at your option) any later version.
9 *
e7eb3a27
RK
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 *
937be4c0 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
937be4c0
RK
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
05b75f8d 33#include "common.h"
937be4c0
RK
34
35#if HAVE_COREAUDIO_AUDIOHARDWARE_H
36
937be4c0
RK
37#include <poll.h>
38#include <sys/socket.h>
39#include <unistd.h>
937be4c0
RK
40
41#include "configuration.h"
42#include "syscalls.h"
43#include "log.h"
44#include "speaker-protocol.h"
45#include "speaker.h"
f5fd9a6b 46#include "coreaudio.h"
937be4c0
RK
47
48/** @brief Core Audio Device ID */
49static 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 */
55static int pfd[2];
56
57/** @brief Slot number in poll array */
58static int pfd_slot;
59
60/** @brief Callback from Core Audio */
61static 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 */
111static void coreaudio_init(void) {
112 OSStatus status;
113 UInt32 propertySize;
114 AudioStreamBasicDescription asbd;
115
f5fd9a6b 116 adid = coreaudio_getdevice(config->device);
937be4c0
RK
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 */
145static 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 */
155static 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 */
166static 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 */
e84fb5f0 195static void coreaudio_beforepoll(int attribute((unused)) *timeoutp) {
937be4c0
RK
196 pfd_slot = addfd(pfd[1], POLLOUT);
197}
198
199/** @brief Process poll() results for Core Audio */
200static int coreaudio_ready(void) {
201 return !!(fds[pfd_slot].revents & (POLLOUT|POLLERR));
202}
203
204/** @brief Backend definition for Core Audio */
205const 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/*
219Local Variables:
220c-basic-offset:2
221comment-column:40
222fill-column:79
223indent-tabs-mode:nil
224End:
225*/