diff --git a/media/libcubeb/AUTHORS b/media/libcubeb/AUTHORS index b441e8a1c374..394e9ae6768c 100644 --- a/media/libcubeb/AUTHORS +++ b/media/libcubeb/AUTHORS @@ -4,3 +4,5 @@ Michael Wu Paul Adenot David Richards Sebastien Alaiwan +KO Myung-Hun +Haakon Sporsheim diff --git a/media/libcubeb/README_MOZILLA b/media/libcubeb/README_MOZILLA index 6272a228c3f6..e6b0710b0d57 100644 --- a/media/libcubeb/README_MOZILLA +++ b/media/libcubeb/README_MOZILLA @@ -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 git commit ID used was 588c82be50ffee59b7fab71b56e6081a5a89301c. +The git commit ID used was 46696c53376777d926444d5187d3a1ddd8d84a27. diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index cd26dba7863b..4c016e0d231e 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -152,6 +152,74 @@ enum { 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. @param stream @param user_ptr @@ -179,6 +247,12 @@ typedef void (* cubeb_state_callback)(cubeb_stream * stream, * @param user */ 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 application scoped initialization. @param context @@ -337,6 +411,41 @@ int cubeb_stream_device_destroy(cubeb_stream * stream, int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, 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) } #endif diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h index 503b59a469e1..323510712bbc 100644 --- a/media/libcubeb/src/cubeb-internal.h +++ b/media/libcubeb/src/cubeb-internal.h @@ -8,6 +8,8 @@ #define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 #include "cubeb/cubeb.h" +#include +#include struct cubeb_ops { int (* init)(cubeb ** context, char const * context_name); @@ -17,6 +19,8 @@ struct cubeb_ops { cubeb_stream_params params, uint32_t * latency_ms); 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); int (* stream_init)(cubeb * context, cubeb_stream ** stream, char const * stream_name, cubeb_stream_params stream_params, unsigned int latency, @@ -36,7 +40,6 @@ struct cubeb_ops { cubeb_device * device); int (* stream_register_device_changed_callback)(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback); - }; #define XASSERT(expr) do { \ diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index 8f3b040a0bdb..f22e8a901892 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -7,6 +7,7 @@ #undef NDEBUG #include #include +#include #if defined(HAVE_CONFIG_H) #include "config.h" #endif @@ -56,6 +57,9 @@ int opensl_init(cubeb ** context, char const * context_name); #if defined(USE_AUDIOTRACK) int audiotrack_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_KAI) +int kai_init(cubeb ** context, char const * context_name); +#endif int validate_stream_params(cubeb_stream_params stream_params) @@ -89,12 +93,12 @@ int cubeb_init(cubeb ** context, char const * context_name) { int (* init[])(cubeb **, char const *) = { -#if defined(USE_PULSE) - pulse_init, -#endif #if defined(USE_JACK) jack_init, #endif +#if defined(USE_PULSE) + pulse_init, +#endif #if defined(USE_ALSA) alsa_init, #endif @@ -121,6 +125,9 @@ cubeb_init(cubeb ** context, char const * context_name) #endif #if defined(USE_AUDIOTRACK) audiotrack_init, +#endif +#if defined(USE_KAI) + kai_init, #endif }; 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); } + +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; +} + diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index 5901a3b5bd8f..d9d26e7b20a1 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -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 * 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) { return CUBEB_ERROR; } @@ -1116,6 +1116,7 @@ static struct cubeb_ops const alsa_ops = { .get_max_channel_count = alsa_get_max_channel_count, .get_min_latency = alsa_get_min_latency, .get_preferred_sample_rate = alsa_get_preferred_sample_rate, + .enumerate_devices = NULL, .destroy = alsa_destroy, .stream_init = alsa_stream_init, .stream_destroy = alsa_stream_destroy, diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c index cd6abe5334c9..bf85bb8c07c6 100644 --- a/media/libcubeb/src/cubeb_audiotrack.c +++ b/media/libcubeb/src/cubeb_audiotrack.c @@ -415,6 +415,7 @@ static struct cubeb_ops const audiotrack_ops = { .get_max_channel_count = audiotrack_get_max_channel_count, .get_min_latency = audiotrack_get_min_latency, .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, + .enumerate_devices = NULL, .destroy = audiotrack_destroy, .stream_init = audiotrack_stream_init, .stream_destroy = audiotrack_stream_destroy, diff --git a/media/libcubeb/src/cubeb_audiounit.c b/media/libcubeb/src/cubeb_audiounit.c index 9b6b0e95a3bb..750610779216 100644 --- a/media/libcubeb/src/cubeb_audiounit.c +++ b/media/libcubeb/src/cubeb_audiounit.c @@ -159,7 +159,10 @@ audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, pthread_mutex_unlock(&stm->mutex); 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; @@ -400,6 +403,28 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) } #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 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; } +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 = { .init = audiounit_init, .get_backend_id = audiounit_get_backend_id, .get_max_channel_count = audiounit_get_max_channel_count, .get_min_latency = audiounit_get_min_latency, .get_preferred_sample_rate = audiounit_get_preferred_sample_rate, + .enumerate_devices = audiounit_enumerate_devices, .destroy = audiounit_destroy, .stream_init = audiounit_stream_init, .stream_destroy = audiounit_stream_destroy, diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index 7642e029ef6e..526aef0a4438 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -817,6 +817,7 @@ static struct cubeb_ops const opensl_ops = { .get_max_channel_count = opensl_get_max_channel_count, .get_min_latency = opensl_get_min_latency, .get_preferred_sample_rate = opensl_get_preferred_sample_rate, + .enumerate_devices = NULL, .destroy = opensl_destroy, .stream_init = opensl_stream_init, .stream_destroy = opensl_stream_destroy, diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index c1ac2fb21c23..ef356617573a 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -26,6 +26,8 @@ X(pa_context_drain) \ X(pa_context_get_server_info) \ 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_new) \ X(pa_context_rttime_new) \ @@ -37,6 +39,7 @@ X(pa_frame_size) \ X(pa_operation_get_state) \ X(pa_operation_unref) \ + X(pa_proplist_gets) \ X(pa_rtclock_now) \ X(pa_stream_begin_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) { 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) { short * b = buffer; for (uint32_t i = 0; i < samples; i++) { @@ -724,12 +727,221 @@ pulse_stream_set_panning(cubeb_stream * stream, float panning) 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 = { .init = pulse_init, .get_backend_id = pulse_get_backend_id, .get_max_channel_count = pulse_get_max_channel_count, .get_min_latency = pulse_get_min_latency, .get_preferred_sample_rate = pulse_get_preferred_sample_rate, + .enumerate_devices = pulse_enumerate_devices, .destroy = pulse_destroy, .stream_init = pulse_stream_init, .stream_destroy = pulse_stream_destroy, diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index e6d531a4f186..94f9961735fa 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -356,6 +356,7 @@ static struct cubeb_ops const sndio_ops = { .get_max_channel_count = sndio_get_max_channel_count, .get_min_latency = sndio_get_min_latency, .get_preferred_sample_rate = sndio_get_preferred_sample_rate, + .enumerate_devices = NULL, .destroy = sndio_destroy, .stream_init = sndio_stream_init, .stream_destroy = sndio_stream_destroy, diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index 2ba8509bb723..c7966f239c0b 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -4,14 +4,15 @@ * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ -// This enables assert in release, and lets us have debug-only code #if defined(HAVE_CONFIG_H) #include "config.h" #endif +#include #include #include #include #include +#include #include #include #include "cubeb/cubeb.h" @@ -22,11 +23,23 @@ #include #include -/**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 #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #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 #ifdef LOGGING_ENABLED @@ -48,13 +61,13 @@ ms_to_hns(uint32_t ms) } uint32_t -hns_to_ms(uint32_t hns) +hns_to_ms(REFERENCE_TIME hns) { return hns / 10000; } double -hns_to_s(uint32_t hns) +hns_to_s(REFERENCE_TIME hns) { return static_cast(hns) / 10000000; } @@ -76,7 +89,7 @@ void SafeRelease(T * ptr) } /* 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 { public: @@ -112,7 +125,7 @@ public: } /* This is guaranteed to have the good behaviour if it succeeds. The behaviour - * is undefined otherwise. */ + is undefined otherwise. */ void assert_current_thread_owns() { #ifdef DEBUG @@ -150,12 +163,12 @@ struct auto_com { if (result == RPC_E_CHANGED_MODE) { // This is not an error, COM was not initialized by this function, so it is // 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) { // This is not an error. We are allowed to call CoInitializeEx more than // once, as long as it is matches by an CoUninitialize call. // 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)) { CoUninitialize(); @@ -184,8 +197,8 @@ int setup_wasapi_stream(cubeb_stream * stm); struct cubeb { cubeb_ops const * ops; - /* Library dynamically opened to increase the render - * thread priority, and the two function pointers we need. */ + /* Library dynamically opened to increase the render thread priority, and + the two function pointers we need. */ HMODULE mmcss_module; set_mm_thread_characteristics_function set_mm_thread_characteristics; revert_mm_thread_characteristics_function revert_mm_thread_characteristics; @@ -196,9 +209,9 @@ class wasapi_endpoint_notification_client; struct cubeb_stream { cubeb * context; - /* Mixer pameters. We need to convert the input - * stream to this samplerate/channel layout, as WASAPI - * does not resample nor upmix itself. */ + /* Mixer pameters. We need to convert the input stream to this + samplerate/channel layout, as WASAPI * does not resample nor upmix + itself. */ cubeb_stream_params mix_params; cubeb_stream_params stream_params; /* The latency initially requested for this stream. */ @@ -208,10 +221,10 @@ struct cubeb_stream void * user_ptr; /* Lifetime considerations: - * - client, render_client, audio_clock and audio_stream_volume are interface - * pointer to the IAudioClient. - * - The lifetime for device_enumerator and notification_client, resampler, - * mix_buffer are the same as the cubeb_stream instance. */ + - client, render_client, audio_clock and audio_stream_volume are interface + pointer to the IAudioClient. + - The lifetime for device_enumerator and notification_client, resampler, + mix_buffer are the same as the cubeb_stream instance. */ /* Main handle on the WASAPI stream. */ IAudioClient * client; @@ -222,23 +235,23 @@ struct cubeb_stream /* Interface pointer to use the stream audio clock. */ IAudioClock * audio_clock; /* 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; /* 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; /* 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; /* Device enumerator to be able to be notified when the default - * device change. */ + device change. */ IMMDeviceEnumerator * device_enumerator; /* 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 - * device */ + audio device changes and route the audio to the new default audio output + device */ wasapi_endpoint_notification_client * notification_client; /* 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; /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required. The reconfiguration is handled by the render loop thread. */ @@ -256,10 +269,10 @@ struct cubeb_stream /* Resampler instance. Resampling will only happen if necessary. */ cubeb_resampler * resampler; /* 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; /* Stream volume. Set via stream_set_volume and used to reset volume on - * device changes. */ + device changes. */ float volume; /* True if the stream is draining. */ bool draining; @@ -307,9 +320,14 @@ public: , reconfigure_event(event) { } + virtual ~wasapi_endpoint_notification_client() + { } + HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id) { + LOG("Audio device default changed.\n"); + /* we only support a single stream type for now. */ if (flow != eRender && role != eMultimedia) { return S_OK; @@ -317,14 +335,14 @@ public: BOOL ok = SetEvent(reconfigure_event); if (!ok) { - LOG("SetEvent on reconfigure_event failed: %x", GetLastError()); + LOG("SetEvent on reconfigure_event failed: %x\n", GetLastError()); } return S_OK; } /* 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) { 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); /* 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 - * rendered by the hardware, so we do the same for consistency. */ + channel, but directsound and winmm simply drop the channels that cannot be + rendered by the hardware, so we do the same for consistency. */ long out_index = 0; for (long i = 0; i < inframes * in_channels; i += in_channels) { 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, - * before the eventual upmix occurs. */ +/* This returns the size of a frame in the stream, before the eventual upmix + occurs. */ static size_t 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) { /* If we need to upmix after resampling, resample into the mix buffer to - * avoid a copy. */ + avoid a copy. */ float * dest; if (should_upmix(stm) || should_downmix(stm)) { 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. */ if (out_frames < frames_needed) { - LOG("draining.\n"); + LOG("start draining.\n"); stm->draining = true; } /* 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); 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 mmcss_handle = NULL; HRESULT hr = 0; - bool first = true; DWORD mmcss_task_index = 0; auto_com com; if (!com.ok()) { LOG("COM initialization failed on render_loop thread.\n"); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); return 0; } /* We could consider using "Pro Audio" here for WebAudio and - * maybe WebRTC. */ + maybe WebRTC. */ mmcss_handle = stm->context->set_mm_thread_characteristics("Audio", &mmcss_task_index); if (!mmcss_handle) { @@ -529,7 +547,7 @@ wasapi_stream_render_loop(LPVOID stream) case WAIT_OBJECT_0: { /* shutdown */ is_playing = false; /* We don't check if the drain is actually finished here, we just want to - * shutdown. */ + shutdown. */ if (stm->draining) { 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); close_wasapi_stream(stm); /* 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); if (r != CUBEB_OK) { /* Don't destroy the stream here, since we expect the caller to do so after the error has propagated via the state callback. */ is_playing = false; - hr = -1; + hr = E_FAIL; continue; } } @@ -560,14 +578,12 @@ wasapi_stream_render_loop(LPVOID stream) hr = stm->client->GetCurrentPadding(&padding); if (FAILED(hr)) { - LOG("Failed to get padding\n"); + LOG("Failed to get padding: %x\n", hr); is_playing = false; continue; } XASSERT(padding <= stm->buffer_frame_count); - long available = stm->buffer_frame_count - padding; - if (stm->draining) { if (padding == 0) { stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); @@ -576,6 +592,8 @@ wasapi_stream_render_loop(LPVOID stream) continue; } + long available = stm->buffer_frame_count - padding; + if (available == 0) { continue; } @@ -588,11 +606,11 @@ wasapi_stream_render_loop(LPVOID stream) hr = stm->render_client->ReleaseBuffer(wrote, 0); if (FAILED(hr)) { - LOG("failed to release buffer.\n"); + LOG("failed to release buffer: %x\n", hr); is_playing = false; } } else { - LOG("failed to get buffer.\n"); + LOG("failed to get buffer: %x\n", hr); is_playing = false; } } @@ -601,7 +619,7 @@ wasapi_stream_render_loop(LPVOID stream) XASSERT(stm->shutdown_event == wait_array[0]); if (++timeout_count >= timeout_limit) { is_playing = false; - hr = -1; + hr = E_FAIL; } break; default: @@ -636,7 +654,6 @@ HRESULT register_notification_client(cubeb_stream * stm) HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&stm->device_enumerator)); - if (FAILED(hr)) { LOG("Could not get device enumerator: %x\n", 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); hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client); - if (FAILED(hr)) { LOG("Could not register endpoint notification callback: %x\n", hr); return hr; @@ -677,16 +693,16 @@ HRESULT get_default_endpoint(IMMDevice ** device) NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&enumerator)); if (FAILED(hr)) { - LOG("Could not get device enumerator.\n"); + LOG("Could not get device enumerator: %x\n", hr); return hr; } /* eMultimedia is okay for now ("Music, movies, narration, [...]"). - * We will need to change this when we distinguish streams by use-case, other - * possible values being eConsole ("Games, system notification sounds [...]") - * and eCommunication ("Voice communication"). */ + We will need to change this when we distinguish streams by use-case, other + possible values being eConsole ("Games, system notification sounds [...]") + and eCommunication ("Voice communication"). */ hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, device); 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); return hr; } @@ -781,12 +797,15 @@ int wasapi_init(cubeb ** context, char const * context_name) IMMDevice * device; hr = get_default_endpoint(&device); if (FAILED(hr)) { - LOG("Could not get device.\n"); + LOG("Could not get device: %x\n", hr); return CUBEB_ERROR; } SafeRelease(device); cubeb * ctx = (cubeb *)calloc(1, sizeof(cubeb)); + if (!ctx) { + return CUBEB_ERROR; + } ctx->ops = &wasapi_ops; @@ -906,10 +925,14 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten return CUBEB_ERROR; } + if (params.format != CUBEB_SAMPLE_FLOAT32NE) { + return CUBEB_ERROR_INVALID_FORMAT; + } + IMMDevice * device; hr = get_default_endpoint(&device); if (FAILED(hr)) { - LOG("Could not get default endpoint:%x.\n", hr); + LOG("Could not get default endpoint: %x\n", hr); return CUBEB_ERROR; } @@ -918,7 +941,7 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten NULL, (void **)&client); SafeRelease(device); 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; } @@ -926,15 +949,15 @@ wasapi_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten hr = client->GetDevicePeriod(&default_period, NULL); if (FAILED(hr)) { SafeRelease(client); - LOG("Could not get device period: %x.\n", hr); + LOG("Could not get device period: %x\n", hr); return CUBEB_ERROR; } LOG("default device period: %ld\n", default_period); /* According to the docs, the best latency we can achieve is by synchronizing - * the stream and the engine. - * http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ + the stream and the engine. + http://msdn.microsoft.com/en-us/library/windows/desktop/dd370871%28v=vs.85%29.aspx */ *latency_ms = hns_to_ms(default_period); SafeRelease(client); @@ -983,22 +1006,22 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) void wasapi_stream_destroy(cubeb_stream * stm); -/* Based on the mix format and the stream format, try to find a way to play what - * the user requested. */ +/* Based on the mix format and the stream format, try to find a way to play + what the user requested. */ static void 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 - * handled in the callback. */ + handled in the callback. */ if ((*mix_format)->nChannels <= 2) { return; } /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], - * 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 - * 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*/ + 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 + 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*/ if ((*mix_format)->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { return; } @@ -1009,7 +1032,7 @@ handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cub WAVEFORMATEXTENSIBLE hw_mix_format = *format_pcm; /* 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) { case 1: /* 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) { /* 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); WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast(closest); 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; } else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { /* 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 - * of the code figure out the right conversion path. */ + field with some sound cards. We restore the mix format, and let the rest + of the code figure out the right conversion path. */ *reinterpret_cast(*mix_format) = hw_mix_format; } else if (hr == S_OK) { 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 - * this pointer. */ + this pointer. */ hr = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, 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, - * 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); if (FAILED(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); /* Shared mode WASAPI always supports float32 sample format, so this - * is safe. */ + is safe. */ stm->mix_params.format = CUBEB_SAMPLE_FLOAT32NE; stm->mix_params.rate = mix_format->nSamplesPerSec; stm->mix_params.channels = mix_format->nChannels; @@ -1107,17 +1132,15 @@ int setup_wasapi_stream(cubeb_stream * stm) 0, mix_format, NULL); - CoTaskMemFree(mix_format); - if (FAILED(hr)) { - LOG("Unable to initialize audio client: %x.\n", hr); + LOG("Unable to initialize audio client: %x\n", hr); return CUBEB_ERROR; } hr = stm->client->GetBufferSize(&stm->buffer_frame_count); 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; } @@ -1127,21 +1150,21 @@ int setup_wasapi_stream(cubeb_stream * stm) hr = stm->client->SetEventHandle(stm->refill_event); 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; } hr = stm->client->GetService(__uuidof(IAudioRenderClient), (void **)&stm->render_client); 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; } hr = stm->client->GetService(__uuidof(IAudioStreamVolume), (void **)&stm->audio_stream_volume); if (FAILED(hr)) { - LOG("Could not get the IAudioStreamVolume %x.\n", hr); + LOG("Could not get the IAudioStreamVolume: %x\n", hr); return CUBEB_ERROR; } @@ -1149,7 +1172,7 @@ int setup_wasapi_stream(cubeb_stream * stm) hr = stm->client->GetService(__uuidof(IAudioClock), (void **)&stm->audio_clock); if (FAILED(hr)) { - LOG("Could not get the IAudioClock %x.\n", hr); + LOG("Could not get the IAudioClock: %x\n", hr); 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, - * and copy it over, so we are always resampling the number - * of channels of the stream, not the number of channels - * that WASAPI wants. */ + and copy it over, so we are always resampling the number + of channels of the stream, not the number of channels + that WASAPI wants. */ stm->resampler = cubeb_resampler_create(stm, stm->stream_params, stm->mix_params.rate, stm->data_callback, @@ -1191,6 +1214,10 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** 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)); XASSERT(stm); @@ -1235,7 +1262,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, hr = register_notification_client(stm); if (FAILED(hr)) { /* 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); } @@ -1325,7 +1352,7 @@ int wasapi_stream_start(cubeb_stream * stm) LOG("could not start the stream after reconfig: %x\n", hr); return CUBEB_ERROR; } - } else if (FAILED(hr)) { + } else if (FAILED(hr)) { LOG("could not start the stream.\n"); 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); /* The GetStreamLatency method only works if the - * AudioClient has been initialized. */ + AudioClient has been initialized. */ if (!stm->client) { return CUBEB_ERROR; } 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); *latency = static_cast(latency_s * stm->stream_params.rate); @@ -1428,12 +1458,245 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume) 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(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(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 = { /*.init =*/ wasapi_init, /*.get_backend_id =*/ wasapi_get_backend_id, /*.get_max_channel_count =*/ wasapi_get_max_channel_count, /*.get_min_latency =*/ wasapi_get_min_latency, /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, + /*.enumerate_devices =*/ wasapi_enumerate_devices, /*.destroy =*/ wasapi_destroy, /*.stream_init =*/ wasapi_stream_init, /*.stream_destroy =*/ wasapi_stream_destroy, diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index 029c867c4a03..da5828f1d45b 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -8,7 +8,6 @@ #undef WINVER #define WINVER 0x0501 #undef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN #include #include @@ -27,15 +26,46 @@ #endif /**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 #define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */ #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.*/ #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #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 NBUFS 4 @@ -671,12 +701,307 @@ winmm_stream_set_volume(cubeb_stream * stm, float volume) 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 = { /*.init =*/ winmm_init, /*.get_backend_id =*/ winmm_get_backend_id, /*.get_max_channel_count=*/ winmm_get_max_channel_count, /*.get_min_latency=*/ winmm_get_min_latency, /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, + /*.enumerate_devices =*/ winmm_enumerate_devices, /*.destroy =*/ winmm_destroy, /*.stream_init =*/ winmm_stream_init, /*.stream_destroy =*/ winmm_stream_destroy, diff --git a/media/libcubeb/tests/common.h b/media/libcubeb/tests/common.h index 69445b86c6f5..47cc28cc7f45 100644 --- a/media/libcubeb/tests/common.h +++ b/media/libcubeb/tests/common.h @@ -27,3 +27,4 @@ void delay(unsigned int ms) #if !defined(M_PI) #define M_PI 3.14159265358979323846 #endif + diff --git a/media/libcubeb/tests/moz.build b/media/libcubeb/tests/moz.build index 4088f7c1259c..e98d8d98d21e 100644 --- a/media/libcubeb/tests/moz.build +++ b/media/libcubeb/tests/moz.build @@ -4,6 +4,8 @@ # 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/. +DEFINES['CUBEB_GECKO_BUILD'] = True + GeckoCppUnitTests([ 'test_tone' ]) diff --git a/media/libcubeb/tests/test_audio.cpp b/media/libcubeb/tests/test_audio.cpp index 69c166638b47..0158fb781769 100644 --- a/media/libcubeb/tests/test_audio.cpp +++ b/media/libcubeb/tests/test_audio.cpp @@ -19,7 +19,9 @@ #include "cubeb/cubeb.h" #include "common.h" +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif #define MAX_NUM_CHANNELS 32 @@ -45,6 +47,8 @@ typedef struct { synth_state* synth_create(int num_channels, float sample_rate) { synth_state* synth = (synth_state *) malloc(sizeof(synth_state)); + if (!synth) + return NULL; for(int i=0;i < MAX_NUM_CHANNELS;++i) synth->phase[i] = 0.0f; synth->num_channels = num_channels; @@ -106,6 +110,12 @@ int supports_float32(const char* backend_id) 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. */ 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); if ((is_float && !supports_float32(backend_id)) || + (!is_float && !supports_int16(backend_id)) || !supports_channel_count(backend_id, num_channels)) { /* don't treat this as a test failure. */ goto cleanup; @@ -168,22 +179,30 @@ cleanup: return r; } -int run_panning_volume_test() +int run_panning_volume_test(int is_float) { int r = CUBEB_OK; cubeb *ctx = NULL; synth_state* synth = NULL; cubeb_stream *stream = NULL; + const char * backend_id = NULL; r = cubeb_init(&ctx, "Cubeb audio test"); if (r != CUBEB_OK) { fprintf(stderr, "Error initializing cubeb library\n"); 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; - params.format = CUBEB_SAMPLE_S16NE; + params.format = is_float ? CUBEB_SAMPLE_FLOAT32NE : CUBEB_SAMPLE_S16NE; params.rate = 44100; params.channels = 2; @@ -194,7 +213,7 @@ int run_panning_volume_test() } 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) { fprintf(stderr, "Error initializing cubeb stream: %d\n", r); goto cleanup; @@ -262,9 +281,12 @@ void run_channel_rate_test() int main(int argc, char *argv[]) { +#ifdef CUBEB_GECKO_BUILD 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(); return CUBEB_OK; diff --git a/media/libcubeb/tests/test_latency.cpp b/media/libcubeb/tests/test_latency.cpp index c1b2ae2da109..5b4da8e7da2a 100644 --- a/media/libcubeb/tests/test_latency.cpp +++ b/media/libcubeb/tests/test_latency.cpp @@ -5,12 +5,17 @@ #include #include #include +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif + #define LOG(msg) fprintf(stderr, "%s\n", msg); int main(int argc, char * argv[]) { +#ifdef CUBEB_GECKO_BUILD ScopedXPCOM xpcom("test_latency"); +#endif cubeb * ctx = NULL; int r; diff --git a/media/libcubeb/tests/test_sanity.cpp b/media/libcubeb/tests/test_sanity.cpp index 1d7e5562bfaf..8c80f80510b0 100644 --- a/media/libcubeb/tests/test_sanity.cpp +++ b/media/libcubeb/tests/test_sanity.cpp @@ -14,20 +14,26 @@ #include #include #include "common.h" +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif #if (defined(_WIN32) || defined(__WIN32__)) #define __func__ __FUNCTION__ #endif #define ARRAY_LENGTH(_x) (sizeof(_x) / sizeof(_x[0])) -#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__); -#define END_TEST fprintf(stderr, "END %s\n", __func__); +#define BEGIN_TEST fprintf(stderr, "START %s\n", __func__) +#define END_TEST fprintf(stderr, "END %s\n", __func__) #define STREAM_LATENCY 100 #define STREAM_RATE 44100 #define STREAM_CHANNELS 1 +#if (defined(_WIN32) || defined(__WIN32__)) +#define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE +#else #define STREAM_FORMAT CUBEB_SAMPLE_S16LE +#endif static int dummy; 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) { assert(stm && user_ptr == &dummy && p && nframes > 0); +#if STREAM_FORMAT == CUBEB_SAMPLE_FLOAT32LE + memset(p, 0, nframes * sizeof(float)); +#else memset(p, 0, nframes * sizeof(short)); +#endif + total_frames_written += nframes; if (delay_callback) { delay(10); @@ -57,9 +68,9 @@ test_init_destroy_context(void) cubeb * ctx; char const* backend_id; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); @@ -70,8 +81,8 @@ test_init_destroy_context(void) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_destroy_multiple_contexts(void) @@ -82,20 +93,20 @@ test_init_destroy_multiple_contexts(void) int order[4] = {2, 0, 3, 1}; assert(ARRAY_LENGTH(ctx) == ARRAY_LENGTH(order)); - BEGIN_TEST + BEGIN_TEST; - for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { - r = cubeb_init(&ctx[i], NULL); - assert(r == 0 && ctx[i]); - } + for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { + r = cubeb_init(&ctx[i], NULL); + assert(r == 0 && ctx[i]); + } /* destroy in a different order */ for (i = 0; i < ARRAY_LENGTH(ctx); ++i) { cubeb_destroy(ctx[order[i]]); } - END_TEST - } + END_TEST; +} static void test_context_variables(void) @@ -105,14 +116,14 @@ test_context_variables(void) uint32_t value; 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); - params.channels = 2; - params.format = CUBEB_SAMPLE_S16LE; - params.rate = 44100; + params.channels = STREAM_CHANNELS; + params.format = STREAM_FORMAT; + params.rate = STREAM_RATE; r = cubeb_get_min_latency(ctx, params, &value); assert(r == CUBEB_OK || r == CUBEB_ERROR_NOT_SUPPORTED); if (r == CUBEB_OK) { @@ -127,8 +138,8 @@ test_context_variables(void) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_destroy_stream(void) @@ -138,9 +149,9 @@ test_init_destroy_stream(void) cubeb_stream * stream; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; @@ -154,8 +165,8 @@ test_init_destroy_stream(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_init_destroy_multiple_streams(void) @@ -166,9 +177,9 @@ test_init_destroy_multiple_streams(void) cubeb_stream * stream[8]; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; @@ -188,8 +199,8 @@ test_init_destroy_multiple_streams(void) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_configure_stream(void) @@ -199,9 +210,9 @@ test_configure_stream(void) cubeb_stream * stream; cubeb_stream_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; @@ -220,8 +231,8 @@ test_configure_stream(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void 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_params params; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; @@ -281,8 +292,8 @@ test_init_start_stop_destroy_multiple_streams(int early, int delay_ms) cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static 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); 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.channels = STREAM_CHANNELS; @@ -320,8 +331,8 @@ test_init_destroy_multiple_contexts_and_streams(void) cubeb_destroy(ctx[i]); } - END_TEST - } + END_TEST; +} static void test_basic_stream_operations(void) @@ -332,9 +343,9 @@ test_basic_stream_operations(void) cubeb_stream_params params; uint64_t position; - BEGIN_TEST + BEGIN_TEST; - r = cubeb_init(&ctx, "test_sanity"); + r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); params.format = STREAM_FORMAT; @@ -366,8 +377,8 @@ test_basic_stream_operations(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static void test_stream_position(void) @@ -379,9 +390,9 @@ test_stream_position(void) cubeb_stream_params params; uint64_t position, last_position; - BEGIN_TEST + BEGIN_TEST; - total_frames_written = 0; + total_frames_written = 0; r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); @@ -456,8 +467,8 @@ test_stream_position(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} static int do_drain; static int got_drain; @@ -495,9 +506,9 @@ test_drain(void) cubeb_stream_params params; uint64_t position; - BEGIN_TEST + BEGIN_TEST; - total_frames_written = 0; + total_frames_written = 0; r = cubeb_init(&ctx, "test_sanity"); assert(r == 0 && ctx); @@ -539,8 +550,8 @@ test_drain(void) cubeb_stream_destroy(stream); cubeb_destroy(ctx); - END_TEST - } + END_TEST; +} int is_windows_7() { @@ -572,7 +583,9 @@ int is_windows_7() int main(int argc, char * argv[]) { +#ifdef CUBEB_GECKO_BUILD ScopedXPCOM xpcom("test_sanity"); +#endif test_init_destroy_context(); test_init_destroy_multiple_contexts(); diff --git a/media/libcubeb/tests/test_tone.cpp b/media/libcubeb/tests/test_tone.cpp index ae50c69281b5..023c46f8550d 100644 --- a/media/libcubeb/tests/test_tone.cpp +++ b/media/libcubeb/tests/test_tone.cpp @@ -17,9 +17,16 @@ #include "cubeb/cubeb.h" #include "common.h" +#ifdef CUBEB_GECKO_BUILD #include "TestHarness.h" +#endif #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 */ struct cb_user_data { @@ -29,7 +36,12 @@ struct cb_user_data { long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes) { struct cb_user_data *u = (struct cb_user_data *)user; +#if STREAM_FORMAT == CUBEB_SAMPLE_FLOAT32LE short *b = (short *)buffer; +#else + float *b = (float *)buffer; +#endif + float t1, t2; int i; if (stream == NULL || u == NULL) @@ -38,10 +50,24 @@ long data_cb(cubeb_stream *stream, void *user, void *buffer, long nframes) /* generate our test tone on the fly */ for (i = 0; i < nframes; i++) { /* North American dial tone */ - b[i] = 16000*sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY); - b[i] += 16000*sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY); + t1 = sin(2*M_PI*(i + u->position)*350/SAMPLE_FREQUENCY); + t2 = sin(2*M_PI*(i + u->position)*440/SAMPLE_FREQUENCY); +#if STREAM_FORMAT == CUBEB_SAMPLE_FLOAT32LE + 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 */ - /*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 STREAM_FORMAT == CUBEB_SAMPLE_FLOAT32LE + b[i] = t1; +#else + b[i] = SHRT_MAX * t1; +#endif + */ } /* remember our phase to avoid clicking on buffer transitions */ /* we'll still click if position overflows */ @@ -73,7 +99,9 @@ void state_cb(cubeb_stream *stream, void *user, cubeb_state state) int main(int argc, char *argv[]) { +#ifdef CUBEB_GECKO_BUILD ScopedXPCOM xpcom("test_tone"); +#endif cubeb *ctx; cubeb_stream *stream; @@ -87,7 +115,7 @@ int main(int argc, char *argv[]) return r; } - params.format = CUBEB_SAMPLE_S16NE; + params.format = STREAM_FORMAT; params.rate = SAMPLE_FREQUENCY; params.channels = 1;