mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2024-12-22 22:35:28 +00:00
WASAPI: sine wave example works with raw device
This commit is contained in:
parent
dd7a6a8bbc
commit
90fa377c99
18
README.md
18
README.md
|
@ -242,7 +242,23 @@ cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/mxe/usr/x86_64-w64-mingw32.static/share
|
|||
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
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
static void panic(const char *format, ...) {
|
||||
|
@ -28,11 +29,36 @@ static int usage(char *exe) {
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
static const float PI = 3.1415926535f;
|
||||
static float seconds_offset = 0.0f;
|
||||
static void write_sample_s16ne(char *ptr, double sample) {
|
||||
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) {
|
||||
float float_sample_rate = outstream->sample_rate;
|
||||
float seconds_per_frame = 1.0f / float_sample_rate;
|
||||
double float_sample_rate = outstream->sample_rate;
|
||||
double seconds_per_frame = 1.0f / float_sample_rate;
|
||||
struct SoundIoChannelArea *areas;
|
||||
int err;
|
||||
|
||||
|
@ -48,13 +74,13 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m
|
|||
|
||||
const struct SoundIoChannelLayout *layout = &outstream->layout;
|
||||
|
||||
float pitch = 440.0f;
|
||||
float radians_per_second = pitch * 2.0f * PI;
|
||||
double pitch = 440.0;
|
||||
double radians_per_second = pitch * 2.0 * PI;
|
||||
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) {
|
||||
float *ptr = (float*)(areas[channel].ptr + areas[channel].step * frame);
|
||||
*ptr = sample;
|
||||
write_sample(areas[channel].ptr, sample);
|
||||
areas[channel].ptr += areas[channel].step;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
struct SoundIoOutStream *outstream = soundio_outstream_create(device);
|
||||
outstream->format = SoundIoFormatFloat32NE;
|
||||
outstream->write_callback = write_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))) {
|
||||
fprintf(stderr, "unable to open device: %s", soundio_strerror(err));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
if (outstream->layout_error)
|
||||
fprintf(stderr, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error));
|
||||
|
||||
|
|
|
@ -454,9 +454,6 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) {
|
|||
if (device->aim != SoundIoDeviceAimOutput)
|
||||
return SoundIoErrorInvalid;
|
||||
|
||||
if (outstream->format <= SoundIoFormatInvalid)
|
||||
return SoundIoErrorInvalid;
|
||||
|
||||
if (device->probe_error)
|
||||
return device->probe_error;
|
||||
|
||||
|
@ -465,6 +462,9 @@ int soundio_outstream_open(struct SoundIoOutStream *outstream) {
|
|||
SoundIoFormatFloat32NE : device->formats[0];
|
||||
}
|
||||
|
||||
if (outstream->format <= SoundIoFormatInvalid)
|
||||
return SoundIoErrorInvalid;
|
||||
|
||||
if (!outstream->layout.channel_count) {
|
||||
const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo);
|
||||
outstream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0];
|
||||
|
|
|
@ -18,8 +18,6 @@ const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
|
|||
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = {
|
||||
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.
|
||||
static int test_sample_rates[] = {
|
||||
8000,
|
||||
|
@ -64,6 +62,27 @@ static SoundIoChannelLayoutId test_layouts[] = {
|
|||
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 *
|
||||
// Possible errors:
|
||||
// * SoundIoErrorNoMem
|
||||
|
@ -611,6 +630,7 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
rd.device_shared->ref_count = 1;
|
||||
rd.device_shared->soundio = soundio;
|
||||
rd.device_shared->is_raw = false;
|
||||
rd.device_shared->software_latency_max = 2.0;
|
||||
|
||||
SoundIoDevicePrivate *dev_raw = allocate<SoundIoDevicePrivate>(1);
|
||||
if (!dev_raw) {
|
||||
|
@ -624,6 +644,7 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
rd.device_raw->ref_count = 1;
|
||||
rd.device_raw->soundio = soundio;
|
||||
rd.device_raw->is_raw = true;
|
||||
rd.device_raw->software_latency_max = 0.5;
|
||||
|
||||
int 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;
|
||||
}
|
||||
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);
|
||||
rd.device_raw->software_latency_min = dev_w_raw->period_duration * 2;
|
||||
|
||||
|
||||
if (rd.endpoint) {
|
||||
|
@ -954,9 +978,14 @@ static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOu
|
|||
|
||||
if (osw->thread) {
|
||||
osw->thread_exit_flag.clear();
|
||||
if (osw->is_raw) {
|
||||
if (osw->h_event)
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -966,6 +995,8 @@ static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOu
|
|||
IUnknown_Release(osw->audio_clock_adjustment);
|
||||
if (osw->audio_client)
|
||||
IUnknown_Release(osw->audio_client);
|
||||
if (osw->h_event)
|
||||
CloseHandle(osw->h_event);
|
||||
|
||||
soundio_os_cond_destroy(osw->cond);
|
||||
soundio_os_mutex_destroy(osw->mutex);
|
||||
|
@ -1029,7 +1060,6 @@ static int outstream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStr
|
|||
CoTaskMemFree(mix_format);
|
||||
mix_format = nullptr;
|
||||
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;
|
||||
share_mode = AUDCLNT_SHAREMODE_SHARED;
|
||||
periodicity = 0;
|
||||
|
@ -1065,22 +1095,35 @@ static int outstream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStr
|
|||
CoTaskMemFree(mix_format);
|
||||
mix_format = nullptr;
|
||||
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;
|
||||
to_wave_format_layout(&outstream->layout, &wave_format);
|
||||
to_wave_format_format(outstream->format, &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)
|
||||
periodicity = buffer_duration;
|
||||
if (FAILED(hr = IAudioClient_Initialize(osw->audio_client, share_mode, flags,
|
||||
buffer_duration, periodicity, (WAVEFORMATEX*)&wave_format, nullptr)))
|
||||
{
|
||||
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 {
|
||||
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 {
|
||||
outstream_destroy_wasapi(si, os);
|
||||
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;
|
||||
|
||||
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) {
|
||||
if (FAILED(hr = IAudioClient_GetService(osw->audio_client, IID_IAudioClockAdjustment,
|
||||
(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) {
|
||||
SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi;
|
||||
int err;
|
||||
|
||||
if (osw->is_raw) {
|
||||
soundio_panic("TODO start raw");
|
||||
} 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)))
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ struct SoundIoOutStreamWasapi {
|
|||
int writable_frame_count;
|
||||
UINT32 buffer_frame_count;
|
||||
int write_frame_count;
|
||||
HANDLE h_event;
|
||||
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
|
||||
};
|
||||
|
||||
|
|
|
@ -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"
|
||||
"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()))
|
||||
panic("out of memory");
|
||||
|
|
Loading…
Reference in a new issue