Bug 1225703 - Update in-tree libcubeb. r=padenot

This commit is contained in:
Matthew Gregan 2015-11-19 16:37:36 +13:00
parent 08b31d2445
commit 442950b2c5
19 changed files with 1498 additions and 160 deletions

View File

@ -4,3 +4,5 @@ Michael Wu <mwu@mozilla.com>
Paul Adenot <paul@paul.cx> Paul Adenot <paul@paul.cx>
David Richards <drichards@mozilla.com> David Richards <drichards@mozilla.com>
Sebastien Alaiwan <sebastien.alaiwan@gmail.com> Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
KO Myung-Hun <komh@chollian.net>
Haakon Sporsheim <haakon.sporsheim@telenordigital.com>

View File

@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system.
The cubeb git repository is: git://github.com/kinetiknz/cubeb.git The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
The git commit ID used was 588c82be50ffee59b7fab71b56e6081a5a89301c. The git commit ID used was eb30ecdf0c6b02e463a91ac93887ac08e31b360a.

View File

@ -152,6 +152,74 @@ enum {
CUBEB_ERROR_NOT_SUPPORTED = -4 /**< Optional function not implemented in current backend. */ CUBEB_ERROR_NOT_SUPPORTED = -4 /**< Optional function not implemented in current backend. */
}; };
typedef enum {
CUBEB_DEVICE_TYPE_UNKNOWN,
CUBEB_DEVICE_TYPE_INPUT,
CUBEB_DEVICE_TYPE_OUTPUT
} cubeb_device_type;
typedef enum {
CUBEB_DEVICE_STATE_DISABLED,
CUBEB_DEVICE_STATE_UNPLUGGED,
CUBEB_DEVICE_STATE_ENABLED
} cubeb_device_state;
typedef void * cubeb_devid;
typedef enum {
CUBEB_DEVICE_FMT_S16LE = 0x0010,
CUBEB_DEVICE_FMT_S16BE = 0x0020,
CUBEB_DEVICE_FMT_F32LE = 0x1000,
CUBEB_DEVICE_FMT_F32BE = 0x2000
} cubeb_device_fmt;
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
#else
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
#endif
#define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
#define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
#define CUBEB_DEVICE_FMT_ALL (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
typedef enum {
CUBEB_DEVICE_PREF_NONE = 0x00,
CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
CUBEB_DEVICE_PREF_VOICE = 0x02,
CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
CUBEB_DEVICE_PREF_ALL = 0x0F
} cubeb_device_pref;
typedef struct {
cubeb_devid devid; /* Device identifier handle */
char * device_id; /* Device identifier which might be presented in a UI */
char * friendly_name; /* Friendly device name which might be presented in a UI */
char * group_id; /* Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */
char * vendor_name; /* Optional vendor name, may be NULL */
cubeb_device_type type; /* Type of device (Input/Output) */
cubeb_device_state state; /* State of device disabled/enabled/unplugged */
cubeb_device_pref preferred;/* Preferred device */
cubeb_device_fmt format; /* Sample format supported */
cubeb_device_fmt default_format;
unsigned int max_channels; /* Channels */
unsigned int default_rate; /* Default/Preferred sample rate */
unsigned int max_rate; /* Maximum sample rate supported */
unsigned int min_rate; /* Minimum sample rate supported */
unsigned int latency_lo_ms; /* Lowest possible latency in milliseconds */
unsigned int latency_hi_ms; /* Higest possible latency in milliseconds */
} cubeb_device_info;
/** Device collection. */
typedef struct {
uint32_t count; /**< Device count in collection. */
cubeb_device_info * device[1]; /**< Array of pointers to device info. */
} cubeb_device_collection;
/** User supplied data callback. /** User supplied data callback.
@param stream @param stream
@param user_ptr @param user_ptr
@ -179,6 +247,12 @@ typedef void (* cubeb_state_callback)(cubeb_stream * stream,
* @param user */ * @param user */
typedef void (* cubeb_device_changed_callback)(void * user_ptr); typedef void (* cubeb_device_changed_callback)(void * user_ptr);
/**
* User supplied callback called when the underlying device collection changed.
* @param context
* @param user_ptr */
typedef void (* cubeb_device_collection_changed_callback)(cubeb * context, void * user_ptr);
/** Initialize an application context. This will perform any library or /** Initialize an application context. This will perform any library or
application scoped initialization. application scoped initialization.
@param context @param context
@ -337,6 +411,41 @@ int cubeb_stream_device_destroy(cubeb_stream * stream,
int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback); cubeb_device_changed_callback device_changed_callback);
/** Returns enumerated devices.
@param context
@param devtype device type to include
@param collection output collection. Must be destroyed with cubeb_device_collection_destroy
@retval CUBEB_OK in case of success
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */
int cubeb_enumerate_devices(cubeb * context,
cubeb_device_type devtype,
cubeb_device_collection ** collection);
/** Destroy a cubeb_device_collection.
@param collection collection to destroy
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
int cubeb_device_collection_destroy(cubeb_device_collection * collection);
/** Destroy a cubeb_device_info structure.
@param info pointer to device info structure
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if info is an invalid pointer */
int cubeb_device_info_destroy(cubeb_device_info * info);
/** Registers a callback which is called when the system detects
a new device or a device is removed.
@param context
@param callback a function called whenever the system device list changes.
Passing NULL allow to unregister a function
@param user_ptr pointer to user specified data which will be present in
subsequent callbacks.
@retval CUBEB_ERROR_NOT_SUPPORTED */
int cubeb_register_device_collection_changed(cubeb * context,
cubeb_device_collection_changed_callback callback,
void * user_ptr);
#if defined(__cplusplus) #if defined(__cplusplus)
} }
#endif #endif

View File

@ -8,6 +8,8 @@
#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 #define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include <stdio.h>
#include <string.h>
struct cubeb_ops { struct cubeb_ops {
int (* init)(cubeb ** context, char const * context_name); int (* init)(cubeb ** context, char const * context_name);
@ -17,6 +19,8 @@ struct cubeb_ops {
cubeb_stream_params params, cubeb_stream_params params,
uint32_t * latency_ms); uint32_t * latency_ms);
int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate); int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
int (* enumerate_devices)(cubeb * context, cubeb_device_type type,
cubeb_device_collection ** collection);
void (* destroy)(cubeb * context); void (* destroy)(cubeb * context);
int (* stream_init)(cubeb * context, cubeb_stream ** stream, char const * stream_name, int (* stream_init)(cubeb * context, cubeb_stream ** stream, char const * stream_name,
cubeb_stream_params stream_params, unsigned int latency, cubeb_stream_params stream_params, unsigned int latency,
@ -36,7 +40,6 @@ struct cubeb_ops {
cubeb_device * device); cubeb_device * device);
int (* stream_register_device_changed_callback)(cubeb_stream * stream, int (* stream_register_device_changed_callback)(cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback); cubeb_device_changed_callback device_changed_callback);
}; };
#define XASSERT(expr) do { \ #define XASSERT(expr) do { \

View File

@ -7,6 +7,7 @@
#undef NDEBUG #undef NDEBUG
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h>
#if defined(HAVE_CONFIG_H) #if defined(HAVE_CONFIG_H)
#include "config.h" #include "config.h"
#endif #endif
@ -56,6 +57,9 @@ int opensl_init(cubeb ** context, char const * context_name);
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
int audiotrack_init(cubeb ** context, char const * context_name); int audiotrack_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_KAI)
int kai_init(cubeb ** context, char const * context_name);
#endif
int int
validate_stream_params(cubeb_stream_params stream_params) validate_stream_params(cubeb_stream_params stream_params)
@ -89,12 +93,12 @@ int
cubeb_init(cubeb ** context, char const * context_name) cubeb_init(cubeb ** context, char const * context_name)
{ {
int (* init[])(cubeb **, char const *) = { int (* init[])(cubeb **, char const *) = {
#if defined(USE_PULSE)
pulse_init,
#endif
#if defined(USE_JACK) #if defined(USE_JACK)
jack_init, jack_init,
#endif #endif
#if defined(USE_PULSE)
pulse_init,
#endif
#if defined(USE_ALSA) #if defined(USE_ALSA)
alsa_init, alsa_init,
#endif #endif
@ -121,6 +125,9 @@ cubeb_init(cubeb ** context, char const * context_name)
#endif #endif
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
audiotrack_init, audiotrack_init,
#endif
#if defined(USE_KAI)
kai_init,
#endif #endif
}; };
int i; int i;
@ -356,3 +363,50 @@ int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback); return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback);
} }
int cubeb_enumerate_devices(cubeb * context,
cubeb_device_type devtype,
cubeb_device_collection ** collection)
{
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
return CUBEB_ERROR_INVALID_PARAMETER;
if (collection == NULL)
return CUBEB_ERROR_INVALID_PARAMETER;
if (!context->ops->enumerate_devices)
return CUBEB_ERROR_NOT_SUPPORTED;
return context->ops->enumerate_devices(context, devtype, collection);
}
int cubeb_device_collection_destroy(cubeb_device_collection * collection)
{
uint32_t i;
if (collection == NULL)
return CUBEB_ERROR_INVALID_PARAMETER;
for (i = 0; i < collection->count; i++)
cubeb_device_info_destroy(collection->device[i]);
free(collection);
return CUBEB_OK;
}
int cubeb_device_info_destroy(cubeb_device_info * info)
{
free(info->device_id);
free(info->friendly_name);
free(info->group_id);
free(info->vendor_name);
free(info);
return CUBEB_OK;
}
int cubeb_register_device_collection_changed(cubeb * context,
cubeb_device_collection_changed_callback callback,
void * user_ptr)
{
return CUBEB_ERROR_NOT_SUPPORTED;
}

View File

@ -963,7 +963,7 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
/* get a pcm, disabling resampling, so we get a rate the /* get a pcm, disabling resampling, so we get a rate the
* hardware/dmix/pulse/etc. supports. */ * hardware/dmix/pulse/etc. supports. */
r = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK | SND_PCM_NO_AUTO_RESAMPLE, 0); r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK | SND_PCM_NO_AUTO_RESAMPLE, 0);
if (r < 0) { if (r < 0) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1116,6 +1116,7 @@ static struct cubeb_ops const alsa_ops = {
.get_max_channel_count = alsa_get_max_channel_count, .get_max_channel_count = alsa_get_max_channel_count,
.get_min_latency = alsa_get_min_latency, .get_min_latency = alsa_get_min_latency,
.get_preferred_sample_rate = alsa_get_preferred_sample_rate, .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
.enumerate_devices = NULL,
.destroy = alsa_destroy, .destroy = alsa_destroy,
.stream_init = alsa_stream_init, .stream_init = alsa_stream_init,
.stream_destroy = alsa_stream_destroy, .stream_destroy = alsa_stream_destroy,

View File

@ -415,6 +415,7 @@ static struct cubeb_ops const audiotrack_ops = {
.get_max_channel_count = audiotrack_get_max_channel_count, .get_max_channel_count = audiotrack_get_max_channel_count,
.get_min_latency = audiotrack_get_min_latency, .get_min_latency = audiotrack_get_min_latency,
.get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
.enumerate_devices = NULL,
.destroy = audiotrack_destroy, .destroy = audiotrack_destroy,
.stream_init = audiotrack_stream_init, .stream_init = audiotrack_stream_init,
.stream_destroy = audiotrack_stream_destroy, .stream_destroy = audiotrack_stream_destroy,

View File

@ -159,7 +159,10 @@ audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
pthread_mutex_unlock(&stm->mutex); pthread_mutex_unlock(&stm->mutex);
if (stm->sample_spec.mChannelsPerFrame == 2) { if (stm->sample_spec.mChannelsPerFrame == 2) {
cubeb_pan_stereo_buffer_float((float*)buf, got, panning); if (stm->sample_spec.mFormatFlags & kAudioFormatFlagIsFloat)
cubeb_pan_stereo_buffer_float((float*)buf, got, panning);
else if (stm->sample_spec.mFormatFlags & kAudioFormatFlagIsSignedInteger)
cubeb_pan_stereo_buffer_int((short*)buf, got, panning);
} }
return noErr; return noErr;
@ -400,6 +403,28 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
} }
#endif /* !TARGET_OS_IPHONE */ #endif /* !TARGET_OS_IPHONE */
static AudioObjectID
audiounit_get_default_device_id(cubeb_device_type type)
{
AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
AudioDeviceID devid;
UInt32 size;
if (type == CUBEB_DEVICE_TYPE_OUTPUT)
adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
else if (type == CUBEB_DEVICE_TYPE_INPUT)
adr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
else
return kAudioObjectUnknown;
size = sizeof(AudioDeviceID);
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) {
return kAudioObjectUnknown;
}
return devid;
}
int int
audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{ {
@ -996,12 +1021,281 @@ int audiounit_stream_register_device_changed_callback(cubeb_stream * stream,
return CUBEB_OK; return CUBEB_OK;
} }
static OSStatus
audiounit_get_devices(AudioObjectID ** devices, uint32_t * count)
{
OSStatus ret;
UInt32 size = 0;
AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster };
ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size);
if (ret != noErr)
return ret;
*count = (uint32_t)(size / sizeof(AudioObjectID));
if (size >= sizeof(AudioObjectID)) {
if (*devices != NULL) free(*devices);
*devices = malloc(size);
memset(*devices, 0, size);
ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, (void *)*devices);
if (ret != noErr) {
free(*devices);
*devices = NULL;
}
} else {
*devices = NULL;
}
return ret;
}
static char *
audiounit_strref_to_cstr_utf8(CFStringRef strref) {
CFIndex len, size;
char * ret;
if (strref == NULL)
return NULL;
len = CFStringGetLength(strref);
size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8);
ret = malloc(size);
if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) {
free(ret);
ret = NULL;
}
return ret;
}
static uint32_t
audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)
{
AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
UInt32 size = 0;
uint32_t i, ret = 0;
adr.mSelector = kAudioDevicePropertyStreamConfiguration;
if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr && size > 0) {
AudioBufferList * list = alloca(size);
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) == noErr) {
for (i = 0; i < list->mNumberBuffers; i++)
ret += list->mBuffers[i].mNumberChannels;
}
}
return ret;
}
static void
audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope,
uint32_t * min, uint32_t * max, uint32_t * def)
{
AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
adr.mSelector = kAudioDevicePropertyNominalSampleRate;
if (AudioObjectHasProperty(devid, &adr)) {
UInt32 size = sizeof(Float64);
Float64 fvalue = 0.0;
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) == noErr)
*def = fvalue;
}
adr.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
if (AudioObjectHasProperty(devid, &adr)) {
UInt32 size = 0;
AudioValueRange range;
if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) {
uint32_t i, count = size / sizeof(AudioValueRange);
AudioValueRange * ranges = malloc(size);
range.mMinimum = 9999999999.0;
range.mMaximum = 0.0;
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges) == noErr) {
for (i = 0; i < count; i++) {
if (ranges[i].mMaximum > range.mMaximum)
range.mMaximum = ranges[i].mMaximum;
if (ranges[i].mMinimum < range.mMinimum)
range.mMinimum = ranges[i].mMinimum;
}
}
free(ranges);
}
*max = (uint32_t)range.mMaximum;
*min = (uint32_t)range.mMinimum;
} else {
*min = *max = 0;
}
}
static UInt32
audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope)
{
AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster };
UInt32 size, dev, stream = 0, offset;
AudioStreamID sid[1];
adr.mSelector = kAudioDevicePropertyLatency;
size = sizeof(UInt32);
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &dev) != noErr)
dev = 0;
adr.mSelector = kAudioDevicePropertyStreams;
size = sizeof(sid);
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, sid) == noErr) {
adr.mSelector = kAudioStreamPropertyLatency;
size = sizeof(UInt32);
AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream);
}
adr.mSelector = kAudioDevicePropertySafetyOffset;
size = sizeof(UInt32);
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr)
offset = 0;
return dev + stream + offset;
}
static cubeb_device_info *
audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type)
{
AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster };
UInt32 size, ch, latency;
cubeb_device_info * ret;
CFStringRef str = NULL;
AudioValueRange range;
if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
adr.mScope = kAudioDevicePropertyScopeOutput;
} else if (type == CUBEB_DEVICE_TYPE_INPUT) {
adr.mScope = kAudioDevicePropertyScopeInput;
} else {
return NULL;
}
if ((ch = audiounit_get_channel_count(devid, adr.mScope)) == 0)
return NULL;
ret = calloc(1, sizeof(cubeb_device_info));
size = sizeof(CFStringRef);
adr.mSelector = kAudioDevicePropertyDeviceUID;
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
ret->device_id = audiounit_strref_to_cstr_utf8(str);
ret->devid = (cubeb_devid)ret->device_id;
ret->group_id = strdup(ret->device_id);
CFRelease(str);
}
size = sizeof(CFStringRef);
adr.mSelector = kAudioObjectPropertyName;
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
UInt32 ds;
size = sizeof(UInt32);
adr.mSelector = kAudioDevicePropertyDataSource;
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds) == noErr) {
CFStringRef dsname;
AudioValueTranslation trl = { &ds, sizeof(ds), &dsname, sizeof(dsname) };
adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;
size = sizeof(AudioValueTranslation);
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl) == noErr) {
CFStringRef fullstr = CFStringCreateWithFormat(NULL, NULL,
CFSTR("%@ (%@)"), str, dsname);
CFRelease(dsname);
if (fullstr != NULL) {
CFRelease(str);
str = fullstr;
}
}
}
ret->friendly_name = audiounit_strref_to_cstr_utf8(str);
CFRelease(str);
}
size = sizeof(CFStringRef);
adr.mSelector = kAudioObjectPropertyManufacturer;
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) {
ret->vendor_name = audiounit_strref_to_cstr_utf8(str);
CFRelease(str);
}
ret->type = type;
ret->state = CUBEB_DEVICE_STATE_ENABLED;
ret->preferred = (devid == audiounit_get_default_device_id(type)) ?
CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
ret->max_channels = ch;
ret->format = CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */
/* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */
ret->default_format = CUBEB_DEVICE_FMT_F32NE;
audiounit_get_available_samplerate(devid, adr.mScope,
&ret->min_rate, &ret->max_rate, &ret->default_rate);
latency = audiounit_get_device_presentation_latency(devid, adr.mScope);
adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
size = sizeof(AudioValueRange);
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) {
ret->latency_lo_ms = ((latency + range.mMinimum) * 1000) / ret->default_rate;
ret->latency_hi_ms = ((latency + range.mMaximum) * 1000) / ret->default_rate;
} else {
ret->latency_lo_ms = 10; /* Default to 10ms */
ret->latency_hi_ms = 100; /* Default to 100ms */
}
return ret;
}
static int
audiounit_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection ** collection)
{
AudioObjectID * hwdevs = NULL;
uint32_t i, hwdevcount = 0;
OSStatus err;
if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr)
return CUBEB_ERROR;
*collection = malloc(sizeof(cubeb_device_collection) +
sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0));
(*collection)->count = 0;
if (hwdevcount > 0) {
cubeb_device_info * cur;
if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
for (i = 0; i < hwdevcount; i++) {
if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT)) != NULL)
(*collection)->device[(*collection)->count++] = cur;
}
}
if (type & CUBEB_DEVICE_TYPE_INPUT) {
for (i = 0; i < hwdevcount; i++) {
if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_INPUT)) != NULL)
(*collection)->device[(*collection)->count++] = cur;
}
}
}
free(hwdevs);
return CUBEB_OK;
}
static struct cubeb_ops const audiounit_ops = { static struct cubeb_ops const audiounit_ops = {
.init = audiounit_init, .init = audiounit_init,
.get_backend_id = audiounit_get_backend_id, .get_backend_id = audiounit_get_backend_id,
.get_max_channel_count = audiounit_get_max_channel_count, .get_max_channel_count = audiounit_get_max_channel_count,
.get_min_latency = audiounit_get_min_latency, .get_min_latency = audiounit_get_min_latency,
.get_preferred_sample_rate = audiounit_get_preferred_sample_rate, .get_preferred_sample_rate = audiounit_get_preferred_sample_rate,
.enumerate_devices = audiounit_enumerate_devices,
.destroy = audiounit_destroy, .destroy = audiounit_destroy,
.stream_init = audiounit_stream_init, .stream_init = audiounit_stream_init,
.stream_destroy = audiounit_stream_destroy, .stream_destroy = audiounit_stream_destroy,

View File

@ -817,6 +817,7 @@ static struct cubeb_ops const opensl_ops = {
.get_max_channel_count = opensl_get_max_channel_count, .get_max_channel_count = opensl_get_max_channel_count,
.get_min_latency = opensl_get_min_latency, .get_min_latency = opensl_get_min_latency,
.get_preferred_sample_rate = opensl_get_preferred_sample_rate, .get_preferred_sample_rate = opensl_get_preferred_sample_rate,
.enumerate_devices = NULL,
.destroy = opensl_destroy, .destroy = opensl_destroy,
.stream_init = opensl_stream_init, .stream_init = opensl_stream_init,
.stream_destroy = opensl_stream_destroy, .stream_destroy = opensl_stream_destroy,

View File

@ -26,6 +26,8 @@
X(pa_context_drain) \ X(pa_context_drain) \
X(pa_context_get_server_info) \ X(pa_context_get_server_info) \
X(pa_context_get_sink_info_by_name) \ X(pa_context_get_sink_info_by_name) \
X(pa_context_get_sink_info_list) \
X(pa_context_get_source_info_list) \
X(pa_context_get_state) \ X(pa_context_get_state) \
X(pa_context_new) \ X(pa_context_new) \
X(pa_context_rttime_new) \ X(pa_context_rttime_new) \
@ -37,6 +39,7 @@
X(pa_frame_size) \ X(pa_frame_size) \
X(pa_operation_get_state) \ X(pa_operation_get_state) \
X(pa_operation_unref) \ X(pa_operation_unref) \
X(pa_proplist_gets) \
X(pa_rtclock_now) \ X(pa_rtclock_now) \
X(pa_stream_begin_write) \ X(pa_stream_begin_write) \
X(pa_stream_cancel_write) \ X(pa_stream_cancel_write) \
@ -205,7 +208,7 @@ stream_request_callback(pa_stream * s, size_t nbytes, void * u)
if (stm->volume != PULSE_NO_GAIN) { if (stm->volume != PULSE_NO_GAIN) {
uint32_t samples = size * stm->sample_spec.channels / frame_size ; uint32_t samples = size * stm->sample_spec.channels / frame_size ;
if (stm->sample_spec.format == PA_SAMPLE_S16LE || if (stm->sample_spec.format == PA_SAMPLE_S16BE ||
stm->sample_spec.format == PA_SAMPLE_S16LE) { stm->sample_spec.format == PA_SAMPLE_S16LE) {
short * b = buffer; short * b = buffer;
for (uint32_t i = 0; i < samples; i++) { for (uint32_t i = 0; i < samples; i++) {
@ -724,12 +727,221 @@ pulse_stream_set_panning(cubeb_stream * stream, float panning)
return CUBEB_OK; return CUBEB_OK;
} }
typedef struct {
char * default_sink_name;
char * default_source_name;
cubeb_device_info ** devinfo;
uint32_t max;
uint32_t count;
} pulse_dev_list_data;
static cubeb_device_fmt
pulse_format_to_cubeb_format(pa_sample_format_t format)
{
switch (format) {
case PA_SAMPLE_S16LE:
return CUBEB_DEVICE_FMT_S16LE;
case PA_SAMPLE_S16BE:
return CUBEB_DEVICE_FMT_S16BE;
case PA_SAMPLE_FLOAT32LE:
return CUBEB_DEVICE_FMT_F32LE;
case PA_SAMPLE_FLOAT32BE:
return CUBEB_DEVICE_FMT_F32BE;
default:
return CUBEB_DEVICE_FMT_F32NE;
}
}
static void
pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data)
{
if (list_data->count == list_data->max) {
list_data->max += 8;
list_data->devinfo = realloc(list_data->devinfo,
sizeof(cubeb_device_info) * list_data->max);
}
}
static cubeb_device_state
pulse_get_state_from_sink_port(pa_sink_port_info * info)
{
if (info != NULL) {
#if PA_CHECK_VERSION(2, 0, 0)
if (info->available == PA_PORT_AVAILABLE_NO)
return CUBEB_DEVICE_STATE_UNPLUGGED;
else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
#endif
return CUBEB_DEVICE_STATE_ENABLED;
}
return CUBEB_DEVICE_STATE_DISABLED;
}
static void
pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
int eol, void * user_data)
{
pulse_dev_list_data * list_data = user_data;
cubeb_device_info * devinfo;
const char * prop;
(void)context;
if (eol || info == NULL)
return;
devinfo = calloc(1, sizeof(cubeb_device_info));
devinfo->device_id = strdup(info->name);
devinfo->devid = (cubeb_devid)devinfo->device_id;
devinfo->friendly_name = strdup(info->description);
prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
if (prop)
devinfo->group_id = strdup(prop);
prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
if (prop)
devinfo->vendor_name = strdup(prop);
devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
devinfo->state = pulse_get_state_from_sink_port(info->active_port);
devinfo->preferred = strcmp(info->name, list_data->default_sink_name) == 0;
devinfo->format = CUBEB_DEVICE_FMT_ALL;
devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
devinfo->max_channels = info->channel_map.channels;
devinfo->min_rate = 1;
devinfo->max_rate = PA_RATE_MAX;
devinfo->default_rate = info->sample_spec.rate;
devinfo->latency_lo_ms = 40;
devinfo->latency_hi_ms = 400;
pulse_ensure_dev_list_data_list_size (list_data);
list_data->devinfo[list_data->count++] = devinfo;
}
static cubeb_device_state
pulse_get_state_from_source_port(pa_source_port_info * info)
{
if (info != NULL) {
#if PA_CHECK_VERSION(2, 0, 0)
if (info->available == PA_PORT_AVAILABLE_NO)
return CUBEB_DEVICE_STATE_UNPLUGGED;
else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
#endif
return CUBEB_DEVICE_STATE_ENABLED;
}
return CUBEB_DEVICE_STATE_DISABLED;
}
static void
pulse_source_info_cb(pa_context * context, const pa_source_info * info,
int eol, void * user_data)
{
pulse_dev_list_data * list_data = user_data;
cubeb_device_info * devinfo;
const char * prop;
(void)context;
if (eol)
return;
devinfo = calloc(1, sizeof(cubeb_device_info));
devinfo->device_id = strdup(info->name);
devinfo->devid = (cubeb_devid)devinfo->device_id;
devinfo->friendly_name = strdup(info->description);
prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
if (prop)
devinfo->group_id = strdup(prop);
prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
if (prop)
devinfo->vendor_name = strdup(prop);
devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
devinfo->state = pulse_get_state_from_source_port(info->active_port);
devinfo->preferred = strcmp(info->name, list_data->default_source_name) == 0;
devinfo->format = CUBEB_DEVICE_FMT_ALL;
devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
devinfo->max_channels = info->channel_map.channels;
devinfo->min_rate = 1;
devinfo->max_rate = PA_RATE_MAX;
devinfo->default_rate = info->sample_spec.rate;
devinfo->latency_lo_ms = 1;
devinfo->latency_hi_ms = 10;
pulse_ensure_dev_list_data_list_size (list_data);
list_data->devinfo[list_data->count++] = devinfo;
}
static void
pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
{
pulse_dev_list_data * list_data = userdata;
(void)c;
free(list_data->default_sink_name);
free(list_data->default_source_name);
list_data->default_sink_name = strdup(i->default_sink_name);
list_data->default_source_name = strdup(i->default_source_name);
}
static int
pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection ** collection)
{
pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0 };
pa_operation * o;
uint32_t i;
o = WRAP(pa_context_get_server_info)(context->context,
pulse_server_info_cb, &user_data);
if (o) {
operation_wait(context, NULL, o);
WRAP(pa_operation_unref)(o);
}
if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
o = WRAP(pa_context_get_sink_info_list)(context->context,
pulse_sink_info_cb, &user_data);
if (o) {
operation_wait(context, NULL, o);
WRAP(pa_operation_unref)(o);
}
}
if (type & CUBEB_DEVICE_TYPE_INPUT) {
o = WRAP(pa_context_get_source_info_list)(context->context,
pulse_source_info_cb, &user_data);
if (o) {
operation_wait(context, NULL, o);
WRAP(pa_operation_unref)(o);
}
}
*collection = malloc(sizeof(cubeb_device_collection) +
sizeof(cubeb_device_info*) * (user_data.count > 0 ? user_data.count - 1 : 0));
(*collection)->count = user_data.count;
for (i = 0; i < user_data.count; i++)
(*collection)->device[i] = user_data.devinfo[i];
free(user_data.devinfo);
return CUBEB_OK;
}
static struct cubeb_ops const pulse_ops = { static struct cubeb_ops const pulse_ops = {
.init = pulse_init, .init = pulse_init,
.get_backend_id = pulse_get_backend_id, .get_backend_id = pulse_get_backend_id,
.get_max_channel_count = pulse_get_max_channel_count, .get_max_channel_count = pulse_get_max_channel_count,
.get_min_latency = pulse_get_min_latency, .get_min_latency = pulse_get_min_latency,
.get_preferred_sample_rate = pulse_get_preferred_sample_rate, .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
.enumerate_devices = pulse_enumerate_devices,
.destroy = pulse_destroy, .destroy = pulse_destroy,
.stream_init = pulse_stream_init, .stream_init = pulse_stream_init,
.stream_destroy = pulse_stream_destroy, .stream_destroy = pulse_stream_destroy,

View File

@ -356,6 +356,7 @@ static struct cubeb_ops const sndio_ops = {
.get_max_channel_count = sndio_get_max_channel_count, .get_max_channel_count = sndio_get_max_channel_count,
.get_min_latency = sndio_get_min_latency, .get_min_latency = sndio_get_min_latency,
.get_preferred_sample_rate = sndio_get_preferred_sample_rate, .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
.enumerate_devices = NULL,
.destroy = sndio_destroy, .destroy = sndio_destroy,
.stream_init = sndio_stream_init, .stream_init = sndio_stream_init,
.stream_destroy = sndio_stream_destroy, .stream_destroy = sndio_stream_destroy,

View File

@ -4,14 +4,15 @@
* This program is made available under an ISC-style license. See the * This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
// This enables assert in release, and lets us have debug-only code
#if defined(HAVE_CONFIG_H) #if defined(HAVE_CONFIG_H)
#include "config.h" #include "config.h"
#endif #endif
#include <initguid.h>
#include <windows.h> #include <windows.h>
#include <mmdeviceapi.h> #include <mmdeviceapi.h>
#include <windef.h> #include <windef.h>
#include <audioclient.h> #include <audioclient.h>
#include <devicetopology.h>
#include <process.h> #include <process.h>
#include <avrt.h> #include <avrt.h>
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
@ -22,11 +23,23 @@
#include <stdlib.h> #include <stdlib.h>
#include <cmath> #include <cmath>
/**Taken from winbase.h, Not in MinGW.*/ /* devicetopology.h missing in MinGW. */
#ifndef __devicetopology_h__
#include "cubeb_devicetopology.h"
#endif
/* Taken from winbase.h, Not in MinGW. */
#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
#endif #endif
#ifndef PKEY_Device_FriendlyName
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
#endif
#ifndef PKEY_Device_InstanceId
DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 0x00000100); // VT_LPWSTR
#endif
// #define LOGGING_ENABLED // #define LOGGING_ENABLED
#ifdef LOGGING_ENABLED #ifdef LOGGING_ENABLED
@ -48,13 +61,13 @@ ms_to_hns(uint32_t ms)
} }
uint32_t uint32_t
hns_to_ms(uint32_t hns) hns_to_ms(REFERENCE_TIME hns)
{ {
return hns / 10000; return hns / 10000;
} }
double double
hns_to_s(uint32_t hns) hns_to_s(REFERENCE_TIME hns)
{ {
return static_cast<double>(hns) / 10000000; return static_cast<double>(hns) / 10000000;
} }
@ -76,7 +89,7 @@ void SafeRelease(T * ptr)
} }
/* This wraps a critical section to track the owner in debug mode, adapted from /* This wraps a critical section to track the owner in debug mode, adapted from
* NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */ NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */
class owned_critical_section class owned_critical_section
{ {
public: public:
@ -112,7 +125,7 @@ public:
} }
/* This is guaranteed to have the good behaviour if it succeeds. The behaviour /* This is guaranteed to have the good behaviour if it succeeds. The behaviour
* is undefined otherwise. */ is undefined otherwise. */
void assert_current_thread_owns() void assert_current_thread_owns()
{ {
#ifdef DEBUG #ifdef DEBUG
@ -150,12 +163,12 @@ struct auto_com {
if (result == RPC_E_CHANGED_MODE) { if (result == RPC_E_CHANGED_MODE) {
// This is not an error, COM was not initialized by this function, so it is // This is not an error, COM was not initialized by this function, so it is
// not necessary to uninit it. // not necessary to uninit it.
LOG("COM already initialized in STA.\n"); LOG("COM was already initialized in STA.\n");
} else if (result == S_FALSE) { } else if (result == S_FALSE) {
// This is not an error. We are allowed to call CoInitializeEx more than // This is not an error. We are allowed to call CoInitializeEx more than
// once, as long as it is matches by an CoUninitialize call. // once, as long as it is matches by an CoUninitialize call.
// We do that in the dtor which is guaranteed to be called. // We do that in the dtor which is guaranteed to be called.
LOG("COM already initialized in MTA\n"); LOG("COM was already initialized in MTA\n");
} }
if (SUCCEEDED(result)) { if (SUCCEEDED(result)) {
CoUninitialize(); CoUninitialize();
@ -184,8 +197,8 @@ int setup_wasapi_stream(cubeb_stream * stm);
struct cubeb struct cubeb
{ {
cubeb_ops const * ops; cubeb_ops const * ops;
/* Library dynamically opened to increase the render /* Library dynamically opened to increase the render thread priority, and
* thread priority, and the two function pointers we need. */ the two function pointers we need. */
HMODULE mmcss_module; HMODULE mmcss_module;
set_mm_thread_characteristics_function set_mm_thread_characteristics; set_mm_thread_characteristics_function set_mm_thread_characteristics;
revert_mm_thread_characteristics_function revert_mm_thread_characteristics; revert_mm_thread_characteristics_function revert_mm_thread_characteristics;
@ -196,9 +209,9 @@ class wasapi_endpoint_notification_client;
struct cubeb_stream struct cubeb_stream
{ {
cubeb * context; cubeb * context;
/* Mixer pameters. We need to convert the input /* Mixer pameters. We need to convert the input stream to this
* stream to this samplerate/channel layout, as WASAPI samplerate/channel layout, as WASAPI * does not resample nor upmix
* does not resample nor upmix itself. */ itself. */
cubeb_stream_params mix_params; cubeb_stream_params mix_params;
cubeb_stream_params stream_params; cubeb_stream_params stream_params;
/* The latency initially requested for this stream. */ /* The latency initially requested for this stream. */
@ -208,10 +221,10 @@ struct cubeb_stream
void * user_ptr; void * user_ptr;
/* Lifetime considerations: /* Lifetime considerations:
* - client, render_client, audio_clock and audio_stream_volume are interface - client, render_client, audio_clock and audio_stream_volume are interface
* pointer to the IAudioClient. pointer to the IAudioClient.
* - The lifetime for device_enumerator and notification_client, resampler, - The lifetime for device_enumerator and notification_client, resampler,
* mix_buffer are the same as the cubeb_stream instance. */ mix_buffer are the same as the cubeb_stream instance. */
/* Main handle on the WASAPI stream. */ /* Main handle on the WASAPI stream. */
IAudioClient * client; IAudioClient * client;
@ -222,23 +235,23 @@ struct cubeb_stream
/* Interface pointer to use the stream audio clock. */ /* Interface pointer to use the stream audio clock. */
IAudioClock * audio_clock; IAudioClock * audio_clock;
/* Frames written to the stream since it was opened. Reset on device /* Frames written to the stream since it was opened. Reset on device
* change. Uses mix_params.rate. */ change. Uses mix_params.rate. */
UINT64 frames_written; UINT64 frames_written;
/* Frames written to the (logical) stream since it was first /* Frames written to the (logical) stream since it was first
* created. Updated on device change. Uses stream_params.rate. */ created. Updated on device change. Uses stream_params.rate. */
UINT64 total_frames_written; UINT64 total_frames_written;
/* Last valid reported stream position. Used to ensure the position /* Last valid reported stream position. Used to ensure the position
* reported by stream_get_position increases monotonically. */ reported by stream_get_position increases monotonically. */
UINT64 prev_position; UINT64 prev_position;
/* Device enumerator to be able to be notified when the default /* Device enumerator to be able to be notified when the default
* device change. */ device change. */
IMMDeviceEnumerator * device_enumerator; IMMDeviceEnumerator * device_enumerator;
/* Device notification client, to be able to be notified when the default /* Device notification client, to be able to be notified when the default
* audio device changes and route the audio to the new default audio output audio device changes and route the audio to the new default audio output
* device */ device */
wasapi_endpoint_notification_client * notification_client; wasapi_endpoint_notification_client * notification_client;
/* This event is set by the stream_stop and stream_destroy /* This event is set by the stream_stop and stream_destroy
* function, so the render loop can exit properly. */ function, so the render loop can exit properly. */
HANDLE shutdown_event; HANDLE shutdown_event;
/* Set by OnDefaultDeviceChanged when a stream reconfiguration is required. /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
The reconfiguration is handled by the render loop thread. */ The reconfiguration is handled by the render loop thread. */
@ -256,10 +269,10 @@ struct cubeb_stream
/* Resampler instance. Resampling will only happen if necessary. */ /* Resampler instance. Resampling will only happen if necessary. */
cubeb_resampler * resampler; cubeb_resampler * resampler;
/* Buffer used to downmix or upmix to the number of channels the mixer has. /* Buffer used to downmix or upmix to the number of channels the mixer has.
* its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */ its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */
float * mix_buffer; float * mix_buffer;
/* Stream volume. Set via stream_set_volume and used to reset volume on /* Stream volume. Set via stream_set_volume and used to reset volume on
* device changes. */ device changes. */
float volume; float volume;
/* True if the stream is draining. */ /* True if the stream is draining. */
bool draining; bool draining;
@ -307,9 +320,14 @@ public:
, reconfigure_event(event) , reconfigure_event(event)
{ } { }
virtual ~wasapi_endpoint_notification_client()
{ }
HRESULT STDMETHODCALLTYPE HRESULT STDMETHODCALLTYPE
OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)
{ {
LOG("Audio device default changed.\n");
/* we only support a single stream type for now. */ /* we only support a single stream type for now. */
if (flow != eRender && role != eMultimedia) { if (flow != eRender && role != eMultimedia) {
return S_OK; return S_OK;
@ -317,14 +335,14 @@ public:
BOOL ok = SetEvent(reconfigure_event); BOOL ok = SetEvent(reconfigure_event);
if (!ok) { if (!ok) {
LOG("SetEvent on reconfigure_event failed: %x", GetLastError()); LOG("SetEvent on reconfigure_event failed: %x\n", GetLastError());
} }
return S_OK; return S_OK;
} }
/* The remaining methods are not implemented, they simply log when called (if /* The remaining methods are not implemented, they simply log when called (if
* log is enabled), for debugging. */ log is enabled), for debugging. */
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id) HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
{ {
LOG("Audio device added.\n"); LOG("Audio device added.\n");
@ -422,8 +440,8 @@ downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channel
{ {
XASSERT(in_channels >= out_channels); XASSERT(in_channels >= out_channels);
/* We could use a downmix matrix here, applying mixing weight based on the /* We could use a downmix matrix here, applying mixing weight based on the
* channel, but directsound and winmm simply drop the channels that cannot be channel, but directsound and winmm simply drop the channels that cannot be
* rendered by the hardware, so we do the same for consistency. */ rendered by the hardware, so we do the same for consistency. */
long out_index = 0; long out_index = 0;
for (long i = 0; i < inframes * in_channels; i += in_channels) { for (long i = 0; i < inframes * in_channels; i += in_channels) {
for (int j = 0; j < out_channels; ++j) { for (int j = 0; j < out_channels; ++j) {
@ -433,8 +451,8 @@ downmix(T * in, long inframes, T * out, int32_t in_channels, int32_t out_channel
} }
} }
/* This returns the size of a frame in the stream, /* This returns the size of a frame in the stream, before the eventual upmix
* before the eventual upmix occurs. */ occurs. */
static size_t static size_t
frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames) frames_to_bytes_before_mix(cubeb_stream * stm, size_t frames)
{ {
@ -446,7 +464,7 @@ long
refill(cubeb_stream * stm, float * data, long frames_needed) refill(cubeb_stream * stm, float * data, long frames_needed)
{ {
/* If we need to upmix after resampling, resample into the mix buffer to /* If we need to upmix after resampling, resample into the mix buffer to
* avoid a copy. */ avoid a copy. */
float * dest; float * dest;
if (should_upmix(stm) || should_downmix(stm)) { if (should_upmix(stm) || should_downmix(stm)) {
dest = stm->mix_buffer; dest = stm->mix_buffer;
@ -465,12 +483,12 @@ refill(cubeb_stream * stm, float * data, long frames_needed)
/* Go in draining mode if we got fewer frames than requested. */ /* Go in draining mode if we got fewer frames than requested. */
if (out_frames < frames_needed) { if (out_frames < frames_needed) {
LOG("draining.\n"); LOG("start draining.\n");
stm->draining = true; stm->draining = true;
} }
/* If this is not true, there will be glitches. /* If this is not true, there will be glitches.
* It is alright to have produced less frames if we are draining, though. */ It is alright to have produced less frames if we are draining, though. */
XASSERT(out_frames == frames_needed || stm->draining); XASSERT(out_frames == frames_needed || stm->draining);
if (should_upmix(stm)) { if (should_upmix(stm)) {
@ -493,16 +511,16 @@ wasapi_stream_render_loop(LPVOID stream)
HANDLE wait_array[3] = {stm->shutdown_event, stm->reconfigure_event, stm->refill_event}; HANDLE wait_array[3] = {stm->shutdown_event, stm->reconfigure_event, stm->refill_event};
HANDLE mmcss_handle = NULL; HANDLE mmcss_handle = NULL;
HRESULT hr = 0; HRESULT hr = 0;
bool first = true;
DWORD mmcss_task_index = 0; DWORD mmcss_task_index = 0;
auto_com com; auto_com com;
if (!com.ok()) { if (!com.ok()) {
LOG("COM initialization failed on render_loop thread.\n"); LOG("COM initialization failed on render_loop thread.\n");
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return 0; return 0;
} }
/* We could consider using "Pro Audio" here for WebAudio and /* We could consider using "Pro Audio" here for WebAudio and
* maybe WebRTC. */ maybe WebRTC. */
mmcss_handle = mmcss_handle =
stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index); stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index);
if (!mmcss_handle) { if (!mmcss_handle) {
@ -529,7 +547,7 @@ wasapi_stream_render_loop(LPVOID stream)
case WAIT_OBJECT_0: { /* shutdown */ case WAIT_OBJECT_0: { /* shutdown */
is_playing = false; is_playing = false;
/* We don't check if the drain is actually finished here, we just want to /* We don't check if the drain is actually finished here, we just want to
* shutdown. */ shutdown. */
if (stm->draining) { if (stm->draining) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
} }
@ -542,13 +560,13 @@ wasapi_stream_render_loop(LPVOID stream)
auto_lock lock(stm->stream_reset_lock); auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm); close_wasapi_stream(stm);
/* Reopen a stream and start it immediately. This will automatically pick the /* Reopen a stream and start it immediately. This will automatically pick the
* new default device for this role. */ new default device for this role. */
int r = setup_wasapi_stream(stm); int r = setup_wasapi_stream(stm);
if (r != CUBEB_OK) { if (r != CUBEB_OK) {
/* Don't destroy the stream here, since we expect the caller to do /* Don't destroy the stream here, since we expect the caller to do
so after the error has propagated via the state callback. */ so after the error has propagated via the state callback. */
is_playing = false; is_playing = false;
hr = -1; hr = E_FAIL;
continue; continue;
} }
} }
@ -560,14 +578,12 @@ wasapi_stream_render_loop(LPVOID stream)
hr = stm->client->GetCurrentPadding(&padding); hr = stm->client->GetCurrentPadding(&padding);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Failed to get padding\n"); LOG("Failed to get padding: %x\n", hr);
is_playing = false; is_playing = false;
continue; continue;
} }
XASSERT(padding <= stm->buffer_frame_count); XASSERT(padding <= stm->buffer_frame_count);
long available = stm->buffer_frame_count - padding;
if (stm->draining) { if (stm->draining) {
if (padding == 0) { if (padding == 0) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
@ -576,6 +592,8 @@ wasapi_stream_render_loop(LPVOID stream)
continue; continue;
} }
long available = stm->buffer_frame_count - padding;
if (available == 0) { if (available == 0) {
continue; continue;
} }
@ -588,11 +606,11 @@ wasapi_stream_render_loop(LPVOID stream)
hr = stm->render_client->ReleaseBuffer(wrote, 0); hr = stm->render_client->ReleaseBuffer(wrote, 0);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("failed to release buffer.\n"); LOG("failed to release buffer: %x\n", hr);
is_playing = false; is_playing = false;
} }
} else { } else {
LOG("failed to get buffer.\n"); LOG("failed to get buffer: %x\n", hr);
is_playing = false; is_playing = false;
} }
} }
@ -601,7 +619,7 @@ wasapi_stream_render_loop(LPVOID stream)
XASSERT(stm->shutdown_event == wait_array[0]); XASSERT(stm->shutdown_event == wait_array[0]);
if (++timeout_count >= timeout_limit) { if (++timeout_count >= timeout_limit) {
is_playing = false; is_playing = false;
hr = -1; hr = E_FAIL;
} }
break; break;
default: default:
@ -636,7 +654,6 @@ HRESULT register_notification_client(cubeb_stream * stm)
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
NULL, CLSCTX_INPROC_SERVER, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&stm->device_enumerator)); IID_PPV_ARGS(&stm->device_enumerator));
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get device enumerator: %x\n", hr); LOG("Could not get device enumerator: %x\n", hr);
return hr; return hr;
@ -645,7 +662,6 @@ HRESULT register_notification_client(cubeb_stream * stm)
stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event); stm->notification_client = new wasapi_endpoint_notification_client(stm->reconfigure_event);
hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client); hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not register endpoint notification callback: %x\n", hr); LOG("Could not register endpoint notification callback: %x\n", hr);
return hr; return hr;
@ -677,16 +693,16 @@ HRESULT get_default_endpoint(IMMDevice ** device)
NULL, CLSCTX_INPROC_SERVER, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&enumerator)); IID_PPV_ARGS(&enumerator));
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get device enumerator.\n"); LOG("Could not get device enumerator: %x\n", hr);
return hr; return hr;
} }
/* eMultimedia is okay for now ("Music, movies, narration, [...]"). /* eMultimedia is okay for now ("Music, movies, narration, [...]").
* We will need to change this when we distinguish streams by use-case, other We will need to change this when we distinguish streams by use-case, other
* possible values being eConsole ("Games, system notification sounds [...]") possible values being eConsole ("Games, system notification sounds [...]")
* and eCommunication ("Voice communication"). */ and eCommunication ("Voice communication"). */
hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, device); hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, device);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get default audio endpoint. %d\n", __LINE__); LOG("Could not get default audio endpoint: %x\n", hr);
SafeRelease(enumerator); SafeRelease(enumerator);
return hr; return hr;
} }
@ -781,12 +797,15 @@ int wasapi_init(cubeb ** context, char const * context_name)
IMMDevice * device; IMMDevice * device;
hr = get_default_endpoint(&device); hr = get_default_endpoint(&device);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get device.\n"); LOG("Could not get device: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
SafeRelease(device); SafeRelease(device);
cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb)); cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb));
if (!ctx) {
return CUBEB_ERROR;
}
ctx->ops = &wasapi_ops; ctx->ops = &wasapi_ops;
@ -906,10 +925,14 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten
return CUBEB_ERROR; return CUBEB_ERROR;
} }
if (params.format != CUBEB_SAMPLE_FLOAT32NE) {
return CUBEB_ERROR_INVALID_FORMAT;
}
IMMDevice * device; IMMDevice * device;
hr = get_default_endpoint(&device); hr = get_default_endpoint(&device);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get default endpoint:%x.\n", hr); LOG("Could not get default endpoint: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -918,7 +941,7 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten
NULL, (void **)&client); NULL, (void **)&client);
SafeRelease(device); SafeRelease(device);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not activate device for latency: %x.\n", hr); LOG("Could not activate device for latency: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -926,15 +949,15 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten
hr = client->GetDevicePeriod(&default_period, NULL); hr = client->GetDevicePeriod(&default_period, NULL);
if (FAILED(hr)) { if (FAILED(hr)) {
SafeRelease(client); SafeRelease(client);
LOG("Could not get device period: %x.\n", hr); LOG("Could not get device period: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
LOG("default device period: %ld\n", default_period); LOG("default device period: %ld\n", default_period);
/* According to the docs, the best latency we can achieve is by synchronizing /* According to the docs, the best latency we can achieve is by synchronizing
* the stream and the engine. the stream and the engine.
* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */
*latency_ms = hns_to_ms(default_period); *latency_ms = hns_to_ms(default_period);
SafeRelease(client); SafeRelease(client);
@ -983,22 +1006,22 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
void wasapi_stream_destroy(cubeb_stream * stm); void wasapi_stream_destroy(cubeb_stream * stm);
/* Based on the mix format and the stream format, try to find a way to play what /* Based on the mix format and the stream format, try to find a way to play
* the user requested. */ what the user requested. */
static void static void
handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params) handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cubeb_stream_params * stream_params)
{ {
/* Common case: the hardware is stereo. Up-mixing and down-mixing will be /* Common case: the hardware is stereo. Up-mixing and down-mixing will be
* handled in the callback. */ handled in the callback. */
if ((*mix_format)->nChannels <= 2) { if ((*mix_format)->nChannels <= 2) {
return; return;
} }
/* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1],
* so the reinterpret_cast below should be safe. In practice, this is not so the reinterpret_cast below should be safe. In practice, this is not
* true, and we just want to bail out and let the rest of the code find a good true, and we just want to bail out and let the rest of the code find a good
* conversion path instead of trying to make WASAPI do it by itself. conversion path instead of trying to make WASAPI do it by itself.
* [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/ [1]: http://msdn.microsoft.com/en-us/library/windows/desktop/dd370811%28v=vs.85%29.aspx*/
if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) {
return; return;
} }
@ -1009,7 +1032,7 @@ handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cub
WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm; WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm;
/* The hardware is in surround mode, we want to only use front left and front /* The hardware is in surround mode, we want to only use front left and front
* right. Try that, and check if it works. */ right. Try that, and check if it works. */
switch (stream_params->channels) { switch (stream_params->channels) {
case 1: /* Mono */ case 1: /* Mono */
format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO; format_pcm->dwChannelMask = KSAUDIO_SPEAKER_MONO;
@ -1036,7 +1059,7 @@ handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cub
if (hr == S_FALSE) { if (hr == S_FALSE) {
/* Not supported, but WASAPI gives us a suggestion. Use it, and handle the /* Not supported, but WASAPI gives us a suggestion. Use it, and handle the
* eventual upmix/downmix ourselves */ eventual upmix/downmix ourselves */
LOG("Using WASAPI suggested format: channels: %d\n", closest->nChannels); LOG("Using WASAPI suggested format: channels: %d\n", closest->nChannels);
WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest); WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat); XASSERT(closest_pcm->SubFormat == format_pcm->SubFormat);
@ -1044,11 +1067,13 @@ handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cub
*mix_format = closest; *mix_format = closest;
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
/* Not supported, no suggestion. This should not happen, but it does in the /* Not supported, no suggestion. This should not happen, but it does in the
* field with some sound cards. We restore the mix format, and let the rest field with some sound cards. We restore the mix format, and let the rest
* of the code figure out the right conversion path. */ of the code figure out the right conversion path. */
*reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format) = hw_mix_format; *reinterpret_cast<WAVEFORMATEXTENSIBLE *>(*mix_format) = hw_mix_format;
} else if (hr == S_OK) { } else if (hr == S_OK) {
LOG("Requested format accepted by WASAPI.\n"); LOG("Requested format accepted by WASAPI.\n");
} else {
LOG("IsFormatSupported unhandled error: %x\n", hr);
} }
} }
@ -1074,7 +1099,7 @@ int setup_wasapi_stream(cubeb_stream * stm)
} }
/* Get a client. We will get all other interfaces we need from /* Get a client. We will get all other interfaces we need from
* this pointer. */ this pointer. */
hr = device->Activate(__uuidof(IAudioClient), hr = device->Activate(__uuidof(IAudioClient),
CLSCTX_INPROC_SERVER, CLSCTX_INPROC_SERVER,
NULL, (void **)&stm->client); NULL, (void **)&stm->client);
@ -1085,7 +1110,7 @@ int setup_wasapi_stream(cubeb_stream * stm)
} }
/* We have to distinguish between the format the mixer uses, /* We have to distinguish between the format the mixer uses,
* and the format the stream we want to play uses. */ and the format the stream we want to play uses. */
hr = stm->client->GetMixFormat(&mix_format); hr = stm->client->GetMixFormat(&mix_format);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not fetch current mix format from the audio client: error: %x\n", hr); LOG("Could not fetch current mix format from the audio client: error: %x\n", hr);
@ -1095,7 +1120,7 @@ int setup_wasapi_stream(cubeb_stream * stm)
handle_channel_layout(stm, &mix_format, &stm->stream_params); handle_channel_layout(stm, &mix_format, &stm->stream_params);
/* Shared mode WASAPI always supports float32 sample format, so this /* Shared mode WASAPI always supports float32 sample format, so this
* is safe. */ is safe. */
stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE; stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE;
stm->mix_params.rate = mix_format->nSamplesPerSec; stm->mix_params.rate = mix_format->nSamplesPerSec;
stm->mix_params.channels = mix_format->nChannels; stm->mix_params.channels = mix_format->nChannels;
@ -1107,17 +1132,15 @@ int setup_wasapi_stream(cubeb_stream * stm)
0, 0,
mix_format, mix_format,
NULL); NULL);
CoTaskMemFree(mix_format); CoTaskMemFree(mix_format);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Unable to initialize audio client: %x.\n", hr); LOG("Unable to initialize audio client: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
hr = stm->client->GetBufferSize(&stm->buffer_frame_count); hr = stm->client->GetBufferSize(&stm->buffer_frame_count);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get the buffer size from the client %x.\n", hr); LOG("Could not get the buffer size from the client: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1127,21 +1150,21 @@ int setup_wasapi_stream(cubeb_stream * stm)
hr = stm->client->SetEventHandle(stm->refill_event); hr = stm->client->SetEventHandle(stm->refill_event);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could set the event handle for the client %x.\n", hr); LOG("Could set the event handle for the client: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
hr = stm->client->GetService(__uuidof(IAudioRenderClient), hr = stm->client->GetService(__uuidof(IAudioRenderClient),
(void **)&stm->render_client); (void **)&stm->render_client);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get the render client %x.\n", hr); LOG("Could not get the render client: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
hr = stm->client->GetService(__uuidof(IAudioStreamVolume), hr = stm->client->GetService(__uuidof(IAudioStreamVolume),
(void **)&stm->audio_stream_volume); (void **)&stm->audio_stream_volume);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get the IAudioStreamVolume %x.\n", hr); LOG("Could not get the IAudioStreamVolume: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1149,7 +1172,7 @@ int setup_wasapi_stream(cubeb_stream * stm)
hr = stm->client->GetService(__uuidof(IAudioClock), hr = stm->client->GetService(__uuidof(IAudioClock),
(void **)&stm->audio_clock); (void **)&stm->audio_clock);
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Could not get the IAudioClock %x.\n", hr); LOG("Could not get the IAudioClock: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1159,9 +1182,9 @@ int setup_wasapi_stream(cubeb_stream * stm)
} }
/* If we are playing a mono stream, we only resample one channel, /* If we are playing a mono stream, we only resample one channel,
* and copy it over, so we are always resampling the number and copy it over, so we are always resampling the number
* of channels of the stream, not the number of channels of channels of the stream, not the number of channels
* that WASAPI wants. */ that WASAPI wants. */
stm->resampler = cubeb_resampler_create(stm, stm->stream_params, stm->resampler = cubeb_resampler_create(stm, stm->stream_params,
stm->mix_params.rate, stm->mix_params.rate,
stm->data_callback, stm->data_callback,
@ -1191,6 +1214,10 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
XASSERT(context && stream); XASSERT(context && stream);
if (stream_params.format != CUBEB_SAMPLE_FLOAT32NE) {
return CUBEB_ERROR_INVALID_FORMAT;
}
cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream)); cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
XASSERT(stm); XASSERT(stm);
@ -1235,7 +1262,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
hr = register_notification_client(stm); hr = register_notification_client(stm);
if (FAILED(hr)) { if (FAILED(hr)) {
/* this is not fatal, we can still play audio, but we won't be able /* this is not fatal, we can still play audio, but we won't be able
* to keep using the default audio endpoint if it changes. */ to keep using the default audio endpoint if it changes. */
LOG("failed to register notification client, %x\n", hr); LOG("failed to register notification client, %x\n", hr);
} }
@ -1325,7 +1352,7 @@ int wasapi_stream_start(cubeb_stream * stm)
LOG("could not start the stream after reconfig: %x\n", hr); LOG("could not start the stream after reconfig: %x\n", hr);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
} else if (FAILED(hr)) { } else if (FAILED(hr)) {
LOG("could not start the stream.\n"); LOG("could not start the stream.\n");
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1402,13 +1429,16 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
auto_lock lock(stm->stream_reset_lock); auto_lock lock(stm->stream_reset_lock);
/* The GetStreamLatency method only works if the /* The GetStreamLatency method only works if the
* AudioClient has been initialized. */ AudioClient has been initialized. */
if (!stm->client) { if (!stm->client) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
REFERENCE_TIME latency_hns; REFERENCE_TIME latency_hns;
stm->client->GetStreamLatency(&latency_hns); HRESULT hr = stm->client->GetStreamLatency(&latency_hns);
if (FAILED(hr)) {
return CUBEB_ERROR;
}
double latency_s = hns_to_s(latency_hns); double latency_s = hns_to_s(latency_hns);
*latency = static_cast<uint32_t>(latency_s * stm->stream_params.rate); *latency = static_cast<uint32_t>(latency_s * stm->stream_params.rate);
@ -1428,12 +1458,245 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK; return CUBEB_OK;
} }
static char *
wstr_to_utf8(LPCWSTR str)
{
char * ret = NULL;
int size;
size = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, 0, NULL, NULL);
if (size > 0) {
ret = (char *) malloc(size);
::WideCharToMultiByte(CP_UTF8, 0, str, -1, ret, size, NULL, NULL);
}
return ret;
}
static IMMDevice *
wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{
IMMDevice * ret = NULL;
IDeviceTopology * devtopo = NULL;
IConnector * connector = NULL;
if (SUCCEEDED(dev->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&devtopo)) &&
SUCCEEDED(devtopo->GetConnector(0, &connector))) {
LPWSTR filterid;
if (SUCCEEDED(connector->GetDeviceIdConnectedTo(&filterid))) {
if (FAILED(enumerator->GetDevice(filterid, &ret)))
ret = NULL;
CoTaskMemFree(filterid);
}
}
SafeRelease(connector);
SafeRelease(devtopo);
return ret;
}
static BOOL
wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id,
IMMDeviceEnumerator * enumerator)
{
BOOL ret = FALSE;
IMMDevice * dev;
HRESULT hr;
hr = enumerator->GetDefaultAudioEndpoint(flow, role, &dev);
if (SUCCEEDED(hr)) {
LPWSTR defdevid = NULL;
if (SUCCEEDED(dev->GetId(&defdevid)))
ret = (wcscmp(defdevid, device_id) == 0);
if (defdevid != NULL)
CoTaskMemFree(defdevid);
SafeRelease(dev);
}
return ret;
}
static cubeb_device_info *
wasapi_create_device(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{
IMMEndpoint * endpoint = NULL;
IMMDevice * devnode;
IAudioClient * client = NULL;
cubeb_device_info * ret = NULL;
EDataFlow flow;
LPWSTR device_id = NULL;
DWORD state = DEVICE_STATE_NOTPRESENT;
IPropertyStore * propstore = NULL;
PROPVARIANT propvar;
REFERENCE_TIME def_period, min_period;
HRESULT hr;
PropVariantInit(&propvar);
hr = dev->QueryInterface(IID_PPV_ARGS(&endpoint));
if (FAILED(hr)) goto done;
hr = endpoint->GetDataFlow(&flow);
if (FAILED(hr)) goto done;
hr = dev->GetId(&device_id);
if (FAILED(hr)) goto done;
hr = dev->OpenPropertyStore(STGM_READ, &propstore);
if (FAILED(hr)) goto done;
hr = dev->GetState(&state);
if (FAILED(hr)) goto done;
ret = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
ret->devid = ret->device_id = wstr_to_utf8(device_id);
hr = propstore->GetValue(PKEY_Device_FriendlyName, &propvar);
if (SUCCEEDED(hr))
ret->friendly_name = wstr_to_utf8(propvar.pwszVal);
devnode = wasapi_get_device_node(enumerator, dev);
if (devnode != NULL) {
IPropertyStore * ps = NULL;
hr = devnode->OpenPropertyStore(STGM_READ, &ps);
if (FAILED(hr)) goto done;
PropVariantClear(&propvar);
hr = ps->GetValue(PKEY_Device_InstanceId, &propvar);
if (SUCCEEDED(hr)) {
ret->group_id = wstr_to_utf8(propvar.pwszVal);
}
SafeRelease(ps);
}
ret->preferred = CUBEB_DEVICE_PREF_NONE;
if (wasapi_is_default_device(flow, eMultimedia, device_id, enumerator))
ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA);
if (wasapi_is_default_device(flow, eCommunications, device_id, enumerator))
ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE);
if (wasapi_is_default_device(flow, eConsole, device_id, enumerator))
ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION);
if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT;
switch (state) {
case DEVICE_STATE_ACTIVE:
ret->state = CUBEB_DEVICE_STATE_ENABLED;
break;
case DEVICE_STATE_UNPLUGGED:
ret->state = CUBEB_DEVICE_STATE_UNPLUGGED;
break;
default:
ret->state = CUBEB_DEVICE_STATE_DISABLED;
break;
};
ret->format = CUBEB_DEVICE_FMT_F32NE; /* cubeb only supports 32bit float at the moment */
ret->default_format = CUBEB_DEVICE_FMT_F32NE;
PropVariantClear(&propvar);
hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &propvar);
if (SUCCEEDED(hr) && propvar.vt == VT_BLOB) {
if (propvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) {
const PCMWAVEFORMAT * pcm = reinterpret_cast<const PCMWAVEFORMAT *>(propvar.blob.pBlobData);
ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec;
ret->max_channels = pcm->wf.nChannels;
} else if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX)) {
WAVEFORMATEX* wfx = reinterpret_cast<WAVEFORMATEX*>(propvar.blob.pBlobData);
if (propvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize ||
wfx->wFormatTag == WAVE_FORMAT_PCM) {
ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec;
ret->max_channels = wfx->nChannels;
}
}
}
if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, (void**)&client)) &&
SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) {
ret->latency_lo_ms = hns_to_ms(min_period);
ret->latency_hi_ms = hns_to_ms(def_period);
} else {
ret->latency_lo_ms = 0;
ret->latency_hi_ms = 0;
}
SafeRelease(client);
done:
SafeRelease(devnode);
SafeRelease(endpoint);
SafeRelease(propstore);
if (device_id != NULL)
CoTaskMemFree(device_id);
PropVariantClear(&propvar);
return ret;
}
static int
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection ** out)
{
auto_com com;
IMMDeviceEnumerator * enumerator;
IMMDeviceCollection * collection;
IMMDevice * dev;
cubeb_device_info * cur;
HRESULT hr;
UINT cc, i;
EDataFlow flow;
*out = NULL;
if (!com.ok())
return CUBEB_ERROR;
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator));
if (FAILED(hr)) {
LOG("Could not get device enumerator: %x\n", hr);
return CUBEB_ERROR;
}
if (type == CUBEB_DEVICE_TYPE_OUTPUT) flow = eRender;
else if (type == CUBEB_DEVICE_TYPE_INPUT) flow = eCapture;
else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_INPUT)) flow = eAll;
else return CUBEB_ERROR;
hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL, &collection);
if (FAILED(hr)) {
LOG("Could not enumerate audio endpoints: %x\n", hr);
return CUBEB_ERROR;
}
hr = collection->GetCount(&cc);
if (FAILED(hr)) {
LOG("IMMDeviceCollection::GetCount() failed: %x\n", hr);
return CUBEB_ERROR;
}
*out = (cubeb_device_collection *) malloc(sizeof(cubeb_device_collection) +
sizeof(cubeb_device_info*) * (cc > 0 ? cc - 1 : 0));
(*out)->count = 0;
for (i = 0; i < cc; i++) {
hr = collection->Item(i, &dev);
if (FAILED(hr)) {
LOG("IMMDeviceCollection::Item(%u) failed: %x\n", i-1, hr);
} else if ((cur = wasapi_create_device(enumerator, dev)) != NULL) {
(*out)->device[(*out)->count++] = cur;
}
}
SafeRelease(collection);
SafeRelease(enumerator);
return CUBEB_OK;
}
cubeb_ops const wasapi_ops = { cubeb_ops const wasapi_ops = {
/*.init =*/ wasapi_init, /*.init =*/ wasapi_init,
/*.get_backend_id =*/ wasapi_get_backend_id, /*.get_backend_id =*/ wasapi_get_backend_id,
/*.get_max_channel_count =*/ wasapi_get_max_channel_count, /*.get_max_channel_count =*/ wasapi_get_max_channel_count,
/*.get_min_latency =*/ wasapi_get_min_latency, /*.get_min_latency =*/ wasapi_get_min_latency,
/*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate,
/*.enumerate_devices =*/ wasapi_enumerate_devices,
/*.destroy =*/ wasapi_destroy, /*.destroy =*/ wasapi_destroy,
/*.stream_init =*/ wasapi_stream_init, /*.stream_init =*/ wasapi_stream_init,
/*.stream_destroy =*/ wasapi_stream_destroy, /*.stream_destroy =*/ wasapi_stream_destroy,

View File

@ -8,7 +8,6 @@
#undef WINVER #undef WINVER
#define WINVER 0x0501 #define WINVER 0x0501
#undef WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <malloc.h> #include <malloc.h>
#include <windows.h> #include <windows.h>
@ -27,15 +26,46 @@
#endif #endif
/**This is also missing from the MinGW headers. It also appears to be undocumented by Microsoft.*/ /**This is also missing from the MinGW headers. It also appears to be undocumented by Microsoft.*/
#ifndef WAVE_FORMAT_48M08
#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
#endif
#ifndef WAVE_FORMAT_48M16
#define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */
#endif
#ifndef WAVE_FORMAT_48S08
#define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */
#endif
#ifndef WAVE_FORMAT_48S16 #ifndef WAVE_FORMAT_48S16
#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */ #define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
#endif #endif
#ifndef WAVE_FORMAT_96M08
#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
#endif
#ifndef WAVE_FORMAT_96M16
#define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */
#endif
#ifndef WAVE_FORMAT_96S08
#define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */
#endif
#ifndef WAVE_FORMAT_96S16
#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
#endif
/**Taken from winbase.h, also not in MinGW.*/ /**Taken from winbase.h, also not in MinGW.*/
#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
#endif #endif
#ifndef DRVM_MAPPER
#define DRVM_MAPPER (0x2000)
#endif
#ifndef DRVM_MAPPER_PREFERRED_GET
#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER+21)
#endif
#ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER+23)
#endif
#define CUBEB_STREAM_MAX 32 #define CUBEB_STREAM_MAX 32
#define NBUFS 4 #define NBUFS 4
@ -671,12 +701,307 @@ winmm_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK; return CUBEB_OK;
} }
#define MM_11025HZ_MASK (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16)
#define MM_22050HZ_MASK (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16)
#define MM_44100HZ_MASK (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16)
#define MM_48000HZ_MASK (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16)
#define MM_96000HZ_MASK (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16)
static void
winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
{
if (formats & MM_11025HZ_MASK) {
info->min_rate = 11025;
info->default_rate = 11025;
info->max_rate = 11025;
}
if (formats & MM_22050HZ_MASK) {
if (info->min_rate == 0) info->min_rate = 22050;
info->max_rate = 22050;
info->default_rate = 22050;
}
if (formats & MM_44100HZ_MASK) {
if (info->min_rate == 0) info->min_rate = 44100;
info->max_rate = 44100;
info->default_rate = 44100;
}
if (formats & MM_48000HZ_MASK) {
if (info->min_rate == 0) info->min_rate = 48000;
info->max_rate = 48000;
info->default_rate = 48000;
}
if (formats & MM_96000HZ_MASK) {
if (info->min_rate == 0) {
info->min_rate = 96000;
info->default_rate = 96000;
}
info->max_rate = 96000;
}
}
#define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \
WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
static int
winmm_query_supported_formats(UINT devid, DWORD formats,
cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt)
{
WAVEFORMATEXTENSIBLE wfx;
if (formats & MM_S16_MASK)
*deffmt = *supfmt = CUBEB_DEVICE_FMT_S16LE;
else
*deffmt = *supfmt = 0;
ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfx.Format.nChannels = 2;
wfx.Format.nSamplesPerSec = 44100;
wfx.Format.wBitsPerSample = 32;
wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
wfx.Format.cbSize = 22;
wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR)
*supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE);
return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR;
}
static char *
guid_to_cstr(LPGUID guid)
{
char * ret = malloc(sizeof(char) * 40);
_snprintf(ret, sizeof(char) * 40,
"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
guid->Data1, guid->Data2, guid->Data3,
guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
return ret;
}
static cubeb_device_pref
winmm_query_preferred_out_device(UINT devid)
{
DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
(DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == mmpref)
ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
if (waveOutMessage((HWAVEOUT)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
(DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == compref)
ret |= CUBEB_DEVICE_PREF_VOICE;
return ret;
}
static char *
device_id_idx(UINT devid)
{
char * ret = (char *)malloc(sizeof(char)*16);
_snprintf(ret, 16, "%u", devid);
return ret;
}
static cubeb_device_info *
winmm_create_device_from_outcaps2(LPWAVEOUTCAPS2A caps, UINT devid)
{
cubeb_device_info * ret;
ret = calloc(1, sizeof(cubeb_device_info));
ret->devid = (cubeb_devid)(size_t)devid;
ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname);
ret->group_id = guid_to_cstr(&caps->ProductGuid);
ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
ret->state = CUBEB_DEVICE_STATE_ENABLED;
ret->preferred = winmm_query_preferred_out_device(devid);
ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats,
&ret->format, &ret->default_format);
/* Hardcoed latency estimates... */
ret->latency_lo_ms = 100;
ret->latency_hi_ms = 200;
return ret;
}
static cubeb_device_info *
winmm_create_device_from_outcaps(LPWAVEOUTCAPSA caps, UINT devid)
{
cubeb_device_info * ret;
ret = calloc(1, sizeof(cubeb_device_info));
ret->devid = (cubeb_devid)(size_t)devid;
ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname);
ret->group_id = NULL;
ret->vendor_name = NULL;
ret->type = CUBEB_DEVICE_TYPE_OUTPUT;
ret->state = CUBEB_DEVICE_STATE_ENABLED;
ret->preferred = winmm_query_preferred_out_device(devid);
ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats,
&ret->format, &ret->default_format);
/* Hardcoed latency estimates... */
ret->latency_lo_ms = 100;
ret->latency_hi_ms = 200;
return ret;
}
static cubeb_device_pref
winmm_query_preferred_in_device(UINT devid)
{
DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
(DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == mmpref)
ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
if (waveInMessage((HWAVEIN)(size_t)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
(DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == compref)
ret |= CUBEB_DEVICE_PREF_VOICE;
return ret;
}
static cubeb_device_info *
winmm_create_device_from_incaps2(LPWAVEINCAPS2A caps, UINT devid)
{
cubeb_device_info * ret;
ret = calloc(1, sizeof(cubeb_device_info));
ret->devid = (cubeb_devid)(size_t)devid;
ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname);
ret->group_id = guid_to_cstr(&caps->ProductGuid);
ret->vendor_name = guid_to_cstr(&caps->ManufacturerGuid);
ret->type = CUBEB_DEVICE_TYPE_INPUT;
ret->state = CUBEB_DEVICE_STATE_ENABLED;
ret->preferred = winmm_query_preferred_in_device(devid);
ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats,
&ret->format, &ret->default_format);
/* Hardcoed latency estimates... */
ret->latency_lo_ms = 100;
ret->latency_hi_ms = 200;
return ret;
}
static cubeb_device_info *
winmm_create_device_from_incaps(LPWAVEINCAPSA caps, UINT devid)
{
cubeb_device_info * ret;
ret = calloc(1, sizeof(cubeb_device_info));
ret->devid = (cubeb_devid)(size_t)devid;
ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname);
ret->group_id = NULL;
ret->vendor_name = NULL;
ret->type = CUBEB_DEVICE_TYPE_INPUT;
ret->state = CUBEB_DEVICE_STATE_ENABLED;
ret->preferred = winmm_query_preferred_in_device(devid);
ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats,
&ret->format, &ret->default_format);
/* Hardcoed latency estimates... */
ret->latency_lo_ms = 100;
ret->latency_hi_ms = 200;
return ret;
}
static int
winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection ** collection)
{
UINT i, incount, outcount, total;
cubeb_device_info * cur;
outcount = waveOutGetNumDevs();
incount = waveInGetNumDevs();
total = outcount + incount;
if (total > 0) {
total -= 1;
}
*collection = malloc(sizeof(cubeb_device_collection) +
sizeof(cubeb_device_info*) * total);
(*collection)->count = 0;
if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
WAVEOUTCAPSA woc;
WAVEOUTCAPS2A woc2;
ZeroMemory(&woc, sizeof(woc));
ZeroMemory(&woc2, sizeof(woc2));
for (i = 0; i < outcount; i++) {
if ((waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR &&
(cur = winmm_create_device_from_outcaps2(&woc2, i)) != NULL) ||
(waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR &&
(cur = winmm_create_device_from_outcaps(&woc, i)) != NULL)
) {
(*collection)->device[(*collection)->count++] = cur;
}
}
}
if (type & CUBEB_DEVICE_TYPE_INPUT) {
WAVEINCAPSA wic;
WAVEINCAPS2A wic2;
ZeroMemory(&wic, sizeof(wic));
ZeroMemory(&wic2, sizeof(wic2));
for (i = 0; i < incount; i++) {
if ((waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR &&
(cur = winmm_create_device_from_incaps2(&wic2, i)) != NULL) ||
(waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR &&
(cur = winmm_create_device_from_incaps(&wic, i)) != NULL)
) {
(*collection)->device[(*collection)->count++] = cur;
}
}
}
return CUBEB_OK;
}
static struct cubeb_ops const winmm_ops = { static struct cubeb_ops const winmm_ops = {
/*.init =*/ winmm_init, /*.init =*/ winmm_init,
/*.get_backend_id =*/ winmm_get_backend_id, /*.get_backend_id =*/ winmm_get_backend_id,
/*.get_max_channel_count=*/ winmm_get_max_channel_count, /*.get_max_channel_count=*/ winmm_get_max_channel_count,
/*.get_min_latency=*/ winmm_get_min_latency, /*.get_min_latency=*/ winmm_get_min_latency,
/*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate,
/*.enumerate_devices =*/ winmm_enumerate_devices,
/*.destroy =*/ winmm_destroy, /*.destroy =*/ winmm_destroy,
/*.stream_init =*/ winmm_stream_init, /*.stream_init =*/ winmm_stream_init,
/*.stream_destroy =*/ winmm_stream_destroy, /*.stream_destroy =*/ winmm_stream_destroy,

View File

@ -27,3 +27,4 @@ void delay(unsigned int ms)
#if !defined(M_PI) #if !defined(M_PI)
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
#endif #endif

View File

@ -4,6 +4,8 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
DEFINES['CUBEB_GECKO_BUILD'] = True
GeckoCppUnitTests([ GeckoCppUnitTests([
'test_tone' 'test_tone'
]) ])

View File

@ -10,7 +10,7 @@
#ifdef NDEBUG #ifdef NDEBUG
#undef NDEBUG #undef NDEBUG
#endif #endif
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 600
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <math.h>
@ -19,7 +19,9 @@
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "common.h" #include "common.h"
#ifdef CUBEB_GECKO_BUILD
#include "TestHarness.h" #include "TestHarness.h"
#endif
#define MAX_NUM_CHANNELS 32 #define MAX_NUM_CHANNELS 32
@ -45,6 +47,8 @@ typedef struct {
synth_state* synth_create(int num_channels, float sample_rate) synth_state* synth_create(int num_channels, float sample_rate)
{ {
synth_state* synth = (synth_state *) malloc(sizeof(synth_state)); synth_state* synth = (synth_state *) malloc(sizeof(synth_state));
if (!synth)
return NULL;
for(int i=0;i < MAX_NUM_CHANNELS;++i) for(int i=0;i < MAX_NUM_CHANNELS;++i)
synth->phase[i] = 0.0f; synth->phase[i] = 0.0f;
synth->num_channels = num_channels; synth->num_channels = num_channels;
@ -106,6 +110,12 @@ int supports_float32(const char* backend_id)
strcmp(backend_id, "audiotrack") != 0); strcmp(backend_id, "audiotrack") != 0);
} }
/* The WASAPI backend only supports float. */
int supports_int16(const char* backend_id)
{
return strcmp(backend_id, "wasapi") != 0;
}
/* Some backends don't have code to deal with more than mono or stereo. */ /* Some backends don't have code to deal with more than mono or stereo. */
int supports_channel_count(const char* backend_id, int nchannels) int supports_channel_count(const char* backend_id, int nchannels)
{ {
@ -131,6 +141,7 @@ int run_test(int num_channels, int sampling_rate, int is_float)
backend_id = cubeb_get_backend_id(ctx); backend_id = cubeb_get_backend_id(ctx);
if ((is_float && !supports_float32(backend_id)) || if ((is_float && !supports_float32(backend_id)) ||
(!is_float && !supports_int16(backend_id)) ||
!supports_channel_count(backend_id, num_channels)) { !supports_channel_count(backend_id, num_channels)) {
/* don't treat this as a test failure. */ /* don't treat this as a test failure. */
goto cleanup; goto cleanup;
@ -168,22 +179,30 @@ cleanup:
return r; return r;
} }
int run_panning_volume_test() int run_panning_volume_test(int is_float)
{ {
int r = CUBEB_OK; int r = CUBEB_OK;
cubeb *ctx = NULL; cubeb *ctx = NULL;
synth_state* synth = NULL; synth_state* synth = NULL;
cubeb_stream *stream = NULL; cubeb_stream *stream = NULL;
const char * backend_id = NULL;
r = cubeb_init(&ctx, "Cubeb audio test"); r = cubeb_init(&ctx, "Cubeb audio test");
if (r != CUBEB_OK) { if (r != CUBEB_OK) {
fprintf(stderr, "Error initializing cubeb library\n"); fprintf(stderr, "Error initializing cubeb library\n");
goto cleanup; goto cleanup;
} }
backend_id = cubeb_get_backend_id(ctx);
if ((is_float && !supports_float32(backend_id)) ||
(!is_float && !supports_int16(backend_id))) {
/* don't treat this as a test failure. */
goto cleanup;
}
cubeb_stream_params params; cubeb_stream_params params;
params.format = CUBEB_SAMPLE_S16NE; params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE;
params.rate = 44100; params.rate = 44100;
params.channels = 2; params.channels = 2;
@ -194,7 +213,7 @@ int run_panning_volume_test()
} }
r = cubeb_stream_init(ctx, &stream, "test tone", params, r = cubeb_stream_init(ctx, &stream, "test tone", params,
100, data_cb_short, state_cb, synth); 100, is_float ? data_cb_float : data_cb_short, state_cb, synth);
if (r != CUBEB_OK) { if (r != CUBEB_OK) {
fprintf(stderr, "Error initializing cubeb stream: %d\n", r); fprintf(stderr, "Error initializing cubeb stream: %d\n", r);
goto cleanup; goto cleanup;
@ -262,9 +281,12 @@ void run_channel_rate_test()
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
#ifdef CUBEB_GECKO_BUILD
ScopedXPCOM xpcom("test_audio"); ScopedXPCOM xpcom("test_audio");
#endif
assert(run_panning_volume_test() == CUBEB_OK); assert(run_panning_volume_test(0) == CUBEB_OK);
assert(run_panning_volume_test(1) == CUBEB_OK);
run_channel_rate_test(); run_channel_rate_test();
return CUBEB_OK; return CUBEB_OK;

View File

@ -5,12 +5,17 @@
#include <cubeb/cubeb.h> #include <cubeb/cubeb.h>
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#ifdef CUBEB_GECKO_BUILD
#include "TestHarness.h" #include "TestHarness.h"
#endif
#define LOG(msg) fprintf(stderr, "%s\n", msg); #define LOG(msg) fprintf(stderr, "%s\n", msg);
int main(int argc, char * argv[]) int main(int argc, char * argv[])
{ {
#ifdef CUBEB_GECKO_BUILD
ScopedXPCOM xpcom("test_latency"); ScopedXPCOM xpcom("test_latency");
#endif
cubeb * ctx = NULL; cubeb * ctx = NULL;
int r; int r;

View File

@ -7,27 +7,33 @@
#ifdef NDEBUG #ifdef NDEBUG
#undef NDEBUG #undef NDEBUG
#endif #endif
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 600
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include "common.h" #include "common.h"
#ifdef CUBEB_GECKO_BUILD
#include "TestHarness.h" #include "TestHarness.h"
#endif
#if (defined(_WIN32) || defined(__WIN32__)) #if (defined(_WIN32) || defined(__WIN32__))
#define __func__ __FUNCTION__ #define __func__ __FUNCTION__
#endif #endif
#define ARRAY_LENGTH(_x) (sizeof(_x) / sizeof(_x[0])) #define ARRAY_LENGTH(_x) (sizeof(_x) / sizeof(_x[0]))
#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__); #define BEGIN_TEST fprintf(stderr, "START %s\n", __func__)
#define END_TEST fprintf(stderr, "END %s\n", __func__); #define END_TEST fprintf(stderr, "END %s\n", __func__)
#define STREAM_LATENCY 100 #define STREAM_LATENCY 100
#define STREAM_RATE 44100 #define STREAM_RATE 44100
#define STREAM_CHANNELS 1 #define STREAM_CHANNELS 1
#if (defined(_WIN32) || defined(__WIN32__))
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
#else
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE #define STREAM_FORMAT CUBEB_SAMPLE_S16LE
#endif
static int dummy; static int dummy;
static uint64_t total_frames_written; static uint64_t total_frames_written;
@ -37,7 +43,12 @@ static long
test_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes) test_data_callback(cubeb_stream * stm, void * user_ptr, void * p, long nframes)
{ {
assert(stm && user_ptr == &dummy && p && nframes > 0); assert(stm && user_ptr == &dummy && p && nframes > 0);
#if (defined(_WIN32) || defined(__WIN32__))
memset(p, 0, nframes * sizeof(float));
#else
memset(p, 0, nframes * sizeof(short)); memset(p, 0, nframes * sizeof(short));
#endif
total_frames_written += nframes; total_frames_written += nframes;
if (delay_callback) { if (delay_callback) {
delay(10); delay(10);
@ -57,9 +68,9 @@ test_init_destroy_context(void)
cubeb * ctx; cubeb * ctx;
char const* backend_id; char const* backend_id;
BEGIN_TEST BEGIN_TEST;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
@ -70,8 +81,8 @@ test_init_destroy_context(void)
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static void static void
test_init_destroy_multiple_contexts(void) test_init_destroy_multiple_contexts(void)
@ -82,20 +93,20 @@ test_init_destroy_multiple_contexts(void)
int order[4] = {2, 0, 3, 1}; int order[4] = {2, 0, 3, 1};
assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order)); assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order));
BEGIN_TEST BEGIN_TEST;
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
r = cubeb_init(&ctx[i], NULL); r = cubeb_init(&ctx[i], NULL);
assert(r == 0 && ctx[i]); assert(r == 0 && ctx[i]);
} }
/* destroy in a different order */ /* destroy in a different order */
for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { for (i = 0; i < ARRAY_LENGTH(ctx); ++i) {
cubeb_destroy(ctx[order[i]]); cubeb_destroy(ctx[order[i]]);
} }
END_TEST END_TEST;
} }
static void static void
test_context_variables(void) test_context_variables(void)
@ -105,14 +116,14 @@ test_context_variables(void)
uint32_t value; uint32_t value;
cubeb_stream_params params; cubeb_stream_params params;
BEGIN_TEST BEGIN_TEST;
r = cubeb_init(&ctx, "test_context_variables"); r = cubeb_init(&ctx, "test_context_variables");
assert(r == 0 && ctx); assert(r == 0 && ctx);
params.channels = 2; params.channels = STREAM_CHANNELS;
params.format = CUBEB_SAMPLE_S16LE; params.format = STREAM_FORMAT;
params.rate = 44100; params.rate = STREAM_RATE;
r = cubeb_get_min_latency(ctx, params, &value); r = cubeb_get_min_latency(ctx, params, &value);
assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED);
if (r == CUBEB_OK) { if (r == CUBEB_OK) {
@ -127,8 +138,8 @@ test_context_variables(void)
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static void static void
test_init_destroy_stream(void) test_init_destroy_stream(void)
@ -138,9 +149,9 @@ test_init_destroy_stream(void)
cubeb_stream * stream; cubeb_stream * stream;
cubeb_stream_params params; cubeb_stream_params params;
BEGIN_TEST BEGIN_TEST;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
params.format = STREAM_FORMAT; params.format = STREAM_FORMAT;
@ -154,8 +165,8 @@ test_init_destroy_stream(void)
cubeb_stream_destroy(stream); cubeb_stream_destroy(stream);
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static void static void
test_init_destroy_multiple_streams(void) test_init_destroy_multiple_streams(void)
@ -166,9 +177,9 @@ test_init_destroy_multiple_streams(void)
cubeb_stream * stream[8]; cubeb_stream * stream[8];
cubeb_stream_params params; cubeb_stream_params params;
BEGIN_TEST BEGIN_TEST;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
params.format = STREAM_FORMAT; params.format = STREAM_FORMAT;
@ -188,8 +199,8 @@ test_init_destroy_multiple_streams(void)
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static void static void
test_configure_stream(void) test_configure_stream(void)
@ -199,9 +210,9 @@ test_configure_stream(void)
cubeb_stream * stream; cubeb_stream * stream;
cubeb_stream_params params; cubeb_stream_params params;
BEGIN_TEST BEGIN_TEST;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
params.format = STREAM_FORMAT; params.format = STREAM_FORMAT;
@ -220,8 +231,8 @@ test_configure_stream(void)
cubeb_stream_destroy(stream); cubeb_stream_destroy(stream);
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static void static void
test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
@ -232,9 +243,9 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
cubeb_stream * stream[8]; cubeb_stream * stream[8];
cubeb_stream_params params; cubeb_stream_params params;
BEGIN_TEST BEGIN_TEST;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
params.format = STREAM_FORMAT; params.format = STREAM_FORMAT;
@ -281,8 +292,8 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms)
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static void static void
test_init_destroy_multiple_contexts_and_streams(void) test_init_destroy_multiple_contexts_and_streams(void)
@ -295,9 +306,9 @@ test_init_destroy_multiple_contexts_and_streams(void)
size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx); size_t streams_per_ctx = ARRAY_LENGTH(stream) / ARRAY_LENGTH(ctx);
assert(ARRAY_LENGTH(ctx) * streams_per_ctx == ARRAY_LENGTH(stream)); assert(ARRAY_LENGTH(ctx) * streams_per_ctx == ARRAY_LENGTH(stream));
BEGIN_TEST BEGIN_TEST;
params.format = STREAM_FORMAT; params.format = STREAM_FORMAT;
params.rate = STREAM_RATE; params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS; params.channels = STREAM_CHANNELS;
@ -320,8 +331,8 @@ test_init_destroy_multiple_contexts_and_streams(void)
cubeb_destroy(ctx[i]); cubeb_destroy(ctx[i]);
} }
END_TEST END_TEST;
} }
static void static void
test_basic_stream_operations(void) test_basic_stream_operations(void)
@ -332,9 +343,9 @@ test_basic_stream_operations(void)
cubeb_stream_params params; cubeb_stream_params params;
uint64_t position; uint64_t position;
BEGIN_TEST BEGIN_TEST;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
params.format = STREAM_FORMAT; params.format = STREAM_FORMAT;
@ -366,8 +377,8 @@ test_basic_stream_operations(void)
cubeb_stream_destroy(stream); cubeb_stream_destroy(stream);
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static void static void
test_stream_position(void) test_stream_position(void)
@ -379,9 +390,9 @@ test_stream_position(void)
cubeb_stream_params params; cubeb_stream_params params;
uint64_t position, last_position; uint64_t position, last_position;
BEGIN_TEST BEGIN_TEST;
total_frames_written = 0; total_frames_written = 0;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
@ -456,8 +467,8 @@ test_stream_position(void)
cubeb_stream_destroy(stream); cubeb_stream_destroy(stream);
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
static int do_drain; static int do_drain;
static int got_drain; static int got_drain;
@ -495,9 +506,9 @@ test_drain(void)
cubeb_stream_params params; cubeb_stream_params params;
uint64_t position; uint64_t position;
BEGIN_TEST BEGIN_TEST;
total_frames_written = 0; total_frames_written = 0;
r = cubeb_init(&ctx, "test_sanity"); r = cubeb_init(&ctx, "test_sanity");
assert(r == 0 && ctx); assert(r == 0 && ctx);
@ -539,8 +550,8 @@ test_drain(void)
cubeb_stream_destroy(stream); cubeb_stream_destroy(stream);
cubeb_destroy(ctx); cubeb_destroy(ctx);
END_TEST END_TEST;
} }
int is_windows_7() int is_windows_7()
{ {
@ -572,7 +583,9 @@ int is_windows_7()
int int
main(int argc, char * argv[]) main(int argc, char * argv[])
{ {
#ifdef CUBEB_GECKO_BUILD
ScopedXPCOM xpcom("test_sanity"); ScopedXPCOM xpcom("test_sanity");
#endif
test_init_destroy_context(); test_init_destroy_context();
test_init_destroy_multiple_contexts(); test_init_destroy_multiple_contexts();

View File

@ -9,17 +9,25 @@
#ifdef NDEBUG #ifdef NDEBUG
#undef NDEBUG #undef NDEBUG
#endif #endif
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 600
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <math.h>
#include <assert.h> #include <assert.h>
#include <limits.h>
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "common.h" #include "common.h"
#ifdef CUBEB_GECKO_BUILD
#include "TestHarness.h" #include "TestHarness.h"
#endif
#define SAMPLE_FREQUENCY 48000 #define SAMPLE_FREQUENCY 48000
#if (defined(_WIN32) || defined(__WIN32__))
#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
#else
#define STREAM_FORMAT CUBEB_SAMPLE_S16LE
#endif
/* store the phase of the generated waveform */ /* store the phase of the generated waveform */
struct cb_user_data { struct cb_user_data {
@ -29,7 +37,12 @@ struct cb_user_data {
long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes) long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes)
{ {
struct cb_user_data *u = (struct cb_user_data *)user; struct cb_user_data *u = (struct cb_user_data *)user;
#if (defined(_WIN32) || defined(__WIN32__))
float *b = (float *)buffer;
#else
short *b = (short *)buffer; short *b = (short *)buffer;
#endif
float t1, t2;
int i; int i;
if (stream == NULL || u == NULL) if (stream == NULL || u == NULL)
@ -38,10 +51,24 @@ long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes)
/* generate our test tone on the fly */ /* generate our test tone on the fly */
for (i = 0; i < nframes; i++) { for (i = 0; i < nframes; i++) {
/* North American dial tone */ /* North American dial tone */
b[i] = 16000*sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY); t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY);
b[i] += 16000*sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY); t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY);
#if (defined(_WIN32) || defined(__WIN32__))
b[i] = 0.5 * t1;
b[i] += 0.5 * t2;
#else
b[i] = (SHRT_MAX / 2) * t1;
b[i] += (SHRT_MAX / 2) * t2;
#endif
/* European dial tone */ /* European dial tone */
/*b[i] = 30000*sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);*/ /*
t1 = sin(2*M_PI*(i + u->position)*425/SAMPLE_FREQUENCY);
#if (defined(_WIN32) || defined(__WIN32__))
b[i] = t1;
#else
b[i] = SHRT_MAX * t1;
#endif
*/
} }
/* remember our phase to avoid clicking on buffer transitions */ /* remember our phase to avoid clicking on buffer transitions */
/* we'll still click if position overflows */ /* we'll still click if position overflows */
@ -73,7 +100,9 @@ void state_cb(cubeb_stream *stream, void *user, cubeb_state state)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
#ifdef CUBEB_GECKO_BUILD
ScopedXPCOM xpcom("test_tone"); ScopedXPCOM xpcom("test_tone");
#endif
cubeb *ctx; cubeb *ctx;
cubeb_stream *stream; cubeb_stream *stream;
@ -87,7 +116,7 @@ int main(int argc, char *argv[])
return r; return r;
} }
params.format = CUBEB_SAMPLE_S16NE; params.format = STREAM_FORMAT;
params.rate = SAMPLE_FREQUENCY; params.rate = SAMPLE_FREQUENCY;
params.channels = 1; params.channels = 1;