From 59fca8cb753ea0444b63ef2a175b9e8ba9a998b8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 2 Sep 2015 12:21:41 -0700 Subject: [PATCH] latency test emits periodic pulses See #2 --- CMakeLists.txt | 90 +++++++------------- soundio/soundio.h | 9 +- test/latency.cpp | 206 ++++++++++++++++++++++++++++++++++++++++++++++ test/underflow.c | 2 +- 4 files changed, 242 insertions(+), 65 deletions(-) create mode 100644 test/latency.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4262ef5..6e6998c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,57 +147,51 @@ set(LIBSOUNDIO_HEADERS ${CONFIGURE_OUT_FILE} ) -set(TEST_SOURCES - "${CMAKE_SOURCE_DIR}/test/unit_tests.cpp" - "${CMAKE_SOURCE_DIR}/src/util.cpp" - "${CMAKE_SOURCE_DIR}/src/os.cpp" - "${CMAKE_SOURCE_DIR}/src/soundio.cpp" - "${CMAKE_SOURCE_DIR}/src/dummy.cpp" - "${CMAKE_SOURCE_DIR}/src/channel_layout.cpp" - "${CMAKE_SOURCE_DIR}/src/ring_buffer.cpp" -) - if(SOUNDIO_HAVE_JACK) set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES} "${CMAKE_SOURCE_DIR}/src/jack.cpp" ) - set(TEST_SOURCES ${TEST_SOURCES} - "${CMAKE_SOURCE_DIR}/src/jack.cpp" - ) endif() if(SOUNDIO_HAVE_PULSEAUDIO) set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES} "${CMAKE_SOURCE_DIR}/src/pulseaudio.cpp" ) - set(TEST_SOURCES ${TEST_SOURCES} - "${CMAKE_SOURCE_DIR}/src/pulseaudio.cpp" - ) endif() if(SOUNDIO_HAVE_ALSA) set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES} "${CMAKE_SOURCE_DIR}/src/alsa.cpp" ) - set(TEST_SOURCES ${TEST_SOURCES} - "${CMAKE_SOURCE_DIR}/src/alsa.cpp" - ) endif() if(SOUNDIO_HAVE_COREAUDIO) set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES} "${CMAKE_SOURCE_DIR}/src/coreaudio.cpp" ) - set(TEST_SOURCES ${TEST_SOURCES} - "${CMAKE_SOURCE_DIR}/src/coreaudio.cpp" - ) endif() if(SOUNDIO_HAVE_WASAPI) set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES} "${CMAKE_SOURCE_DIR}/src/wasapi.cpp" ) - set(TEST_SOURCES ${TEST_SOURCES} - "${CMAKE_SOURCE_DIR}/src/wasapi.cpp" - ) endif() +include_directories( + ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + "${CMAKE_SOURCE_DIR}/test" + "${CMAKE_SOURCE_DIR}/src" +) + +set(LIBSOUNDIO_LIBS + ${JACK_LIBRARY} + ${PULSEAUDIO_LIBRARY} + ${ALSA_LIBRARIES} + ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} + ${AUDIOUNIT_LIBRARY} + m + ${CMAKE_THREAD_LIBS_INIT} +) + + # GTFO, -lstdc++ !! set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") @@ -209,10 +203,8 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -pedantic") set(LIB_CFLAGS "-std=c++11 -fno-exceptions -fno-rtti -fvisibility=hidden -Wall -Werror=strict-prototypes -Werror=old-style-definition -Werror=missing-prototypes -Wno-c99-extensions") set(EXAMPLE_CFLAGS "-std=c99 -Wall") -set(EXAMPLE_INCLUDES "${CMAKE_SOURCE_DIR}/src") set(TEST_CFLAGS "${LIB_CFLAGS} -fprofile-arcs -ftest-coverage") set(TEST_LDFLAGS "-fprofile-arcs -ftest-coverage") -set(TEST_INCLUDES "${CMAKE_SOURCE_DIR}/test") include(TestBigEndian) test_big_endian(IS_BIG_ENDIAN) @@ -241,20 +233,7 @@ set_target_properties(libsoundio_shared PROPERTIES COMPILE_FLAGS ${LIB_CFLAGS} LINKER_LANGUAGE C ) -include_directories( - ${CMAKE_SOURCE_DIR} - ${CMAKE_BINARY_DIR} -) -target_link_libraries(libsoundio_shared LINK_PUBLIC - ${JACK_LIBRARY} - ${PULSEAUDIO_LIBRARY} - ${ALSA_LIBRARIES} - ${COREAUDIO_LIBRARY} - ${COREFOUNDATION_LIBRARY} - ${AUDIOUNIT_LIBRARY} - m - ${CMAKE_THREAD_LIBS_INIT} -) +target_link_libraries(libsoundio_shared LINK_PUBLIC ${LIBSOUNDIO_LIBS}) install(TARGETS libsoundio_shared DESTINATION ${CMAKE_INSTALL_LIBDIR}) @@ -276,7 +255,6 @@ add_executable(sio_sine example/sio_sine.c) set_target_properties(sio_sine PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) -include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(sio_sine libsoundio_shared) install(TARGETS sio_sine DESTINATION ${CMAKE_INSTALL_BINDIR}) @@ -284,7 +262,6 @@ add_executable(sio_list_devices example/sio_list_devices.c) set_target_properties(sio_list_devices PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) -include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(sio_list_devices libsoundio_shared) install(TARGETS sio_list_devices DESTINATION ${CMAKE_INSTALL_BINDIR}) @@ -292,7 +269,6 @@ add_executable(sio_microphone example/sio_microphone.c) set_target_properties(sio_microphone PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) -include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(sio_microphone libsoundio_shared) install(TARGETS sio_microphone DESTINATION ${CMAKE_INSTALL_BINDIR}) @@ -300,49 +276,41 @@ add_executable(sio_record example/sio_record.c) set_target_properties(sio_record PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) -include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(sio_record libsoundio_shared) install(TARGETS sio_record DESTINATION ${CMAKE_INSTALL_BINDIR}) - -add_executable(unit_tests ${TEST_SOURCES}) -target_link_libraries(unit_tests LINK_PUBLIC - ${CMAKE_THREAD_LIBS_INIT} - ${JACK_LIBRARY} - ${PULSEAUDIO_LIBRARY} - ${ALSA_LIBRARIES} - ${COREAUDIO_LIBRARY} - ${COREFOUNDATION_LIBRARY} - ${AUDIOUNIT_LIBRARY} - m -) +add_executable(unit_tests "${CMAKE_SOURCE_DIR}/test/unit_tests.cpp" ${LIBSOUNDIO_SOURCES}) +target_link_libraries(unit_tests LINK_PUBLIC ${LIBSOUNDIO_LIBS}) set_target_properties(unit_tests PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${TEST_CFLAGS} LINK_FLAGS ${TEST_LDFLAGS} ) -include_directories(${TEST_INCLUDES}) + +add_executable(latency "${CMAKE_SOURCE_DIR}/test/latency.cpp" ${LIBSOUNDIO_SOURCES}) +target_link_libraries(latency LINK_PUBLIC ${LIBSOUNDIO_LIBS}) +set_target_properties(latency PROPERTIES + LINKER_LANGUAGE C + COMPILE_FLAGS ${LIB_CFLAGS} +) add_executable(underflow test/underflow.c) set_target_properties(underflow PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) -include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(underflow libsoundio_shared) add_executable(backend_disconnect_recover test/backend_disconnect_recover.c) set_target_properties(backend_disconnect_recover PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) -include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(backend_disconnect_recover libsoundio_shared) add_executable(overflow test/overflow.c) set_target_properties(overflow PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) -include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(overflow libsoundio_shared) diff --git a/soundio/soundio.h b/soundio/soundio.h index 03ed33d..2f4994f 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -1115,7 +1115,10 @@ SOUNDIO_EXPORT int soundio_instream_get_latency(struct SoundIoInStream *instream double *out_latency); -// Ring Buffer +/// A ring buffer is a single-reader single-writer lock-free fixed-size queue. +/// libsoundio ring buffers use memory mapping techniques to enable a +/// contiguous buffer when reading or writing across the boundary of the ring +/// buffer's capacity. struct SoundIoRingBuffer; /// `requested_capacity` in bytes. /// Returns `NULL` if and only if memory could not be allocated. @@ -1129,12 +1132,12 @@ SOUNDIO_EXPORT void soundio_ring_buffer_destroy(struct SoundIoRingBuffer *ring_b /// capacity for alignment purposes. This function returns the actual capacity. SOUNDIO_EXPORT int soundio_ring_buffer_capacity(struct SoundIoRingBuffer *ring_buffer); -/// don't write more than capacity +/// Do not write more than capacity. SOUNDIO_EXPORT char *soundio_ring_buffer_write_ptr(struct SoundIoRingBuffer *ring_buffer); /// `count` in bytes. SOUNDIO_EXPORT void soundio_ring_buffer_advance_write_ptr(struct SoundIoRingBuffer *ring_buffer, int count); -/// don't read more than capacity +/// Do not read more than capacity. SOUNDIO_EXPORT char *soundio_ring_buffer_read_ptr(struct SoundIoRingBuffer *ring_buffer); /// `count` in bytes. SOUNDIO_EXPORT void soundio_ring_buffer_advance_read_ptr(struct SoundIoRingBuffer *ring_buffer, int count); diff --git a/test/latency.cpp b/test/latency.cpp new file mode 100644 index 0000000..bd90e7d --- /dev/null +++ b/test/latency.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "soundio.hpp" +#include "os.h" +#include "util.hpp" +#include "atomics.hpp" + +#include +#include +#include +#include + +static int usage(char *exe) { + fprintf(stderr, "Usage: %s [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n", exe); + return 1; +} + +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 int frames_until_pulse = 0; +static int pulse_frames_left = 0; +static const double PI = 3.14159265358979323846264338328; +static double seconds_offset = 0.0; + +static SoundIoRingBuffer pulse_rb; + +static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { + double float_sample_rate = outstream->sample_rate; + double seconds_per_frame = 1.0f / float_sample_rate; + struct SoundIoChannelArea *areas; + int err; + + int frames_left = frame_count_max; + + while (frames_left > 0) { + int frame_count = frames_left; + + if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) + soundio_panic("begin write: %s", soundio_strerror(err)); + + if (!frame_count) + break; + + const struct SoundIoChannelLayout *layout = &outstream->layout; + + double pitch = 440.0; + double radians_per_second = pitch * 2.0 * PI; + for (int frame = 0; frame < frame_count; frame += 1) { + double sample; + if (frames_until_pulse <= 0) { + if (pulse_frames_left <= 0) { + frames_until_pulse = (1.0 + (rand() / (double)RAND_MAX) * 3.0) * float_sample_rate; + pulse_frames_left = 0.05 * float_sample_rate; + sample = 0.0; + } else { + pulse_frames_left -= 1; + sample = sinf((seconds_offset + frame * seconds_per_frame) * radians_per_second); + } + } else { + frames_until_pulse -= 1; + sample = 0.0; + } + for (int channel = 0; channel < layout->channel_count; channel += 1) { + write_sample(areas[channel].ptr, sample); + areas[channel].ptr += areas[channel].step; + } + } + + seconds_offset += seconds_per_frame * frame_count; + + if ((err = soundio_outstream_end_write(outstream))) + soundio_panic("end write: %s", soundio_strerror(err)); + + frames_left -= frame_count; + } +} + +static void underflow_callback(struct SoundIoOutStream *outstream) { + static int count = 0; + fprintf(stderr, "underflow %d\n", count++); +} + +int main(int argc, char **argv) { + char *exe = argv[0]; + enum SoundIoBackend backend = SoundIoBackendNone; + for (int i = 1; i < argc; i += 1) { + char *arg = argv[i]; + if (arg[0] == '-' && arg[1] == '-') { + i += 1; + if (i >= argc) { + return usage(exe); + } else if (strcmp(arg, "--backend") == 0) { + if (strcmp("dummy", argv[i]) == 0) { + backend = SoundIoBackendDummy; + } else if (strcmp("alsa", argv[i]) == 0) { + backend = SoundIoBackendAlsa; + } else if (strcmp("pulseaudio", argv[i]) == 0) { + backend = SoundIoBackendPulseAudio; + } else if (strcmp("jack", argv[i]) == 0) { + backend = SoundIoBackendJack; + } else if (strcmp("coreaudio", argv[i]) == 0) { + backend = SoundIoBackendCoreAudio; + } else if (strcmp("wasapi", argv[i]) == 0) { + backend = SoundIoBackendWasapi; + } else { + fprintf(stderr, "Invalid backend: %s\n", argv[i]); + return 1; + } + } else { + return usage(exe); + } + } else { + return usage(exe); + } + } + + struct SoundIo *soundio; + if (!(soundio = soundio_create())) + soundio_panic("out of memory"); + + int err = (backend == SoundIoBackendNone) ? + soundio_connect(soundio) : soundio_connect_backend(soundio, backend); + + if (err) + soundio_panic("error connecting: %s", soundio_strerror(err)); + + soundio_flush_events(soundio); + + int default_out_device_index = soundio_default_output_device_index(soundio); + if (default_out_device_index < 0) + soundio_panic("no output device found"); + + struct SoundIoDevice *device = soundio_get_output_device(soundio, default_out_device_index); + if (!device) + soundio_panic("out of memory"); + + 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 { + soundio_panic("No suitable device format available.\n"); + } + + if ((err = soundio_outstream_open(outstream))) + soundio_panic("unable to open device: %s", soundio_strerror(err)); + + if (outstream->layout_error) + fprintf(stderr, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error)); + + if ((err = soundio_outstream_start(outstream))) + soundio_panic("unable to start device: %s", soundio_strerror(err)); + + for (;;) + soundio_wait_events(soundio); + + soundio_outstream_destroy(outstream); + soundio_device_unref(device); + soundio_destroy(soundio); + return 0; +} + diff --git a/test/underflow.c b/test/underflow.c index 2ac0d15..f907c6d 100644 --- a/test/underflow.c +++ b/test/underflow.c @@ -103,7 +103,7 @@ int main(int argc, char **argv) { if (i >= argc) { return usage(exe); } else if (strcmp(arg, "--backend") == 0) { - if (strcmp("-dummy", argv[i]) == 0) { + if (strcmp("dummy", argv[i]) == 0) { backend = SoundIoBackendDummy; } else if (strcmp("alsa", argv[i]) == 0) { backend = SoundIoBackendAlsa;