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