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