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