372 lines
8.5 KiB
C
Executable File
372 lines
8.5 KiB
C
Executable File
/*
|
|
* Copyright © 2015 Mozilla Foundation
|
|
*
|
|
* This program is made available under an ISC-style license. See the
|
|
* accompanying file LICENSE for details.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <sys/fmutex.h>
|
|
|
|
#include <kai.h>
|
|
|
|
#include "cubeb/cubeb.h"
|
|
#include "cubeb-internal.h"
|
|
|
|
/* We don't support more than 2 channels in KAI */
|
|
#define MAX_CHANNELS 2
|
|
|
|
#define NBUFS 2
|
|
#define FRAME_SIZE 2048
|
|
|
|
struct cubeb_stream_item {
|
|
cubeb_stream * stream;
|
|
};
|
|
|
|
static struct cubeb_ops const kai_ops;
|
|
|
|
struct cubeb {
|
|
struct cubeb_ops const * ops;
|
|
};
|
|
|
|
struct cubeb_stream {
|
|
/* Note: Must match cubeb_stream layout in cubeb.c. */
|
|
cubeb * context;
|
|
void * user_ptr;
|
|
/**/
|
|
cubeb_stream_params params;
|
|
cubeb_data_callback data_callback;
|
|
cubeb_state_callback state_callback;
|
|
|
|
HKAI hkai;
|
|
KAISPEC spec;
|
|
uint64_t total_frames;
|
|
float soft_volume;
|
|
_fmutex mutex;
|
|
float float_buffer[FRAME_SIZE * MAX_CHANNELS];
|
|
};
|
|
|
|
static inline long
|
|
frames_to_bytes(long frames, cubeb_stream_params params)
|
|
{
|
|
return frames * 2 * params.channels; /* 2 bytes per frame */
|
|
}
|
|
|
|
static inline long
|
|
bytes_to_frames(long bytes, cubeb_stream_params params)
|
|
{
|
|
return bytes / 2 / params.channels; /* 2 bytes per frame */
|
|
}
|
|
|
|
static void kai_destroy(cubeb * ctx);
|
|
|
|
/*static*/ int
|
|
kai_init(cubeb ** context, char const * context_name)
|
|
{
|
|
cubeb * ctx;
|
|
|
|
XASSERT(context);
|
|
*context = NULL;
|
|
|
|
if (kaiInit(KAIM_AUTO))
|
|
return CUBEB_ERROR;
|
|
|
|
ctx = calloc(1, sizeof(*ctx));
|
|
XASSERT(ctx);
|
|
|
|
ctx->ops = &kai_ops;
|
|
|
|
*context = ctx;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static char const *
|
|
kai_get_backend_id(cubeb * ctx)
|
|
{
|
|
return "kai";
|
|
}
|
|
|
|
static void
|
|
kai_destroy(cubeb * ctx)
|
|
{
|
|
kaiDone();
|
|
|
|
free(ctx);
|
|
}
|
|
|
|
static void
|
|
float_to_s16ne(int16_t *dst, float *src, size_t n)
|
|
{
|
|
long l;
|
|
|
|
while (n--) {
|
|
l = lrintf(*src++ * 0x8000);
|
|
if (l > 32767)
|
|
l = 32767;
|
|
if (l < -32768)
|
|
l = -32768;
|
|
*dst++ = (int16_t)l;
|
|
}
|
|
}
|
|
|
|
static ULONG APIENTRY
|
|
kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
|
|
{
|
|
cubeb_stream * stm = cbdata;
|
|
void *p;
|
|
long wanted_frames;
|
|
long frames;
|
|
float soft_volume;
|
|
int elements = len / sizeof(int16_t);
|
|
|
|
p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE
|
|
? stm->float_buffer : buffer;
|
|
|
|
wanted_frames = bytes_to_frames(len, stm->params);
|
|
frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames);
|
|
|
|
_fmutex_request(&stm->mutex, 0);
|
|
stm->total_frames += frames;
|
|
soft_volume = stm->soft_volume;
|
|
_fmutex_release(&stm->mutex);
|
|
|
|
if (frames < wanted_frames)
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
|
|
|
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE)
|
|
float_to_s16ne(buffer, p, elements);
|
|
|
|
if (soft_volume != -1.0f) {
|
|
int16_t *b = buffer;
|
|
int i;
|
|
|
|
for (i = 0; i < elements; i++)
|
|
*b++ *= soft_volume;
|
|
}
|
|
|
|
return frames_to_bytes(frames, stm->params);
|
|
}
|
|
|
|
static void kai_stream_destroy(cubeb_stream * stm);
|
|
|
|
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)
|
|
{
|
|
cubeb_stream * stm;
|
|
KAISPEC wanted_spec;
|
|
|
|
XASSERT(!input_stream_params && "not supported.");
|
|
if (input_device || output_device) {
|
|
/* Device selection not yet implemented. */
|
|
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
|
}
|
|
|
|
if (!output_stream_params)
|
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
|
|
|
// Loopback is unsupported
|
|
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (output_stream_params->channels < 1 ||
|
|
output_stream_params->channels > MAX_CHANNELS)
|
|
return CUBEB_ERROR_INVALID_FORMAT;
|
|
|
|
XASSERT(context);
|
|
XASSERT(stream);
|
|
|
|
*stream = NULL;
|
|
|
|
stm = calloc(1, sizeof(*stm));
|
|
XASSERT(stm);
|
|
|
|
stm->context = context;
|
|
stm->params = *output_stream_params;
|
|
stm->data_callback = data_callback;
|
|
stm->state_callback = state_callback;
|
|
stm->user_ptr = user_ptr;
|
|
stm->soft_volume = -1.0f;
|
|
|
|
if (_fmutex_create(&stm->mutex, 0)) {
|
|
free(stm);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
wanted_spec.usDeviceIndex = 0;
|
|
wanted_spec.ulType = KAIT_PLAY;
|
|
wanted_spec.ulBitsPerSample = BPS_16;
|
|
wanted_spec.ulSamplingRate = stm->params.rate;
|
|
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
|
|
wanted_spec.ulChannels = stm->params.channels;
|
|
wanted_spec.ulNumBuffers = NBUFS;
|
|
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params);
|
|
wanted_spec.fShareable = TRUE;
|
|
wanted_spec.pfnCallBack = kai_callback;
|
|
wanted_spec.pCallBackData = stm;
|
|
|
|
if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) {
|
|
_fmutex_close(&stm->mutex);
|
|
free(stm);
|
|
return CUBEB_ERROR;
|
|
}
|
|
|
|
*stream = stm;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static void
|
|
kai_stream_destroy(cubeb_stream * stm)
|
|
{
|
|
kaiClose(stm->hkai);
|
|
_fmutex_close(&stm->mutex);
|
|
free(stm);
|
|
}
|
|
|
|
static int
|
|
kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
|
{
|
|
XASSERT(ctx && max_channels);
|
|
|
|
*max_channels = MAX_CHANNELS;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
|
|
{
|
|
/* We have at least two buffers. One is being played, the other one is being
|
|
filled. So there is as much latency as one buffer. */
|
|
*latency = FRAME_SIZE;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
|
{
|
|
cubeb_stream_params params;
|
|
KAISPEC wanted_spec;
|
|
KAISPEC spec;
|
|
HKAI hkai;
|
|
|
|
params.format = CUBEB_SAMPLE_S16NE;
|
|
params.rate = 48000;
|
|
params.channels = 2;
|
|
|
|
wanted_spec.usDeviceIndex = 0;
|
|
wanted_spec.ulType = KAIT_PLAY;
|
|
wanted_spec.ulBitsPerSample = BPS_16;
|
|
wanted_spec.ulSamplingRate = params.rate;
|
|
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
|
|
wanted_spec.ulChannels = params.channels;
|
|
wanted_spec.ulNumBuffers = NBUFS;
|
|
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params);
|
|
wanted_spec.fShareable = TRUE;
|
|
wanted_spec.pfnCallBack = kai_callback;
|
|
wanted_spec.pCallBackData = NULL;
|
|
|
|
/* Test 48KHz */
|
|
if (kaiOpen(&wanted_spec, &spec, &hkai)) {
|
|
/* Not supported. Fall back to 44.1KHz */
|
|
params.rate = 44100;
|
|
} else {
|
|
/* Supported. Use 48KHz */
|
|
kaiClose(hkai);
|
|
}
|
|
|
|
*rate = params.rate;
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
kai_stream_start(cubeb_stream * stm)
|
|
{
|
|
if (kaiPlay(stm->hkai))
|
|
return CUBEB_ERROR;
|
|
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
kai_stream_stop(cubeb_stream * stm)
|
|
{
|
|
if (kaiStop(stm->hkai))
|
|
return CUBEB_ERROR;
|
|
|
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
kai_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
|
{
|
|
_fmutex_request(&stm->mutex, 0);
|
|
*position = stm->total_frames;
|
|
_fmutex_release(&stm->mutex);
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
|
{
|
|
/* Out of buffers, one is being played, the others are being filled.
|
|
So there is as much latency as total buffers - 1. */
|
|
*latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params)
|
|
* (stm->spec.ulNumBuffers - 1);
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static int
|
|
kai_stream_set_volume(cubeb_stream * stm, float volume)
|
|
{
|
|
_fmutex_request(&stm->mutex, 0);
|
|
stm->soft_volume = volume;
|
|
_fmutex_release(&stm->mutex);
|
|
|
|
return CUBEB_OK;
|
|
}
|
|
|
|
static struct cubeb_ops const kai_ops = {
|
|
/*.init =*/ kai_init,
|
|
/*.get_backend_id =*/ kai_get_backend_id,
|
|
/*.get_max_channel_count=*/ kai_get_max_channel_count,
|
|
/*.get_min_latency=*/ kai_get_min_latency,
|
|
/*.get_preferred_sample_rate =*/ kai_get_preferred_sample_rate,
|
|
/*.get_preferred_channel_layout =*/ NULL,
|
|
/*.enumerate_devices =*/ NULL,
|
|
/*.device_collection_destroy =*/ NULL,
|
|
/*.destroy =*/ kai_destroy,
|
|
/*.stream_init =*/ kai_stream_init,
|
|
/*.stream_destroy =*/ kai_stream_destroy,
|
|
/*.stream_start =*/ kai_stream_start,
|
|
/*.stream_stop =*/ kai_stream_stop,
|
|
/*.stream_reset_default_device =*/ NULL,
|
|
/*.stream_get_position =*/ kai_stream_get_position,
|
|
/*.stream_get_latency = */ kai_stream_get_latency,
|
|
/*.stream_get_input_latency = */ NULL,
|
|
/*.stream_set_volume =*/ kai_stream_set_volume,
|
|
/*.stream_set_name =*/ NULL,
|
|
/*.stream_get_current_device =*/ NULL,
|
|
/*.stream_device_destroy =*/ NULL,
|
|
/*.stream_register_device_changed_callback=*/ NULL,
|
|
/*.register_device_collection_changed=*/ NULL
|
|
};
|