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   /* 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 kai_destroy(cubeb * ctx);
63 
64 /*static*/ int
kai_init(cubeb ** context,char const * context_name)65 kai_init(cubeb ** context, char const * context_name)
66 {
67   cubeb * ctx;
68 
69   XASSERT(context);
70   *context = NULL;
71 
72   if (kaiInit(KAIM_AUTO))
73     return CUBEB_ERROR;
74 
75   ctx = calloc(1, sizeof(*ctx));
76   XASSERT(ctx);
77 
78   ctx->ops = &kai_ops;
79 
80   *context = ctx;
81 
82   return CUBEB_OK;
83 }
84 
85 static char const *
kai_get_backend_id(cubeb * ctx)86 kai_get_backend_id(cubeb * ctx)
87 {
88   return "kai";
89 }
90 
91 static void
kai_destroy(cubeb * ctx)92 kai_destroy(cubeb * ctx)
93 {
94   kaiDone();
95 
96   free(ctx);
97 }
98 
99 static void
float_to_s16ne(int16_t * dst,float * src,size_t n)100 float_to_s16ne(int16_t *dst, float *src, size_t n)
101 {
102   long l;
103 
104   while (n--) {
105     l = lrintf(*src++ * 0x8000);
106     if (l > 32767)
107       l = 32767;
108     if (l < -32768)
109       l = -32768;
110     *dst++ = (int16_t)l;
111   }
112 }
113 
114 static ULONG APIENTRY
kai_callback(PVOID cbdata,PVOID buffer,ULONG len)115 kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
116 {
117   cubeb_stream * stm = cbdata;
118   void *p;
119   long wanted_frames;
120   long frames;
121   float soft_volume;
122   int elements = len / sizeof(int16_t);
123 
124   p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE
125       ? 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 kai_stream_destroy(cubeb_stream * stm);
153 
154 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)155 kai_stream_init(cubeb * context, cubeb_stream ** stream,
156                 char const * stream_name,
157                 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_reset_default_device =*/ NULL,
362   /*.stream_get_position =*/ kai_stream_get_position,
363   /*.stream_get_latency = */ kai_stream_get_latency,
364   /*.stream_get_input_latency = */ NULL,
365   /*.stream_set_volume =*/ kai_stream_set_volume,
366   /*.stream_set_name =*/ NULL,
367   /*.stream_get_current_device =*/ NULL,
368   /*.stream_device_destroy =*/ NULL,
369   /*.stream_register_device_changed_callback=*/ NULL,
370   /*.register_device_collection_changed=*/ NULL
371 };
372