WASAPI: sine wave example works with raw device

This commit is contained in:
Andrew Kelley 2015-08-21 17:11:23 -07:00
parent dd7a6a8bbc
commit 90fa377c99
6 changed files with 167 additions and 31 deletions

View file

@ -242,7 +242,23 @@ cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/mxe/usr/x86_64-w64-mingw32.static/share
make make
``` ```
#### Running the Tests ### Testing
For each backend, do the following:
0. Run the unit tests: `./unit_tests`. To see test coverage, install lcov, run
`make coverage`, and then view `coverage/index.html` in a browser.
0. Run the example `./sio_list_devices` and make sure it does not crash, and
the output looks good. If valgrind is available, use it.
0. Run `./sio_list_devices --watch` and make sure it detects when you plug and
unplug a USB microphone.
0. Run `./sio_sine` and make sure you hear a sine wave. For backends with raw
devices, run `./sio_sine --device id` (where 'id' is a device id you got
from `sio_list_devices` and make sure you hear a sine wave.
0. Run `./underflow` and read the testing instructions that it prints.
0. Run `./sio_microphone` and ensure that it is both recording and playing
back correctly. If possible use the `--in-device` and `--out-device`
parameters to test a USB microphone in raw mode.
``` ```
make test make test

View file

@ -11,6 +11,7 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdint.h>
#include <math.h> #include <math.h>
static void panic(const char *format, ...) { static void panic(const char *format, ...) {
@ -28,11 +29,36 @@ static int usage(char *exe) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
static const float PI = 3.1415926535f; static void write_sample_s16ne(char *ptr, double sample) {
static float seconds_offset = 0.0f; int16_t *buf = (int16_t *)ptr;
double range = (double)INT16_MAX - (double)INT16_MIN;
double val = sample * range / 2.0;
*buf = val;
}
static void write_sample_s32ne(char *ptr, double sample) {
int32_t *buf = (int32_t *)ptr;
double range = (double)INT32_MAX - (double)INT32_MIN;
double val = sample * range / 2.0;
*buf = val;
}
static void write_sample_float32ne(char *ptr, double sample) {
float *buf = (float *)ptr;
*buf = sample;
}
static void write_sample_float64ne(char *ptr, double sample) {
double *buf = (double *)ptr;
*buf = sample;
}
static void (*write_sample)(char *ptr, double sample);
static const double PI = 3.14159265358979323846264338328;
static double seconds_offset = 0.0;
static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) {
float float_sample_rate = outstream->sample_rate; double float_sample_rate = outstream->sample_rate;
float seconds_per_frame = 1.0f / float_sample_rate; double seconds_per_frame = 1.0f / float_sample_rate;
struct SoundIoChannelArea *areas; struct SoundIoChannelArea *areas;
int err; int err;
@ -48,13 +74,13 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m
const struct SoundIoChannelLayout *layout = &outstream->layout; const struct SoundIoChannelLayout *layout = &outstream->layout;
float pitch = 440.0f; double pitch = 440.0;
float radians_per_second = pitch * 2.0f * PI; double radians_per_second = pitch * 2.0 * PI;
for (int frame = 0; frame < frame_count; frame += 1) { for (int frame = 0; frame < frame_count; frame += 1) {
float sample = sinf((seconds_offset + frame * seconds_per_frame) * radians_per_second); double sample = sinf((seconds_offset + frame * seconds_per_frame) * radians_per_second);
for (int channel = 0; channel < layout->channel_count; channel += 1) { for (int channel = 0; channel < layout->channel_count; channel += 1) {
float *ptr = (float*)(areas[channel].ptr + areas[channel].step * frame); write_sample(areas[channel].ptr, sample);
*ptr = sample; areas[channel].ptr += areas[channel].step;
} }
} }
seconds_offset += seconds_per_frame * frame_count; seconds_offset += seconds_per_frame * frame_count;
@ -158,15 +184,32 @@ int main(int argc, char **argv) {
fprintf(stderr, "Output device: %s\n", device->name); fprintf(stderr, "Output device: %s\n", device->name);
struct SoundIoOutStream *outstream = soundio_outstream_create(device); struct SoundIoOutStream *outstream = soundio_outstream_create(device);
outstream->format = SoundIoFormatFloat32NE;
outstream->write_callback = write_callback; outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback; outstream->underflow_callback = underflow_callback;
if (soundio_device_supports_format(device, SoundIoFormatFloat32NE)) {
outstream->format = SoundIoFormatFloat32NE;
write_sample = write_sample_float32ne;
} else if (soundio_device_supports_format(device, SoundIoFormatFloat64NE)) {
outstream->format = SoundIoFormatFloat64NE;
write_sample = write_sample_float64ne;
} else if (soundio_device_supports_format(device, SoundIoFormatS32NE)) {
outstream->format = SoundIoFormatS32NE;
write_sample = write_sample_s32ne;
} else if (soundio_device_supports_format(device, SoundIoFormatS16NE)) {
outstream->format = SoundIoFormatS16NE;
write_sample = write_sample_s16ne;
} else {
fprintf(stderr, "No suitable device format available.\n");
return EXIT_FAILURE;
}
if ((err = soundio_outstream_open(outstream))) { if ((err = soundio_outstream_open(outstream))) {
fprintf(stderr, "unable to open device: %s", soundio_strerror(err)); fprintf(stderr, "unable to open device: %s", soundio_strerror(err));
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (outstream->layout_error) if (outstream->layout_error)
fprintf(stderr, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error)); fprintf(stderr, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error));

View file

@ -454,9 +454,6 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) {
if (device->aim != SoundIoDeviceAimOutput) if (device->aim != SoundIoDeviceAimOutput)
return SoundIoErrorInvalid; return SoundIoErrorInvalid;
if (outstream->format <= SoundIoFormatInvalid)
return SoundIoErrorInvalid;
if (device->probe_error) if (device->probe_error)
return device->probe_error; return device->probe_error;
@ -465,6 +462,9 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) {
SoundIoFormatFloat32NE : device->formats[0]; SoundIoFormatFloat32NE : device->formats[0];
} }
if (outstream->format <= SoundIoFormatInvalid)
return SoundIoErrorInvalid;
if (!outstream->layout.channel_count) { if (!outstream->layout.channel_count) {
const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo);
outstream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0]; outstream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0];

View file

@ -18,8 +18,6 @@ const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = { const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; 0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
const static DWORD SOUNDIO_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000;
// Adding more common sample rates helps the heuristics; feel free to do that. // Adding more common sample rates helps the heuristics; feel free to do that.
static int test_sample_rates[] = { static int test_sample_rates[] = {
8000, 8000,
@ -64,6 +62,27 @@ static SoundIoChannelLayoutId test_layouts[] = {
SoundIoChannelLayoutId5Point1Back, SoundIoChannelLayoutId5Point1Back,
}; };
/*
static void print_possible_err_codes() {
fprintf(stderr, "AUDCLNT_E_ALREADY_INITIALIZED: %ld\n", AUDCLNT_E_ALREADY_INITIALIZED);
fprintf(stderr, "AUDCLNT_E_WRONG_ENDPOINT_TYPE: %ld\n", AUDCLNT_E_WRONG_ENDPOINT_TYPE);
fprintf(stderr, "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: %ld\n", AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED);
fprintf(stderr, "AUDCLNT_E_BUFFER_SIZE_ERROR: %ld\n", AUDCLNT_E_BUFFER_SIZE_ERROR);
fprintf(stderr, "AUDCLNT_E_CPUUSAGE_EXCEEDED: %ld\n", AUDCLNT_E_CPUUSAGE_EXCEEDED);
fprintf(stderr, "AUDCLNT_E_DEVICE_INVALIDATED: %ld\n", AUDCLNT_E_DEVICE_INVALIDATED);
fprintf(stderr, "AUDCLNT_E_DEVICE_IN_USE: %ld\n", AUDCLNT_E_DEVICE_IN_USE);
fprintf(stderr, "AUDCLNT_E_ENDPOINT_CREATE_FAILED: %ld\n", AUDCLNT_E_ENDPOINT_CREATE_FAILED);
fprintf(stderr, "AUDCLNT_E_INVALID_DEVICE_PERIOD: %ld\n", AUDCLNT_E_INVALID_DEVICE_PERIOD);
fprintf(stderr, "AUDCLNT_E_UNSUPPORTED_FORMAT: %ld\n", AUDCLNT_E_UNSUPPORTED_FORMAT);
fprintf(stderr, "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: %ld\n", AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED);
fprintf(stderr, "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: %ld\n", AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL);
fprintf(stderr, "AUDCLNT_E_SERVICE_NOT_RUNNING: %ld\n", AUDCLNT_E_SERVICE_NOT_RUNNING);
fprintf(stderr, "E_POINTER: %ld\n", E_POINTER);
fprintf(stderr, "E_INVALIDARG: %ld\n", E_INVALIDARG);
fprintf(stderr, "E_OUTOFMEMORY: %ld\n", E_OUTOFMEMORY);
}
*/
// converts a windows wide string to a UTF-8 encoded char * // converts a windows wide string to a UTF-8 encoded char *
// Possible errors: // Possible errors:
// * SoundIoErrorNoMem // * SoundIoErrorNoMem
@ -611,6 +630,7 @@ static int refresh_devices(SoundIoPrivate *si) {
rd.device_shared->ref_count = 1; rd.device_shared->ref_count = 1;
rd.device_shared->soundio = soundio; rd.device_shared->soundio = soundio;
rd.device_shared->is_raw = false; rd.device_shared->is_raw = false;
rd.device_shared->software_latency_max = 2.0;
SoundIoDevicePrivate *dev_raw = allocate<SoundIoDevicePrivate>(1); SoundIoDevicePrivate *dev_raw = allocate<SoundIoDevicePrivate>(1);
if (!dev_raw) { if (!dev_raw) {
@ -624,6 +644,7 @@ static int refresh_devices(SoundIoPrivate *si) {
rd.device_raw->ref_count = 1; rd.device_raw->ref_count = 1;
rd.device_raw->soundio = soundio; rd.device_raw->soundio = soundio;
rd.device_raw->is_raw = true; rd.device_raw->is_raw = true;
rd.device_raw->software_latency_max = 0.5;
int device_id_len; int device_id_len;
if ((err = from_lpwstr(rd.lpwstr, &rd.device_shared->id, &device_id_len))) { if ((err = from_lpwstr(rd.lpwstr, &rd.device_shared->id, &device_id_len))) {
@ -657,7 +678,10 @@ static int refresh_devices(SoundIoPrivate *si) {
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
dev_w_shared->period_duration = from_reference_time(default_device_period); dev_w_shared->period_duration = from_reference_time(default_device_period);
rd.device_shared->software_latency_current = dev_w_shared->period_duration;
dev_w_raw->period_duration = from_reference_time(min_device_period); dev_w_raw->period_duration = from_reference_time(min_device_period);
rd.device_raw->software_latency_min = dev_w_raw->period_duration * 2;
if (rd.endpoint) { if (rd.endpoint) {
@ -954,9 +978,14 @@ static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOu
if (osw->thread) { if (osw->thread) {
osw->thread_exit_flag.clear(); osw->thread_exit_flag.clear();
soundio_os_mutex_lock(osw->mutex); if (osw->is_raw) {
soundio_os_cond_signal(osw->cond, osw->mutex); if (osw->h_event)
soundio_os_mutex_unlock(osw->mutex); SetEvent(osw->h_event);
} else {
soundio_os_mutex_lock(osw->mutex);
soundio_os_cond_signal(osw->cond, osw->mutex);
soundio_os_mutex_unlock(osw->mutex);
}
soundio_os_thread_destroy(osw->thread); soundio_os_thread_destroy(osw->thread);
} }
@ -966,6 +995,8 @@ static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOu
IUnknown_Release(osw->audio_clock_adjustment); IUnknown_Release(osw->audio_clock_adjustment);
if (osw->audio_client) if (osw->audio_client)
IUnknown_Release(osw->audio_client); IUnknown_Release(osw->audio_client);
if (osw->h_event)
CloseHandle(osw->h_event);
soundio_os_cond_destroy(osw->cond); soundio_os_cond_destroy(osw->cond);
soundio_os_mutex_destroy(osw->mutex); soundio_os_mutex_destroy(osw->mutex);
@ -1029,7 +1060,6 @@ static int outstream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStr
CoTaskMemFree(mix_format); CoTaskMemFree(mix_format);
mix_format = nullptr; mix_format = nullptr;
osw->need_resample = (wave_format.Format.nSamplesPerSec != (DWORD)outstream->sample_rate); osw->need_resample = (wave_format.Format.nSamplesPerSec != (DWORD)outstream->sample_rate);
// TODO do we need SOUNDIO_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM ?
flags = osw->need_resample ? AUDCLNT_STREAMFLAGS_RATEADJUST : 0; flags = osw->need_resample ? AUDCLNT_STREAMFLAGS_RATEADJUST : 0;
share_mode = AUDCLNT_SHAREMODE_SHARED; share_mode = AUDCLNT_SHAREMODE_SHARED;
periodicity = 0; periodicity = 0;
@ -1065,22 +1095,35 @@ static int outstream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStr
CoTaskMemFree(mix_format); CoTaskMemFree(mix_format);
mix_format = nullptr; mix_format = nullptr;
osw->need_resample = (wave_format.Format.nSamplesPerSec != (DWORD)outstream->sample_rate); osw->need_resample = (wave_format.Format.nSamplesPerSec != (DWORD)outstream->sample_rate);
// TODO do we need SOUNDIO_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM ?
flags = osw->need_resample ? AUDCLNT_STREAMFLAGS_RATEADJUST : 0; flags = osw->need_resample ? AUDCLNT_STREAMFLAGS_RATEADJUST : 0;
to_wave_format_layout(&outstream->layout, &wave_format); to_wave_format_layout(&outstream->layout, &wave_format);
to_wave_format_format(outstream->format, &wave_format); to_wave_format_format(outstream->format, &wave_format);
complete_wave_format_data(&wave_format); complete_wave_format_data(&wave_format);
} }
buffer_duration = to_reference_time(osw->buffer_frame_count / outstream->sample_rate); buffer_duration = to_reference_time(osw->buffer_frame_count / (double)outstream->sample_rate);
if (osw->is_raw) if (osw->is_raw)
periodicity = buffer_duration; periodicity = buffer_duration;
if (FAILED(hr = IAudioClient_Initialize(osw->audio_client, share_mode, flags, if (FAILED(hr = IAudioClient_Initialize(osw->audio_client, share_mode, flags,
buffer_duration, periodicity, (WAVEFORMATEX*)&wave_format, nullptr))) buffer_duration, periodicity, (WAVEFORMATEX*)&wave_format, nullptr)))
{ {
outstream_destroy_wasapi(si, os); if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
return SoundIoErrorOpeningDevice; outstream_destroy_wasapi(si, os);
return SoundIoErrorIncompatibleDevice;
} else if (hr == E_OUTOFMEMORY) {
outstream_destroy_wasapi(si, os);
return SoundIoErrorNoMem;
} else {
outstream_destroy_wasapi(si, os);
return SoundIoErrorOpeningDevice;
}
} }
} else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) {
outstream_destroy_wasapi(si, os);
return SoundIoErrorIncompatibleDevice;
} else if (hr == E_OUTOFMEMORY) {
outstream_destroy_wasapi(si, os);
return SoundIoErrorNoMem;
} else { } else {
outstream_destroy_wasapi(si, os); outstream_destroy_wasapi(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
@ -1093,7 +1136,15 @@ static int outstream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStr
outstream->software_latency = osw->buffer_frame_count / (double)outstream->sample_rate; outstream->software_latency = osw->buffer_frame_count / (double)outstream->sample_rate;
if (osw->is_raw) { if (osw->is_raw) {
soundio_panic("TODO event callback"); osw->h_event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (!osw->h_event) {
outstream_destroy_wasapi(si, os);
return SoundIoErrorOpeningDevice;
}
if (FAILED(hr = IAudioClient_SetEventHandle(osw->audio_client, osw->h_event))) {
outstream_destroy_wasapi(si, os);
return SoundIoErrorOpeningDevice;
}
} else if (osw->need_resample) { } else if (osw->need_resample) {
if (FAILED(hr = IAudioClient_GetService(osw->audio_client, IID_IAudioClockAdjustment, if (FAILED(hr = IAudioClient_GetService(osw->audio_client, IID_IAudioClockAdjustment,
(void**)&osw->audio_clock_adjustment))) (void**)&osw->audio_clock_adjustment)))
@ -1181,16 +1232,40 @@ void outstream_shared_run(void *arg) {
} }
} }
void outstream_raw_run(void *arg) {
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *) arg;
SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi;
SoundIoOutStream *outstream = &os->pub;
HRESULT hr;
outstream->write_callback(outstream, osw->buffer_frame_count, osw->buffer_frame_count);
if (FAILED(hr = IAudioClient_Start(osw->audio_client))) {
outstream->error_callback(outstream, SoundIoErrorStreaming);
return;
}
for (;;) {
WaitForSingleObject(osw->h_event, INFINITE);
if (!osw->thread_exit_flag.test_and_set())
return;
outstream->write_callback(outstream, osw->buffer_frame_count, osw->buffer_frame_count);
}
}
static int outstream_start_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { static int outstream_start_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi; SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi;
int err; int err;
if (osw->is_raw) { assert(!osw->thread);
soundio_panic("TODO start raw"); osw->thread_exit_flag.test_and_set();
} else {
assert(!osw->thread);
osw->thread_exit_flag.test_and_set(); if (osw->is_raw) {
if ((err = soundio_os_thread_create(outstream_raw_run, os, true, &osw->thread)))
return err;
} else {
if ((err = soundio_os_thread_create(outstream_shared_run, os, true, &osw->thread))) if ((err = soundio_os_thread_create(outstream_shared_run, os, true, &osw->thread)))
return err; return err;
} }

View file

@ -62,6 +62,7 @@ struct SoundIoOutStreamWasapi {
int writable_frame_count; int writable_frame_count;
UINT32 buffer_frame_count; UINT32 buffer_frame_count;
int write_frame_count; int write_frame_count;
HANDLE h_event;
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
}; };

View file

@ -113,7 +113,8 @@ int main(int argc, char **argv) {
fprintf(stderr, "You should hear a sine wave for 3 seconds, then some period of silence or glitches,\n" fprintf(stderr, "You should hear a sine wave for 3 seconds, then some period of silence or glitches,\n"
"then you should see at least one buffer underflow message, then hear a sine\n" "then you should see at least one buffer underflow message, then hear a sine\n"
"wave for 3 seconds, then the program should exit successfully.\n"); "wave for 3 seconds, then the program should exit successfully.\n"
"WASAPI does not report buffer underflows.\n");
if (!(soundio = soundio_create())) if (!(soundio = soundio_create()))
panic("out of memory"); panic("out of memory");