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
42 #include <sys/socket.h>
44 #include <CoreAudio/AudioHardware.h>
46 #include "configuration.h"
49 #include "speaker-protocol.h"
52 /** @brief Core Audio Device ID */
53 static AudioDeviceID adid
;
55 /** @brief Pipe between main and player threads
57 * We'll write samples to pfd[1] and read them from pfd[0].
61 /** @brief Slot number in poll array */
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
;
77 float *samplesOut
= ab
->mData
;
78 size_t samplesOutLeft
= ab
->mDataByteSize
/ sizeof (float);
79 int16_t input
[1024], *ptr
;
84 while(samplesOutLeft
> 0) {
85 /* Read some more data */
86 bytes
= samplesOutLeft
* sizeof (int16_t);
87 if(bytes
> sizeof input
)
90 bytes_read
= read(pfd
[0], input
, bytes
);
94 continue; /* just try again */
96 return 0; /* underrun - just play 0s */
98 fatal(errno
, "read error in core audio thread");
100 assert(bytes_read
% 4 == 0); /* TODO horrible bodge! */
101 samples
= bytes_read
/ sizeof (int16_t);
102 assert(samples
<= samplesOutLeft
);
104 samplesOutLeft
-= samples
;
106 *samplesOut
++ = *ptr
++ * (0.5 / 32767);
114 /** @brief Core Audio backend initialization */
115 static void coreaudio_init(void) {
118 AudioStreamBasicDescription asbd
;
120 propertySize
= sizeof adid
;
121 status
= AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice
,
122 &propertySize
, &adid
);
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
);
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);
146 fatal(0, "AudioDeviceAddIOProc: %d", (int)status
);
147 if(socketpair(PF_UNIX
, SOCK_STREAM
, 0, pfd
) < 0)
148 fatal(errno
, "error calling socketpair");
151 info("selected Core Audio backend");
154 /** @brief Core Audio deactivation */
155 static void coreaudio_deactivate(void) {
156 const OSStatus status
= AudioDeviceStop(adid
, adioproc
);
158 error(0, "AudioDeviceStop: %d", (int)status
);
159 device_state
= device_error
;
161 device_state
= device_closed
;
164 /** @brief Core Audio backend activation */
165 static void coreaudio_activate(void) {
166 const OSStatus status
= AudioDeviceStart(adid
, adioproc
);
169 error(0, "AudioDeviceStart: %d", (int)status
);
170 device_state
= device_error
;
172 device_state
= device_open
;
175 /** @brief Play via Core Audio */
176 static size_t coreaudio_play(size_t frames
) {
177 static size_t leftover
;
179 size_t bytes
= frames
* bpf
+ leftover
;
180 ssize_t bytes_written
;
183 /* There is a partial frame left over from an earlier write. Try
184 * and finish that off before doing anything else. */
186 bytes_written
= write(pfd
[1], playing
->buffer
+ playing
->start
, bytes
);
187 if(bytes_written
< 0)
189 case EINTR
: /* interrupted */
190 case EAGAIN
: /* buffer full */
191 return 0; /* try later */
193 fatal(errno
, "error writing to core audio player thread");
196 /* We were dealing the leftover bytes of a partial frame */
197 leftover
-= bytes_written
;
200 leftover
= bytes_written
% bpf
;
201 return bytes_written
/ bpf
;
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
);
209 /** @brief Process poll() results for Core Audio */
210 static int coreaudio_ready(void) {
211 return !!(fds
[pfd_slot
].revents
& (POLLOUT
|POLLERR
));
214 /** @brief Backend definition for Core Audio */
215 const struct speaker_backend coreaudio_backend
= {
221 coreaudio_deactivate
,
222 coreaudio_beforepoll
,