2 * This file is part of DisOrder
3 * Copyright (C) 2007 Richard Kettlewell
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.
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.
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
20 /** @file server/speaker-coreaudio.c
21 * @brief Support for @ref BACKEND_COREAUDIO
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
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...
32 * @todo support @ref config::device
37 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
40 #include <sys/socket.h>
42 #include <CoreAudio/AudioHardware.h>
44 #include "configuration.h"
47 #include "speaker-protocol.h"
50 /** @brief Core Audio Device ID */
51 static AudioDeviceID adid;
53 /** @brief Pipe between main and player threads
55 * We'll write samples to pfd[1] and read them from pfd[0].
59 /** @brief Slot number in poll array */
62 /** @brief Callback from Core Audio */
63 static OSStatus adioproc
64 (AudioDeviceID attribute((unused)) inDevice,
65 const AudioTimeStamp attribute((unused)) *inNow,
66 const AudioBufferList attribute((unused)) *inInputData,
67 const AudioTimeStamp attribute((unused)) *inInputTime,
68 AudioBufferList *outOutputData,
69 const AudioTimeStamp attribute((unused)) *inOutputTime,
70 void attribute((unused)) *inClientData) {
71 UInt32 nbuffers = outOutputData->mNumberBuffers;
72 AudioBuffer *ab = outOutputData->mBuffers;
75 float *samplesOut = ab->mData;
76 size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
77 int16_t input[1024], *ptr;
82 while(samplesOutLeft > 0) {
83 /* Read some more data */
84 bytes = samplesOutLeft * sizeof (int16_t);
85 if(bytes > sizeof input)
88 bytes_read = read(pfd[0], input, bytes);
92 continue; /* just try again */
94 return 0; /* underrun - just play 0s */
96 fatal(errno, "read error in core audio thread");
98 assert(bytes_read % 4 == 0); /* TODO horrible bodge! */
99 samples = bytes_read / sizeof (int16_t);
100 assert(samples <= samplesOutLeft);
102 samplesOutLeft -= samples;
104 *samplesOut++ = *ptr++ * (0.5 / 32767);
112 /** @brief Core Audio backend initialization */
113 static void coreaudio_init(void) {
116 AudioStreamBasicDescription asbd;
118 propertySize = sizeof adid;
119 status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
120 &propertySize, &adid);
122 fatal(0, "AudioHardwareGetProperty: %d", (int)status);
123 if(adid == kAudioDeviceUnknown)
124 fatal(0, "no output device");
125 propertySize = sizeof asbd;
126 status = AudioDeviceGetProperty(adid, 0, false,
127 kAudioDevicePropertyStreamFormat,
128 &propertySize, &asbd);
130 fatal(0, "AudioHardwareGetProperty: %d", (int)status);
131 D(("mSampleRate %f", asbd.mSampleRate));
132 D(("mFormatID %08lx", asbd.mFormatID));
133 D(("mFormatFlags %08lx", asbd.mFormatFlags));
134 D(("mBytesPerPacket %08lx", asbd.mBytesPerPacket));
135 D(("mFramesPerPacket %08lx", asbd.mFramesPerPacket));
136 D(("mBytesPerFrame %08lx", asbd.mBytesPerFrame));
137 D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
138 D(("mBitsPerChannel %08lx", asbd.mBitsPerChannel));
139 D(("mReserved %08lx", asbd.mReserved));
140 if(asbd.mFormatID != kAudioFormatLinearPCM)
141 fatal(0, "audio device does not support kAudioFormatLinearPCM");
142 status = AudioDeviceAddIOProc(adid, adioproc, 0);
144 fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
145 if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfd) < 0)
146 fatal(errno, "error calling socketpair");
149 info("selected Core Audio backend");
152 /** @brief Core Audio deactivation */
153 static void coreaudio_deactivate(void) {
154 const OSStatus status = AudioDeviceStop(adid, adioproc);
156 error(0, "AudioDeviceStop: %d", (int)status);
157 device_state = device_error;
159 device_state = device_closed;
162 /** @brief Core Audio backend activation */
163 static void coreaudio_activate(void) {
164 const OSStatus status = AudioDeviceStart(adid, adioproc);
167 error(0, "AudioDeviceStart: %d", (int)status);
168 device_state = device_error;
170 device_state = device_open;
173 /** @brief Play via Core Audio */
174 static size_t coreaudio_play(size_t frames) {
175 static size_t leftover;
177 size_t bytes = frames * bpf + leftover;
178 ssize_t bytes_written;
181 /* There is a partial frame left over from an earlier write. Try
182 * and finish that off before doing anything else. */
184 bytes_written = write(pfd[1], playing->buffer + playing->start, bytes);
185 if(bytes_written < 0)
187 case EINTR: /* interrupted */
188 case EAGAIN: /* buffer full */
189 return 0; /* try later */
191 fatal(errno, "error writing to core audio player thread");
194 /* We were dealing the leftover bytes of a partial frame */
195 leftover -= bytes_written;
198 leftover = bytes_written % bpf;
199 return bytes_written / bpf;
202 /** @brief Fill in poll fd array for Core Audio */
203 static void coreaudio_beforepoll(int attribute((unused)) *timeoutp) {
204 pfd_slot = addfd(pfd[1], POLLOUT);
207 /** @brief Process poll() results for Core Audio */
208 static int coreaudio_ready(void) {
209 return !!(fds[pfd_slot].revents & (POLLOUT|POLLERR));
212 /** @brief Backend definition for Core Audio */
213 const struct speaker_backend coreaudio_backend = {
219 coreaudio_deactivate,
220 coreaudio_beforepoll,