1 /*
2 * Copyright © 2015 Mozilla Foundation
3 *
4 * This program is made available under an ISC-style license. See the
5 * accompanying file LICENSE for details.
6 */
7 #include <math.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <sys/fmutex.h>
11
12 #include <kai.h>
13
14 #include "cubeb-internal.h"
15 #include "cubeb/cubeb.h"
16
17 /* We don't support more than 2 channels in KAI */
18 #define MAX_CHANNELS 2
19
20 #define NBUFS 2
21 #define FRAME_SIZE 2048
22
23 struct cubeb_stream_item {
24 cubeb_stream * stream;
25 };
26
27 static struct cubeb_ops const kai_ops;
28
29 struct cubeb {
30 struct cubeb_ops const * ops;
31 };
32
33 struct cubeb_stream {
34 /* Note: Must match cubeb_stream layout in cubeb.c. */
35 cubeb * context;
36 void * user_ptr;
37 /**/
38 cubeb_stream_params params;
39 cubeb_data_callback data_callback;
40 cubeb_state_callback state_callback;
41
42 HKAI hkai;
43 KAISPEC spec;
44 uint64_t total_frames;
45 float soft_volume;
46 _fmutex mutex;
47 float float_buffer[FRAME_SIZE * MAX_CHANNELS];
48 };
49
50 static inline long
frames_to_bytes(long frames,cubeb_stream_params params)51 frames_to_bytes(long frames, cubeb_stream_params params)
52 {
53 return frames * 2 * params.channels; /* 2 bytes per frame */
54 }
55
56 static inline long
bytes_to_frames(long bytes,cubeb_stream_params params)57 bytes_to_frames(long bytes, cubeb_stream_params params)
58 {
59 return bytes / 2 / params.channels; /* 2 bytes per frame */
60 }
61
62 static void
63 kai_destroy(cubeb * ctx);
64
65 /*static*/ int
kai_init(cubeb ** context,char const * context_name)66 kai_init(cubeb ** context, char const * context_name)
67 {
68 cubeb * ctx;
69
70 XASSERT(context);
71 *context = NULL;
72
73 if (kaiInit(KAIM_AUTO))
74 return CUBEB_ERROR;
75
76 ctx = calloc(1, sizeof(*ctx));
77 XASSERT(ctx);
78
79 ctx->ops = &kai_ops;
80
81 *context = ctx;
82
83 return CUBEB_OK;
84 }
85
86 static char const *
kai_get_backend_id(cubeb * ctx)87 kai_get_backend_id(cubeb * ctx)
88 {
89 return "kai";
90 }
91
92 static void
kai_destroy(cubeb * ctx)93 kai_destroy(cubeb * ctx)
94 {
95 kaiDone();
96
97 free(ctx);
98 }
99
100 static void
float_to_s16ne(int16_t * dst,float * src,size_t n)101 float_to_s16ne(int16_t * dst, float * src, size_t n)
102 {
103 long l;
104
105 while (n--) {
106 l = lrintf(*src++ * 0x8000);
107 if (l > 32767)
108 l = 32767;
109 if (l < -32768)
110 l = -32768;
111 *dst++ = (int16_t)l;
112 }
113 }
114
115 static ULONG APIENTRY
kai_callback(PVOID cbdata,PVOID buffer,ULONG len)116 kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
117 {
118 cubeb_stream * stm = cbdata;
119 void * p;
120 long wanted_frames;
121 long frames;
122 float soft_volume;
123 int elements = len / sizeof(int16_t);
124
125 p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE ? stm->float_buffer : buffer;
126
127 wanted_frames = bytes_to_frames(len, stm->params);
128 frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames);
129
130 _fmutex_request(&stm->mutex, 0);
131 stm->total_frames += frames;
132 soft_volume = stm->soft_volume;
133 _fmutex_release(&stm->mutex);
134
135 if (frames < wanted_frames)
136 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
137
138 if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE)
139 float_to_s16ne(buffer, p, elements);
140
141 if (soft_volume != -1.0f) {
142 int16_t * b = buffer;
143 int i;
144
145 for (i = 0; i < elements; i++)
146 *b++ *= soft_volume;
147 }
148
149 return frames_to_bytes(frames, stm->params);
150 }
151
152 static void
153 kai_stream_destroy(cubeb_stream * stm);
154
155 static int
kai_stream_init(cubeb * context,cubeb_stream ** stream,char const * stream_name,cubeb_devid input_device,cubeb_stream_params * input_stream_params,cubeb_devid output_device,cubeb_stream_params * output_stream_params,unsigned int latency,cubeb_data_callback data_callback,cubeb_state_callback state_callback,void * user_ptr)156 kai_stream_init(cubeb * context, cubeb_stream ** stream,
157 char const * stream_name, cubeb_devid input_device,
158 cubeb_stream_params * input_stream_params,
159 cubeb_devid output_device,
160 cubeb_stream_params * output_stream_params,
161 unsigned int latency, cubeb_data_callback data_callback,
162 cubeb_state_callback state_callback, void * user_ptr)
163 {
164 cubeb_stream * stm;
165 KAISPEC wanted_spec;
166
167 XASSERT(!input_stream_params && "not supported.");
168 if (input_device || output_device) {
169 /* Device selection not yet implemented. */
170 return CUBEB_ERROR_DEVICE_UNAVAILABLE;
171 }
172
173 if (!output_stream_params)
174 return CUBEB_ERROR_INVALID_PARAMETER;
175
176 // Loopback is unsupported
177 if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
178 return CUBEB_ERROR_NOT_SUPPORTED;
179 }
180
181 if (output_stream_params->channels < 1 ||
182 output_stream_params->channels > MAX_CHANNELS)
183 return CUBEB_ERROR_INVALID_FORMAT;
184
185 XASSERT(context);
186 XASSERT(stream);
187
188 *stream = NULL;
189
190 stm = calloc(1, sizeof(*stm));
191 XASSERT(stm);
192
193 stm->context = context;
194 stm->params = *output_stream_params;
195 stm->data_callback = data_callback;
196 stm->state_callback = state_callback;
197 stm->user_ptr = user_ptr;
198 stm->soft_volume = -1.0f;
199
200 if (_fmutex_create(&stm->mutex, 0)) {
201 free(stm);
202 return CUBEB_ERROR;
203 }
204
205 wanted_spec.usDeviceIndex = 0;
206 wanted_spec.ulType = KAIT_PLAY;
207 wanted_spec.ulBitsPerSample = BPS_16;
208 wanted_spec.ulSamplingRate = stm->params.rate;
209 wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
210 wanted_spec.ulChannels = stm->params.channels;
211 wanted_spec.ulNumBuffers = NBUFS;
212 wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params);
213 wanted_spec.fShareable = TRUE;
214 wanted_spec.pfnCallBack = kai_callback;
215 wanted_spec.pCallBackData = stm;
216
217 if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) {
218 _fmutex_close(&stm->mutex);
219 free(stm);
220 return CUBEB_ERROR;
221 }
222
223 *stream = stm;
224
225 return CUBEB_OK;
226 }
227
228 static void
kai_stream_destroy(cubeb_stream * stm)229 kai_stream_destroy(cubeb_stream * stm)
230 {
231 kaiClose(stm->hkai);
232 _fmutex_close(&stm->mutex);
233 free(stm);
234 }
235
236 static int
kai_get_max_channel_count(cubeb * ctx,uint32_t * max_channels)237 kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
238 {
239 XASSERT(ctx && max_channels);
240
241 *max_channels = MAX_CHANNELS;
242
243 return CUBEB_OK;
244 }
245
246 static int
kai_get_min_latency(cubeb * ctx,cubeb_stream_params params,uint32_t * latency)247 kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
248 {
249 /* We have at least two buffers. One is being played, the other one is being
250 filled. So there is as much latency as one buffer. */
251 *latency = FRAME_SIZE;
252
253 return CUBEB_OK;
254 }
255
256 static int
kai_get_preferred_sample_rate(cubeb * ctx,uint32_t * rate)257 kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
258 {
259 cubeb_stream_params params;
260 KAISPEC wanted_spec;
261 KAISPEC spec;
262 HKAI hkai;
263
264 params.format = CUBEB_SAMPLE_S16NE;
265 params.rate = 48000;
266 params.channels = 2;
267
268 wanted_spec.usDeviceIndex = 0;
269 wanted_spec.ulType = KAIT_PLAY;
270 wanted_spec.ulBitsPerSample = BPS_16;
271 wanted_spec.ulSamplingRate = params.rate;
272 wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
273 wanted_spec.ulChannels = params.channels;
274 wanted_spec.ulNumBuffers = NBUFS;
275 wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params);
276 wanted_spec.fShareable = TRUE;
277 wanted_spec.pfnCallBack = kai_callback;
278 wanted_spec.pCallBackData = NULL;
279
280 /* Test 48KHz */
281 if (kaiOpen(&wanted_spec, &spec, &hkai)) {
282 /* Not supported. Fall back to 44.1KHz */
283 params.rate = 44100;
284 } else {
285 /* Supported. Use 48KHz */
286 kaiClose(hkai);
287 }
288
289 *rate = params.rate;
290
291 return CUBEB_OK;
292 }
293
294 static int
kai_stream_start(cubeb_stream * stm)295 kai_stream_start(cubeb_stream * stm)
296 {
297 if (kaiPlay(stm->hkai))
298 return CUBEB_ERROR;
299
300 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
301
302 return CUBEB_OK;
303 }
304
305 static int
kai_stream_stop(cubeb_stream * stm)306 kai_stream_stop(cubeb_stream * stm)
307 {
308 if (kaiStop(stm->hkai))
309 return CUBEB_ERROR;
310
311 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
312
313 return CUBEB_OK;
314 }
315
316 static int
kai_stream_get_position(cubeb_stream * stm,uint64_t * position)317 kai_stream_get_position(cubeb_stream * stm, uint64_t * position)
318 {
319 _fmutex_request(&stm->mutex, 0);
320 *position = stm->total_frames;
321 _fmutex_release(&stm->mutex);
322
323 return CUBEB_OK;
324 }
325
326 static int
kai_stream_get_latency(cubeb_stream * stm,uint32_t * latency)327 kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
328 {
329 /* Out of buffers, one is being played, the others are being filled.
330 So there is as much latency as total buffers - 1. */
331 *latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params) *
332 (stm->spec.ulNumBuffers - 1);
333
334 return CUBEB_OK;
335 }
336
337 static int
kai_stream_set_volume(cubeb_stream * stm,float volume)338 kai_stream_set_volume(cubeb_stream * stm, float volume)
339 {
340 _fmutex_request(&stm->mutex, 0);
341 stm->soft_volume = volume;
342 _fmutex_release(&stm->mutex);
343
344 return CUBEB_OK;
345 }
346
347 static struct cubeb_ops const kai_ops = {
348 /*.init =*/kai_init,
349 /*.get_backend_id =*/kai_get_backend_id,
350 /*.get_max_channel_count=*/kai_get_max_channel_count,
351 /*.get_min_latency=*/kai_get_min_latency,
352 /*.get_preferred_sample_rate =*/kai_get_preferred_sample_rate,
353 /*.get_preferred_channel_layout =*/NULL,
354 /*.enumerate_devices =*/NULL,
355 /*.device_collection_destroy =*/NULL,
356 /*.destroy =*/kai_destroy,
357 /*.stream_init =*/kai_stream_init,
358 /*.stream_destroy =*/kai_stream_destroy,
359 /*.stream_start =*/kai_stream_start,
360 /*.stream_stop =*/kai_stream_stop,
361 /*.stream_get_position =*/kai_stream_get_position,
362 /*.stream_get_latency = */ kai_stream_get_latency,
363 /*.stream_get_input_latency = */ NULL,
364 /*.stream_set_volume =*/kai_stream_set_volume,
365 /*.stream_set_name =*/NULL,
366 /*.stream_get_current_device =*/NULL,
367 /*.stream_device_destroy =*/NULL,
368 /*.stream_register_device_changed_callback=*/NULL,
369 /*.register_device_collection_changed=*/NULL};
370