From e409400f04e77b613c2b74d81fc6365a7ccba9ee Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Jul 2015 01:02:44 -0700 Subject: [PATCH] extracted code from Genesis --- .ycm_extra_conf.py | 126 ------ CMakeLists.txt | 30 +- README.md | 52 ++- default.nix | 2 +- example/list_devices.c | 87 ++++ example/noise.c | 5 - example/sine.c | 12 + src/dummy.cpp | 281 +++++++++++++ src/dummy.hpp | 34 ++ src/dummy_ring_buffer.cpp | 75 ++++ src/dummy_ring_buffer.hpp | 38 ++ src/list.hpp | 143 +++++++ src/os.cpp | 251 +++++++++++ src/os.hpp | 39 ++ src/pulseaudio.cpp | 865 ++++++++++++++++++++++++++++++++++++++ src/pulseaudio.hpp | 54 +++ src/soundio.c | 1 - src/soundio.cpp | 474 +++++++++++++++++++++ src/soundio.h | 293 +++++++++++++ src/soundio.hpp | 21 + src/util.cpp | 21 + src/util.hpp | 102 +++++ 22 files changed, 2854 insertions(+), 152 deletions(-) delete mode 100644 .ycm_extra_conf.py create mode 100644 example/list_devices.c delete mode 100644 example/noise.c create mode 100644 example/sine.c create mode 100644 src/dummy.cpp create mode 100644 src/dummy.hpp create mode 100644 src/dummy_ring_buffer.cpp create mode 100644 src/dummy_ring_buffer.hpp create mode 100644 src/list.hpp create mode 100644 src/os.cpp create mode 100644 src/os.hpp create mode 100644 src/pulseaudio.cpp create mode 100644 src/pulseaudio.hpp delete mode 100644 src/soundio.c create mode 100644 src/soundio.cpp create mode 100644 src/soundio.hpp create mode 100644 src/util.cpp create mode 100644 src/util.hpp diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py deleted file mode 100644 index 701016f..0000000 --- a/.ycm_extra_conf.py +++ /dev/null @@ -1,126 +0,0 @@ -import os -import ycm_core -import subprocess - -def DirectoryOfThisScript(): - return os.path.dirname( os.path.abspath( __file__ ) ) - -def ExtractSearchPaths(compiler): - def find_paths(compiler, c_arg): - child = subprocess.Popen([compiler, c_arg, '-E', '-v', '-'], stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) - stderr, stdout = child.communicate(input="") - lines = stdout.split("\n") - begin_token = '#include <...> search starts here:' - end_token = 'End of search list.' - - def find_token_index(l, token): - for index, item in enumerate(l): - if item == token: - return index - raise Exception("token not found: %s" % token) - begin_index = find_token_index(lines, begin_token) - end_index = find_token_index(lines, end_token) - return map(lambda x: x.strip(), lines[begin_index+1:end_index]) - - results = find_paths(compiler, '-xc') - results.extend(find_paths(compiler, '-xc++')) - return results - -# Set this to the absolute path to the folder (NOT the file!) containing the -# compile_commands.json file to use that instead of 'flags'. See here for -# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html -# -# You can get CMake to generate this file for you by adding: -# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) -# to your CMakeLists.txt file. -# -# Most projects will NOT need to set this to anything; you can just change the -# 'flags' list of compilation flags. Notice that YCM itself uses that approach. -compilation_database_folder = os.path.join(DirectoryOfThisScript(), 'build') -compiler = os.environ['CC'] -search_paths = ExtractSearchPaths(compiler) -extra_flags = [] -for path in search_paths: - extra_flags.append('-I') - extra_flags.append(path) - -if os.path.exists( compilation_database_folder ): - database = ycm_core.CompilationDatabase( compilation_database_folder ) -else: - raise Exception("compilation database not found") - -SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] - - -def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): - if not working_directory: - return list( flags ) - new_flags = [] - make_next_absolute = False - path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] - for flag in flags: - new_flag = flag - - if make_next_absolute: - make_next_absolute = False - if not flag.startswith( '/' ): - new_flag = os.path.join( working_directory, flag ) - - for path_flag in path_flags: - if flag == path_flag: - make_next_absolute = True - break - - if flag.startswith( path_flag ): - path = flag[ len( path_flag ): ] - new_flag = path_flag + os.path.join( working_directory, path ) - break - - if new_flag: - new_flags.append( new_flag ) - return new_flags - - -def IsHeaderFile( filename ): - extension = os.path.splitext( filename )[ 1 ] - return extension in [ '.h', '.hxx', '.hpp', '.hh' ] - - -def GetCompilationInfoForFile( filename ): - # The compilation_commands.json file generated by CMake does not have entries - # for header files. So we do our best by asking the db for flags for a - # corresponding source file, if any. If one exists, the flags for that file - # should be good enough. - if IsHeaderFile( filename ): - basename = os.path.splitext( filename )[ 0 ] - for extension in SOURCE_EXTENSIONS: - replacement_file = basename + extension - if os.path.exists( replacement_file ): - compilation_info = database.GetCompilationInfoForFile( - replacement_file ) - if compilation_info.compiler_flags_: - return compilation_info - return None - return database.GetCompilationInfoForFile( filename ) - - -def FlagsForFile( filename, **kwargs ): - # Bear in mind that compilation_info.compiler_flags_ does NOT return a - # python list, but a "list-like" StringVec object - compilation_info = GetCompilationInfoForFile( filename ) - if not compilation_info: - return None - - final_flags = MakeRelativePathsInFlagsAbsolute( - compilation_info.compiler_flags_, - compilation_info.compiler_working_dir_ ) - - relative_to = DirectoryOfThisScript() - moar_flags = MakeRelativePathsInFlagsAbsolute( extra_flags, relative_to ) - - final_flags.extend(moar_flags) - - return { - 'flags': final_flags, - 'do_cache': True - } diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e28225..ce1eae6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,13 @@ cmake_minimum_required(VERSION 2.8) cmake_policy(SET CMP0042 NEW) cmake_policy(SET CMP0046 NEW) -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) endif() -project(libsoundio C) +project(libsoundio C CXX) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) set(LIBSOUNDIO_VERSION_MAJOR 0) @@ -18,7 +17,12 @@ set(LIBSOUNDIO_VERSION "${LIBSOUNDIO_VERSION_MAJOR}.${LIBSOUNDIO_VERSION_MINOR}. message("Configuring libsoundio version ${LIBSOUNDIO_VERSION}") set(LIBSOUNDIO_SOURCES - "${CMAKE_SOURCE_DIR}/src/soundio.c" + "${CMAKE_SOURCE_DIR}/src/soundio.cpp" + "${CMAKE_SOURCE_DIR}/src/util.cpp" + "${CMAKE_SOURCE_DIR}/src/os.cpp" + "${CMAKE_SOURCE_DIR}/src/dummy.cpp" + "${CMAKE_SOURCE_DIR}/src/pulseaudio.cpp" + "${CMAKE_SOURCE_DIR}/src/dummy_ring_buffer.cpp" ) set(CONFIGURE_OUT_FILE "${CMAKE_BINARY_DIR}/config.h") set(LIBSOUNDIO_HEADERS @@ -58,10 +62,15 @@ else() endif() endif() +# GTFO, -lstdc++ !! +set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") +set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-unused-variable") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wno-unused-variable") -set(LIB_CFLAGS "-std=c11 -Werror -Wall -fdiagnostics-color=auto") +set(LIB_CFLAGS "-std=c++11 -fno-exceptions -fno-rtti -Werror -Wall -Werror=strict-prototypes -Werror=old-style-definition -Werror=missing-prototypes -fdiagnostics-color=auto") set(EXAMPLE_CFLAGS "-std=c99 -pedantic -Werror -Wall") set(EXAMPLE_INCLUDES "${CMAKE_SOURCE_DIR}/src") set(TEST_CFLAGS "${LIB_CFLAGS} -fprofile-arcs -ftest-coverage") @@ -111,12 +120,19 @@ install(FILES # Example Programs -add_executable(noise example/noise.c) -set_target_properties(noise PROPERTIES +add_executable(sine example/sine.c) +set_target_properties(sine PROPERTIES LINKER_LANGUAGE C COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) -target_link_libraries(noise libsoundio_shared) +target_link_libraries(sine libsoundio_shared) + +add_executable(list_devices example/list_devices.c) +set_target_properties(list_devices PROPERTIES + LINKER_LANGUAGE C + COMPILE_FLAGS ${EXAMPLE_CFLAGS}) +include_directories(${EXAMPLE_INCLUDES}) +target_link_libraries(list_devices libsoundio_shared) diff --git a/README.md b/README.md index 08afa9c..f73e4ec 100644 --- a/README.md +++ b/README.md @@ -4,22 +4,50 @@ C library which provides cross-platform audio input and output. The API is suitable for real-time software such as digital audio workstations as well as consumer software such as music players. +This library is an abstraction; however it prioritizes performance and power +over API convenience. Features that only exist in some sound backends are +exposed. + This library is a work-in-progress. ## How It Works - * On Linux, libsoundio tries in order: JACK, PulseAudio, ALSA, Dummy. - * On OSX, libsoundio tries in order: JACK, PulseAudio, CoreAudio, Dummy. - * On Windows, libsoundio tries in order: ASIO, DirectSound, Dummy. - * On BSD, libsoundio tries in order: JACK, PulseAudio, OSS, Dummy. +libsoundio tries these backends in order. If unable to connect to that backend, +due to the backend not being installed, or the server not running, or the +platform is wrong, the next backend is tried. + + 0. JACK + 0. PulseAudio + 0. ALSA (Linux) + 0. CoreAudio (OSX) + 0. ASIO (Windows) + 0. DirectSound (Windows) + 0. OSS (BSD) + 0. Dummy + +## Contributing + +libsoundio is programmed in a tiny subset of C++: + + * No STL. + * No `new` or `delete`. + * No `class`. All fields in structs are `public`. + * No exceptions or run-time type information. + * No references. + * No linking against libstdc++. ## Roadmap - * Dummy (all) - * PulseAudio (Linux, OSX) - * ALSA (Linux) - * JACK (Linux, OSX) - * CoreAudio (OSX) - * DirectSound (Windows) - * ASIO (Windows) - * OSS (BSD) + 0. Dummy + 0. PulseAudio + 0. JACK + 0. ALSA (Linux) + 0. CoreAudio (OSX) + 0. ASIO (Windows) + 0. DirectSound (Windows) + 0. OSS (BSD) + +## Planned Uses for libsoundio + + * [Genesis](https://github.com/andrewrk/genesis) + * [libgroove](https://github.com/andrewrk/libgroove) ([Groove Basin](https://github.com/andrewrk/groovebasin)) diff --git a/default.nix b/default.nix index 3baced4..7b19f4f 100644 --- a/default.nix +++ b/default.nix @@ -4,7 +4,7 @@ with import {}; { buildInputs = [ alsaLib cmake - gcc49 + gcc5 libjack2 libpulseaudio ]; diff --git a/example/list_devices.c b/example/list_devices.c new file mode 100644 index 0000000..045c8b0 --- /dev/null +++ b/example/list_devices.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include + +#include +#include +#include + +// list or keep a watch on audio devices + +static int usage(char *exe) { + fprintf(stderr, "Usage: %s [--watch]\n", exe); + return 1; +} + +static int list_devices(struct SoundIo *soundio) { + int output_count = soundio_get_output_device_count(soundio); + int input_count = soundio_get_input_device_count(soundio); + + int default_output = soundio_get_default_output_device_index(soundio); + int default_input = soundio_get_default_input_device_index(soundio); + + for (int i = 0; i < input_count; i += 1) { + struct SoundIoDevice *device = soundio_get_input_device(soundio, i); + const char *purpose_str = "input"; + const char *default_str = (i == default_input) ? " (default)" : ""; + const char *description = soundio_audio_device_description(device); + int sample_rate = soundio_audio_device_sample_rate(device); + fprintf(stderr, "%s device: %d Hz %s%s\n", purpose_str, sample_rate, description, default_str); + soundio_audio_device_unref(device); + } + for (int i = 0; i < output_count; i += 1) { + struct SoundIoDevice *device = soundio_get_output_device(soundio, i); + + const char *purpose_str = "output"; + const char *default_str = (i == default_output) ? " (default)" : ""; + const char *description = soundio_audio_device_description(device); + int sample_rate = soundio_audio_device_sample_rate(device); + fprintf(stderr, "%s device: %d Hz %s%s\n", purpose_str, sample_rate, description, default_str); + soundio_audio_device_unref(device); + } + + fprintf(stderr, "%d devices found\n", input_count + output_count); + return 0; +} + +static void on_devices_change(struct SoundIo *soundio) { + fprintf(stderr, "devices changed\n"); + list_devices(soundio); +} + +int main(int argc, char **argv) { + char *exe = argv[0]; + bool watch = false; + + for (int i = 1; i < argc; i += 1) { + char *arg = argv[i]; + if (strcmp("--watch", arg) == 0) { + watch = true; + } else { + return usage(exe); + } + } + + int err; + struct SoundIo *soundio; + if ((err = soundio_create(&soundio))) { + fprintf(stderr, "%s\n", soundio_error_string(err)); + return err; + } + + if (watch) { + soundio->on_devices_change = on_devices_change; + for (;;) { + soundio_wait_events(soundio); + } + } else { + int err = list_devices(soundio); + soundio_destroy(soundio); + return err; + } +} diff --git a/example/noise.c b/example/noise.c deleted file mode 100644 index 4adace6..0000000 --- a/example/noise.c +++ /dev/null @@ -1,5 +0,0 @@ -#include "soundio.h" - -int main(int argc, char **argv) { - return 0; -} diff --git a/example/sine.c b/example/sine.c new file mode 100644 index 0000000..9801709 --- /dev/null +++ b/example/sine.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "soundio.h" + +int main(int argc, char **argv) { + return 0; +} diff --git a/src/dummy.cpp b/src/dummy.cpp new file mode 100644 index 0000000..166628b --- /dev/null +++ b/src/dummy.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "dummy.hpp" +#include "soundio.hpp" + +#include +#include + +static void playback_thread_run(void *arg) { + SoundIoOutputDevice *output_device = (SoundIoOutputDevice *)arg; + SoundIoDevice *device = output_device->device; + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + + double start_time = soundio_os_get_time(); + long frames_consumed = 0; + + double time_per_frame = 1.0 / (double)device->default_sample_rate; + while (opd->abort_flag.test_and_set()) { + soundio_os_mutex_lock(opd->mutex); + soundio_os_cond_timed_wait(opd->cond, opd->mutex, opd->period); + soundio_os_mutex_unlock(opd->mutex); + + double now = soundio_os_get_time(); + double total_time = now - start_time; + long total_frames = total_time / time_per_frame; + int frames_to_kill = total_frames - frames_consumed; + int fill_count = soundio_dummy_ring_buffer_fill_count(&opd->ring_buffer); + int frames_in_buffer = fill_count / output_device->bytes_per_frame; + int read_count = min(frames_to_kill, frames_in_buffer); + int frames_left = frames_to_kill - read_count; + int byte_count = read_count * output_device->bytes_per_frame; + soundio_dummy_ring_buffer_advance_read_ptr(&opd->ring_buffer, byte_count); + frames_consumed += read_count; + + if (frames_left > 0) { + output_device->underrun_callback(output_device); + } else if (read_count > 0) { + output_device->write_callback(output_device, read_count); + } + } +} + +/* +static void recording_thread_run(void *arg) { + SoundIoInputDevice *input_device = (SoundIoInputDevice *)arg; + SoundIoDevice *device = input_device->device; + SoundIo *soundio = device->soundio; + // TODO +} +*/ + +static void destroy_audio_hardware_dummy(SoundIo *soundio) { } + +static void flush_events(SoundIo *soundio) { } + +static void refresh_audio_devices(SoundIo *soundio) { } + +static void output_device_destroy_dummy(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + if (opd->thread) { + if (opd->thread) { + opd->abort_flag.clear(); + soundio_os_mutex_lock(opd->mutex); + soundio_os_cond_signal(opd->cond); + soundio_os_mutex_unlock(opd->mutex); + soundio_os_thread_destroy(opd->thread); + opd->thread = nullptr; + } + } + soundio_os_mutex_destroy(opd->mutex); + opd->mutex = nullptr; + + soundio_os_cond_destroy(opd->cond); + opd->cond = nullptr; +} + +static int output_device_init_dummy(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + SoundIoDevice *device = output_device->device; + int buffer_frame_count = output_device->latency * device->default_sample_rate; + opd->buffer_size = output_device->bytes_per_frame * buffer_frame_count; + opd->period = output_device->latency * 0.5; + + soundio_dummy_ring_buffer_init(&opd->ring_buffer, opd->buffer_size); + + opd->mutex = soundio_os_mutex_create(); + if (!opd->mutex) { + output_device_destroy_dummy(soundio, output_device); + return SoundIoErrorNoMem; + } + + opd->cond = soundio_os_cond_create(); + if (!opd->cond) { + output_device_destroy_dummy(soundio, output_device); + return SoundIoErrorNoMem; + } + + return 0; +} + +static int output_device_start_dummy(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + + soundio_output_device_fill_with_silence(output_device); + assert(soundio_dummy_ring_buffer_fill_count(&opd->ring_buffer) == opd->buffer_size); + + opd->abort_flag.test_and_set(); + int err; + if ((err = soundio_os_thread_create(playback_thread_run, output_device, true, &opd->thread))) { + return err; + } + + return 0; +} + +static int output_device_free_count_dummy(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + int fill_count = soundio_dummy_ring_buffer_fill_count(&opd->ring_buffer); + int bytes_free_count = opd->buffer_size - fill_count; + return bytes_free_count / output_device->bytes_per_frame; +} + +static void output_device_begin_write_dummy(SoundIo *soundio, + SoundIoOutputDevice *output_device, char **data, int *frame_count) +{ + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + + int byte_count = *frame_count * output_device->bytes_per_frame; + assert(byte_count <= opd->buffer_size); + *data = opd->ring_buffer.address; +} + +static void output_device_write_dummy(SoundIo *soundio, + SoundIoOutputDevice *output_device, char *data, int frame_count) +{ + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + assert(data == opd->ring_buffer.address); + int byte_count = frame_count * output_device->bytes_per_frame; + soundio_dummy_ring_buffer_advance_write_ptr(&opd->ring_buffer, byte_count); +} + +static void output_device_clear_buffer_dummy(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; + soundio_dummy_ring_buffer_clear(&opd->ring_buffer); +} + +static int input_device_init_dummy(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + // TODO + return 0; +} + +static void input_device_destroy_dummy(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + // TODO +} + +static int input_device_start_dummy(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + // TODO + return 0; +} + +static void input_device_peek_dummy(SoundIo *soundio, + SoundIoInputDevice *input_device, const char **data, int *frame_count) +{ + // TODO +} + +static void input_device_drop_dummy(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + // TODO +} + +static void input_device_clear_buffer_dummy(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + // TODO +} + +int audio_hardware_init_dummy(SoundIo *soundio) { + soundio->safe_devices_info = create(); + soundio->safe_devices_info->default_input_index = 0; + soundio->safe_devices_info->default_output_index = 0; + + // create output device + { + SoundIoDevice *device = create(); + if (!device) + return SoundIoErrorNoMem; + + device->ref_count = 1; + device->soundio = soundio; + device->name = strdup("dummy-out"); + device->description = strdup("Dummy output device"); + if (!device->name || !device->description) { + free(device->name); + free(device->description); + return SoundIoErrorNoMem; + } + device->channel_layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + device->default_sample_format = SoundIoSampleFormatFloat; + device->default_latency = 0.01; + device->default_sample_rate = 48000; + device->purpose = SoundIoDevicePurposeOutput; + + if (soundio->safe_devices_info->devices.append(device)) { + soundio_audio_device_unref(device); + return SoundIoErrorNoMem; + } + } + + // create input device + { + SoundIoDevice *device = create(); + if (!device) + return SoundIoErrorNoMem; + + device->ref_count = 1; + device->soundio = soundio; + device->name = strdup("dummy-in"); + device->description = strdup("Dummy input device"); + if (!device->name || !device->description) { + free(device->name); + free(device->description); + return SoundIoErrorNoMem; + } + device->channel_layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + device->default_sample_format = SoundIoSampleFormatFloat; + device->default_latency = 0.01; + device->default_sample_rate = 48000; + device->purpose = SoundIoDevicePurposeInput; + + if (soundio->safe_devices_info->devices.append(device)) { + soundio_audio_device_unref(device); + return SoundIoErrorNoMem; + } + } + + + soundio->destroy = destroy_audio_hardware_dummy; + soundio->flush_events = flush_events; + soundio->refresh_audio_devices = refresh_audio_devices; + + soundio->output_device_init = output_device_init_dummy; + soundio->output_device_destroy = output_device_destroy_dummy; + soundio->output_device_start = output_device_start_dummy; + soundio->output_device_free_count = output_device_free_count_dummy; + soundio->output_device_begin_write = output_device_begin_write_dummy; + soundio->output_device_write = output_device_write_dummy; + soundio->output_device_clear_buffer = output_device_clear_buffer_dummy; + + soundio->input_device_init = input_device_init_dummy; + soundio->input_device_destroy = input_device_destroy_dummy; + soundio->input_device_start = input_device_start_dummy; + soundio->input_device_peek = input_device_peek_dummy; + soundio->input_device_drop = input_device_drop_dummy; + soundio->input_device_clear_buffer = input_device_clear_buffer_dummy; + + return 0; +} + diff --git a/src/dummy.hpp b/src/dummy.hpp new file mode 100644 index 0000000..9998bd6 --- /dev/null +++ b/src/dummy.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_DUMMY_HPP +#define SOUNDIO_DUMMY_HPP + +#include "os.hpp" +#include "dummy_ring_buffer.hpp" +#include +using std::atomic_flag; + +struct SoundIoOutputDeviceDummy { + struct SoundIoOsThread *thread; + struct SoundIoOsMutex *mutex; + struct SoundIoOsCond *cond; + atomic_flag abort_flag; + int buffer_size; + double period; + struct SoundIoDummyRingBuffer ring_buffer; +}; + +struct SoundIoInputDeviceDummy { + +}; + +int soundio_dummy_init(struct SoundIo *soundio); + +#endif + + diff --git a/src/dummy_ring_buffer.cpp b/src/dummy_ring_buffer.cpp new file mode 100644 index 0000000..4f34d90 --- /dev/null +++ b/src/dummy_ring_buffer.cpp @@ -0,0 +1,75 @@ +#include "dummy_ring_buffer.hpp" +#include "soundio.hpp" +#include + +int soundio_dummy_ring_buffer_create(int requested_capacity, struct SoundIoDummyRingBuffer **out) { + *out = nullptr; + SoundIoDummyRingBuffer *rb = create(); + + if (!rb) { + soundio_dummy_ring_buffer_destroy(rb); + return SoundIoErrorNoMem; + } + + int err; + if ((err = soundio_dummy_ring_buffer_init(rb, requested_capacity))) { + soundio_dummy_ring_buffer_destroy(rb); + return SoundIoErrorNoMem; + } + + *out = rb; + return 0; +} + +int soundio_dummy_ring_buffer_init(struct SoundIoDummyRingBuffer *rb, int requested_capacity) { + // round size up to the nearest power of two + rb->capacity = powf(2, ceilf(log2(requested_capacity))); + + rb->address = allocate_nonzero(rb->capacity); + if (!rb->address) { + soundio_dummy_ring_buffer_deinit(rb); + return SoundIoErrorNoMem; + } + + return 0; +} + +void soundio_dummy_ring_buffer_destroy(struct SoundIoDummyRingBuffer *rb) { + if (!rb) + return; + + soundio_dummy_ring_buffer_deinit(rb); + + destroy(rb); +} + +void soundio_dummy_ring_buffer_deinit(struct SoundIoDummyRingBuffer *rb) { + deallocate(rb, rb->capacity); + rb->address = nullptr; +} + +void soundio_dummy_ring_buffer_clear(struct SoundIoDummyRingBuffer *rb) { + rb->write_offset.store(rb->read_offset.load()); +} + +int soundio_dummy_ring_buffer_free_count(struct SoundIoDummyRingBuffer *rb) { + return rb->capacity - soundio_dummy_ring_buffer_fill_count(rb); +} + +int soundio_dummy_ring_buffer_fill_count(struct SoundIoDummyRingBuffer *rb) { + int count = rb->write_offset - rb->read_offset; + assert(count >= 0); + assert(count <= rb->capacity); + return count; +} + +void soundio_dummy_ring_buffer_advance_write_ptr(struct SoundIoDummyRingBuffer *rb, int count) { + rb->write_offset += count; + assert(soundio_dummy_ring_buffer_fill_count(rb) >= 0); +} + +void soundio_dummy_ring_buffer_advance_read_ptr(struct SoundIoDummyRingBuffer *rb, int count) { + rb->read_offset += count; + assert(soundio_dummy_ring_buffer_fill_count(rb) >= 0); +} + diff --git a/src/dummy_ring_buffer.hpp b/src/dummy_ring_buffer.hpp new file mode 100644 index 0000000..e5a1819 --- /dev/null +++ b/src/dummy_ring_buffer.hpp @@ -0,0 +1,38 @@ +#ifndef SOUNDIO_DUMMY_RING_BUFFER_HPP +#define SOUNDIO_DUMMY_RING_BUFFER_HPP + +#include "util.hpp" +#include +using std::atomic_long; + +#ifndef ATOMIC_LONG_LOCK_FREE +#error "require atomic long to be lock free" +#endif + +struct SoundIoDummyRingBuffer { + char *address; + long capacity; + atomic_long write_offset; + atomic_long read_offset; +}; + +int soundio_dummy_ring_buffer_create(int requested_capacity, struct SoundIoDummyRingBuffer **out); +void soundio_dummy_ring_buffer_destroy(struct SoundIoDummyRingBuffer *rb); +int soundio_dummy_ring_buffer_init(struct SoundIoDummyRingBuffer *rb, int requested_capacity); +void soundio_dummy_ring_buffer_deinit(struct SoundIoDummyRingBuffer *rb); + +// must be called by the writer +void soundio_dummy_ring_buffer_clear(struct SoundIoDummyRingBuffer *rb); + + +// how much is available, ready for writing +int soundio_dummy_ring_buffer_free_count(struct SoundIoDummyRingBuffer *rb); + +// how much of the buffer is used, ready for reading +int soundio_dummy_ring_buffer_fill_count(struct SoundIoDummyRingBuffer *rb); + +void soundio_dummy_ring_buffer_advance_write_ptr(struct SoundIoDummyRingBuffer *rb, int count); +void soundio_dummy_ring_buffer_advance_read_ptr(struct SoundIoDummyRingBuffer *rb, int count); + +#endif + diff --git a/src/list.hpp b/src/list.hpp new file mode 100644 index 0000000..8c9ecfe --- /dev/null +++ b/src/list.hpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_LIST_HPP +#define SOUNDIO_LIST_HPP + +#include "util.hpp" +#include "soundio.h" + +#include + +template +struct SoundIoList { + SoundIoList() { + length = 0; + capacity = 0; + items = nullptr; + } + ~SoundIoList() { + deallocate(items, capacity); + } + int __attribute__((warn_unused_result)) append(T item) { + int err = ensure_capacity(length + 1); + if (err) + return err; + items[length++] = item; + return 0; + } + // remember that the pointer to this item is invalid after you + // modify the length of the list + const T & at(int index) const { + assert(index >= 0); + assert(index < length); + return items[index]; + } + T & at(int index) { + assert(index >= 0); + assert(index < length); + return items[index]; + } + T pop() { + assert(length >= 1); + return items[--length]; + } + + int __attribute__((warn_unused_result)) add_one() { + return resize(length + 1); + } + + const T & last() const { + assert(length >= 1); + return items[length - 1]; + } + + T & last() { + assert(length >= 1); + return items[length - 1]; + } + + int __attribute__((warn_unused_result)) resize(int length) { + assert(length >= 0); + int err = ensure_capacity(length); + if (err) + return err; + length = length; + return 0; + } + + T swap_remove(int index) { + assert(index >= 0); + assert(index < length); + if (index == length - 1) + return pop(); + + T last = pop(); + T item = items[index]; + items[index] = last; + return item; + } + + void remove_range(int start, int end) { + assert(0 <= start); + assert(start <= end); + assert(end <= length); + int del_count = end - start; + for (int i = start; i < length - del_count; i += 1) { + items[i] = items[i + del_count]; + } + length -= del_count; + } + + int __attribute__((warn_unused_result)) insert_space(int pos, int size) { + int old_length = length; + assert(pos >= 0 && pos <= old_length); + int err = resize(old_length + size); + if (err) + return err; + + for (int i = old_length - 1; i >= pos; i -= 1) { + items[i + size] = items[i]; + } + + return 0; + } + + void fill(T value) { + for (int i = 0; i < length; i += 1) { + items[i] = value; + } + } + + void clear() { + length = 0; + } + + int __attribute__((warn_unused_result)) ensure_capacity(int new_capacity) { + int better_capacity = max(capacity, 16); + while (better_capacity < new_capacity) + better_capacity = better_capacity * 2; + if (better_capacity != capacity) { + T *new_items = reallocate_nonzero(items, capacity, better_capacity); + if (!new_items) + return SoundIoErrorNoMem; + items = new_items; + capacity = better_capacity; + } + return 0; + } + + int allocated_size() const { + return capacity * sizeof(T); + } + + T * items; + int length; + int capacity; +}; + +#endif diff --git a/src/os.cpp b/src/os.cpp new file mode 100644 index 0000000..76322f2 --- /dev/null +++ b/src/os.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "os.hpp" +#include "soundio.h" +#include "util.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +struct SoundIoOsThread { + pthread_attr_t attr; + bool attr_init; + + pthread_t id; + bool running; + + void *arg; + void (*run)(void *arg); +}; + +struct SoundIoOsMutex { + pthread_mutex_t id; + bool id_init; +}; + +struct SoundIoOsCond { + pthread_cond_t id; + bool id_init; + + pthread_condattr_t attr; + bool attr_init; +}; + +double soundio_os_get_time(void) { + struct timespec tms; + clock_gettime(CLOCK_MONOTONIC, &tms); + double seconds = (double)tms.tv_sec; + seconds += ((double)tms.tv_nsec) / 1000000000.0; + return seconds; +} + +static void assert_no_err(int err) { + assert(!err); +} + +static void emit_rtprio_warning(void) { + static bool seen = false; + if (seen) + return; + seen = true; + fprintf(stderr, "warning: unable to set high priority thread: Operation not permitted\n"); + fprintf(stderr, "See https://github.com/andrewrk/genesis/wiki/" + "warning:-unable-to-set-high-priority-thread:-Operation-not-permitted\n"); +} + +int soundio_os_concurrency(void) { + long cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN); + if (cpu_core_count <= 0) + cpu_core_count = 1; + return cpu_core_count; +} + +static void *run_thread(void *userdata) { + struct SoundIoOsThread *thread = (struct SoundIoOsThread *)userdata; + thread->run(thread->arg); + return NULL; +} + +int soundio_os_thread_create( + void (*run)(void *arg), void *arg, + bool high_priority, struct SoundIoOsThread ** out_thread) +{ + *out_thread = NULL; + + struct SoundIoOsThread *thread = create(); + if (!thread) { + soundio_os_thread_destroy(thread); + return SoundIoErrorNoMem; + } + + thread->run = run; + thread->arg = arg; + + int err; + if ((err = pthread_attr_init(&thread->attr))) { + soundio_os_thread_destroy(thread); + return SoundIoErrorNoMem; + } + thread->attr_init = true; + + if (high_priority) { + int max_priority = sched_get_priority_max(SCHED_FIFO); + if (max_priority == -1) { + soundio_os_thread_destroy(thread); + return SoundIoErrorSystemResources; + } + struct sched_param param; + param.sched_priority = max_priority; + if ((err = pthread_attr_setschedparam(&thread->attr, ¶m))) { + soundio_os_thread_destroy(thread); + return SoundIoErrorSystemResources; + } + + if ((err = pthread_attr_setschedpolicy(&thread->attr, SCHED_FIFO))) { + soundio_os_thread_destroy(thread); + return SoundIoErrorSystemResources; + } + } + + if ((err = pthread_create(&thread->id, &thread->attr, run_thread, thread))) { + if (err == EPERM) { + emit_rtprio_warning(); + err = pthread_create(&thread->id, NULL, run_thread, thread); + } + if (err) { + soundio_os_thread_destroy(thread); + return SoundIoErrorNoMem; + } + } + thread->running = true; + + *out_thread = thread; + return 0; +} + +void soundio_os_thread_destroy(struct SoundIoOsThread *thread) { + if (!thread) + return; + + if (thread->running) { + assert_no_err(pthread_join(thread->id, NULL)); + } + + if (thread->attr_init) { + assert_no_err(pthread_attr_destroy(&thread->attr)); + } + + destroy(thread); +} + +struct SoundIoOsMutex *soundio_os_mutex_create(void) { + int err; + + struct SoundIoOsMutex *mutex = create(); + if (!mutex) { + soundio_os_mutex_destroy(mutex); + return NULL; + } + + if ((err = pthread_mutex_init(&mutex->id, NULL))) { + soundio_os_mutex_destroy(mutex); + return NULL; + } + mutex->id_init = true; + + return mutex; +} + +void soundio_os_mutex_destroy(struct SoundIoOsMutex *mutex) { + if (!mutex) + return; + + if (mutex->id_init) { + assert_no_err(pthread_mutex_destroy(&mutex->id)); + } + + destroy(mutex); +} + +void soundio_os_mutex_lock(struct SoundIoOsMutex *mutex) { + assert_no_err(pthread_mutex_lock(&mutex->id)); +} + +void soundio_os_mutex_unlock(struct SoundIoOsMutex *mutex) { + assert_no_err(pthread_mutex_unlock(&mutex->id)); +} + +struct SoundIoOsCond * soundio_os_cond_create(void) { + struct SoundIoOsCond *cond = create(); + + if (!cond) { + soundio_os_cond_destroy(cond); + return NULL; + } + + if (pthread_condattr_init(&cond->attr)) { + soundio_os_cond_destroy(cond); + return NULL; + } + cond->attr_init = true; + + if (pthread_condattr_setclock(&cond->attr, CLOCK_MONOTONIC)) { + soundio_os_cond_destroy(cond); + return NULL; + } + + if (pthread_cond_init(&cond->id, &cond->attr)) { + soundio_os_cond_destroy(cond); + return NULL; + } + cond->id_init = true; + + return cond; +} + +void soundio_os_cond_destroy(struct SoundIoOsCond *cond) { + if (!cond) + return; + + if (cond->id_init) { + assert_no_err(pthread_cond_destroy(&cond->id)); + } + + if (cond->attr_init) { + assert_no_err(pthread_condattr_destroy(&cond->attr)); + } + + destroy(cond); +} + +void soundio_os_cond_signal(struct SoundIoOsCond *cond) { + assert_no_err(pthread_cond_signal(&cond->id)); +} + +void soundio_os_cond_broadcast(struct SoundIoOsCond *cond) { + assert_no_err(pthread_cond_broadcast(&cond->id)); +} + +void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, + struct SoundIoOsMutex *mutex, double seconds) +{ + struct timespec tms; + clock_gettime(CLOCK_MONOTONIC, &tms); + tms.tv_nsec += (seconds * 1000000000L); + int err; + if ((err = pthread_cond_timedwait(&cond->id, &mutex->id, &tms))) { + assert(err != EPERM); + assert(err != EINVAL); + } +} diff --git a/src/os.hpp b/src/os.hpp new file mode 100644 index 0000000..c2a7ecc --- /dev/null +++ b/src/os.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_OS_HPP +#define SOUNDIO_OS_HPP + +#include + + +double soundio_os_get_time(void); + +struct SoundIoOsThread; +int soundio_os_thread_create( + void (*run)(void *arg), void *arg, + bool high_priority, struct SoundIoOsThread ** out_thread); + +void soundio_os_thread_destroy(struct SoundIoOsThread *thread); + + +struct SoundIoOsMutex; +struct SoundIoOsMutex *soundio_os_mutex_create(void); +void soundio_os_mutex_destroy(struct SoundIoOsMutex *mutex); +void soundio_os_mutex_lock(struct SoundIoOsMutex *mutex); +void soundio_os_mutex_unlock(struct SoundIoOsMutex *mutex); + +struct SoundIoOsCond; +struct SoundIoOsCond * soundio_os_cond_create(void); +void soundio_os_cond_destroy(struct SoundIoOsCond *cond); +void soundio_os_cond_signal(struct SoundIoOsCond *cond); +void soundio_os_cond_broadcast(struct SoundIoOsCond *cond); + +void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, + struct SoundIoOsMutex *mutex, double seconds); + +#endif diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp new file mode 100644 index 0000000..35908c8 --- /dev/null +++ b/src/pulseaudio.cpp @@ -0,0 +1,865 @@ +#include "pulseaudio.hpp" +#include "soundio.hpp" + +#include +#include + +static void subscribe_callback(pa_context *context, + pa_subscription_event_type_t event_bits, uint32_t index, void *userdata) +{ + SoundIo *soundio = (SoundIo *)userdata; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + ah->device_scan_queued = true; + pa_threaded_mainloop_signal(ah->main_loop, 0); +} + +static void subscribe_to_events(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + pa_subscription_mask_t events = (pa_subscription_mask_t)( + PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER); + pa_operation *subscribe_op = pa_context_subscribe(ah->pulse_context, + events, nullptr, soundio); + if (!subscribe_op) + panic("pa_context_subscribe failed: %s", pa_strerror(pa_context_errno(ah->pulse_context))); + pa_operation_unref(subscribe_op); +} + +static void context_state_callback(pa_context *context, void *userdata) { + SoundIo *soundio = (SoundIo *)userdata; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + + switch (pa_context_get_state(context)) { + case PA_CONTEXT_UNCONNECTED: // The context hasn't been connected yet. + return; + case PA_CONTEXT_CONNECTING: // A connection is being established. + return; + case PA_CONTEXT_AUTHORIZING: // The client is authorizing itself to the daemon. + return; + case PA_CONTEXT_SETTING_NAME: // The client is passing its application name to the daemon. + return; + case PA_CONTEXT_READY: // The connection is established, the context is ready to execute operations. + ah->device_scan_queued = true; + subscribe_to_events(soundio); + ah->ready_flag = true; + pa_threaded_mainloop_signal(ah->main_loop, 0); + return; + case PA_CONTEXT_TERMINATED: // The connection was terminated cleanly. + pa_threaded_mainloop_signal(ah->main_loop, 0); + return; + case PA_CONTEXT_FAILED: // The connection failed or was disconnected. + { + int err_number = pa_context_errno(context); + if (err_number == PA_ERR_CONNECTIONREFUSED) { + ah->connection_refused = true; + } else { + panic("pulseaudio connect failure: %s", pa_strerror(pa_context_errno(context))); + } + return; + } + } +} + +static void destroy_current_audio_devices_info(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (ah->current_audio_devices_info) { + for (int i = 0; i < ah->current_audio_devices_info->devices.length; i += 1) + soundio_audio_device_unref(ah->current_audio_devices_info->devices.at(i)); + + destroy(ah->current_audio_devices_info); + ah->current_audio_devices_info = nullptr; + } +} + +static void destroy_ready_audio_devices_info(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (ah->ready_audio_devices_info) { + for (int i = 0; i < ah->ready_audio_devices_info->devices.length; i += 1) + soundio_audio_device_unref(ah->ready_audio_devices_info->devices.at(i)); + destroy(ah->ready_audio_devices_info); + ah->ready_audio_devices_info = nullptr; + } +} + + +static void destroy_audio_hardware_pa(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (ah->main_loop) + pa_threaded_mainloop_stop(ah->main_loop); + + destroy_current_audio_devices_info(soundio); + destroy_ready_audio_devices_info(soundio); + + pa_context_disconnect(ah->pulse_context); + pa_context_unref(ah->pulse_context); + + if (ah->main_loop) + pa_threaded_mainloop_free(ah->main_loop); + + if (ah->props) + pa_proplist_free(ah->props); + + free(ah->default_sink_name); + free(ah->default_source_name); +} + +static double usec_to_sec(pa_usec_t usec) { + return (double)usec / (double)PA_USEC_PER_SEC; +} + +static SoundIoSampleFormat sample_format_from_pulseaudio(pa_sample_spec sample_spec) { + switch (sample_spec.format) { + case PA_SAMPLE_U8: return SoundIoSampleFormatUInt8; + case PA_SAMPLE_S16NE: return SoundIoSampleFormatInt16; + case PA_SAMPLE_S32NE: return SoundIoSampleFormatInt32; + case PA_SAMPLE_FLOAT32NE: return SoundIoSampleFormatFloat; + default: return SoundIoSampleFormatInvalid; + } +} + +static int sample_rate_from_pulseaudio(pa_sample_spec sample_spec) { + return sample_spec.rate; +} + +static SoundIoChannelId from_pulseaudio_channel_pos(pa_channel_position_t pos) { + switch (pos) { + case PA_CHANNEL_POSITION_MONO: return SoundIoChannelIdFrontCenter; + case PA_CHANNEL_POSITION_FRONT_LEFT: return SoundIoChannelIdFrontLeft; + case PA_CHANNEL_POSITION_FRONT_RIGHT: return SoundIoChannelIdFrontRight; + case PA_CHANNEL_POSITION_FRONT_CENTER: return SoundIoChannelIdFrontCenter; + case PA_CHANNEL_POSITION_REAR_CENTER: return SoundIoChannelIdBackCenter; + case PA_CHANNEL_POSITION_REAR_LEFT: return SoundIoChannelIdBackLeft; + case PA_CHANNEL_POSITION_REAR_RIGHT: return SoundIoChannelIdBackRight; + case PA_CHANNEL_POSITION_LFE: return SoundIoChannelIdLowFrequency; + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return SoundIoChannelIdFrontLeftOfCenter; + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return SoundIoChannelIdFrontRightOfCenter; + case PA_CHANNEL_POSITION_SIDE_LEFT: return SoundIoChannelIdSideLeft; + case PA_CHANNEL_POSITION_SIDE_RIGHT: return SoundIoChannelIdSideRight; + case PA_CHANNEL_POSITION_TOP_CENTER: return SoundIoChannelIdTopCenter; + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return SoundIoChannelIdTopFrontLeft; + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return SoundIoChannelIdTopFrontRight; + case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return SoundIoChannelIdTopFrontCenter; + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return SoundIoChannelIdTopBackLeft; + case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return SoundIoChannelIdTopBackRight; + case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return SoundIoChannelIdTopBackCenter; + + default: + panic("cannot map pulseaudio channel to libsoundio"); + } +} + +static void set_from_pulseaudio_channel_map(pa_channel_map channel_map, SoundIoChannelLayout *channel_layout) { + channel_layout->channel_count = channel_map.channels; + for (int i = 0; i < channel_map.channels; i += 1) { + channel_layout->channels[i] = from_pulseaudio_channel_pos(channel_map.map[i]); + } + channel_layout->name = nullptr; + int builtin_layout_count = soundio_channel_layout_builtin_count(); + for (int i = 0; i < builtin_layout_count; i += 1) { + const SoundIoChannelLayout *builtin_layout = soundio_channel_layout_get_builtin(i); + if (soundio_channel_layout_equal(builtin_layout, channel_layout)) { + channel_layout->name = builtin_layout->name; + break; + } + } +} + +static int perform_operation(SoundIo *soundio, pa_operation *op) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + for (;;) { + switch (pa_operation_get_state(op)) { + case PA_OPERATION_RUNNING: + pa_threaded_mainloop_wait(ah->main_loop); + continue; + case PA_OPERATION_DONE: + pa_operation_unref(op); + return 0; + case PA_OPERATION_CANCELLED: + pa_operation_unref(op); + return -1; + } + } +} + +static void finish_device_query(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + + if (!ah->have_sink_list || + !ah->have_source_list || + !ah->have_default_sink) + { + return; + } + + // based on the default sink name, figure out the default output index + ah->current_audio_devices_info->default_output_index = -1; + ah->current_audio_devices_info->default_input_index = -1; + for (int i = 0; i < ah->current_audio_devices_info->devices.length; i += 1) { + SoundIoDevice *device = ah->current_audio_devices_info->devices.at(i); + if (device->purpose == SoundIoDevicePurposeOutput && + strcmp(device->name, ah->default_sink_name) == 0) + { + ah->current_audio_devices_info->default_output_index = i; + } else if (device->purpose == SoundIoDevicePurposeInput && + strcmp(device->name, ah->default_source_name) == 0) + { + ah->current_audio_devices_info->default_input_index = i; + } + } + + destroy_ready_audio_devices_info(soundio); + ah->ready_audio_devices_info = ah->current_audio_devices_info; + ah->current_audio_devices_info = NULL; + ah->have_devices_flag = true; + pa_threaded_mainloop_signal(ah->main_loop, 0); + soundio->on_events_signal(soundio); +} + +static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *info, int eol, void *userdata) { + SoundIo *soundio = (SoundIo *)userdata; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (eol) { + ah->have_sink_list = true; + finish_device_query(soundio); + } else { + SoundIoDevice *device = create(); + if (!device) + panic("out of memory"); + + device->ref_count = 1; + device->soundio = soundio; + device->name = strdup(info->name); + device->description = strdup(info->description); + if (!device->name || !device->description) + panic("out of memory"); + set_from_pulseaudio_channel_map(info->channel_map, &device->channel_layout); + device->default_sample_format = sample_format_from_pulseaudio(info->sample_spec); + device->default_latency = usec_to_sec(info->configured_latency); + device->default_sample_rate = sample_rate_from_pulseaudio(info->sample_spec); + device->purpose = SoundIoDevicePurposeOutput; + + if (ah->current_audio_devices_info->devices.append(device)) + panic("out of memory"); + } + pa_threaded_mainloop_signal(ah->main_loop, 0); +} + +static void source_info_callback(pa_context *pulse_context, const pa_source_info *info, int eol, void *userdata) { + SoundIo *soundio = (SoundIo *)userdata; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (eol) { + ah->have_source_list = true; + finish_device_query(soundio); + } else { + SoundIoDevice *device = create(); + if (!device) + panic("out of memory"); + + device->ref_count = 1; + device->soundio = soundio; + device->name = strdup(info->name); + device->description = strdup(info->description); + if (!device->name || !device->description) + panic("out of memory"); + set_from_pulseaudio_channel_map(info->channel_map, &device->channel_layout); + device->default_sample_format = sample_format_from_pulseaudio(info->sample_spec); + device->default_latency = usec_to_sec(info->configured_latency); + device->default_sample_rate = sample_rate_from_pulseaudio(info->sample_spec); + device->purpose = SoundIoDevicePurposeInput; + + if (ah->current_audio_devices_info->devices.append(device)) + panic("out of memory"); + } + pa_threaded_mainloop_signal(ah->main_loop, 0); +} + +static void server_info_callback(pa_context *pulse_context, const pa_server_info *info, void *userdata) { + SoundIo *soundio = (SoundIo *)userdata; + assert(soundio); + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + + free(ah->default_sink_name); + free(ah->default_source_name); + + ah->default_sink_name = strdup(info->default_sink_name); + ah->default_source_name = strdup(info->default_source_name); + + if (!ah->default_sink_name || !ah->default_source_name) + panic("out of memory"); + + ah->have_default_sink = true; + finish_device_query(soundio); + pa_threaded_mainloop_signal(ah->main_loop, 0); +} + +static void scan_devices(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + + ah->have_sink_list = false; + ah->have_default_sink = false; + ah->have_source_list = false; + + destroy_current_audio_devices_info(soundio); + ah->current_audio_devices_info = create(); + if (!ah->current_audio_devices_info) + panic("out of memory"); + + pa_threaded_mainloop_lock(ah->main_loop); + + pa_operation *list_sink_op = pa_context_get_sink_info_list(ah->pulse_context, + sink_info_callback, soundio); + pa_operation *list_source_op = pa_context_get_source_info_list(ah->pulse_context, + source_info_callback, soundio); + pa_operation *server_info_op = pa_context_get_server_info(ah->pulse_context, + server_info_callback, soundio); + + if (perform_operation(soundio, list_sink_op)) + panic("list sinks failed"); + if (perform_operation(soundio, list_source_op)) + panic("list sources failed"); + if (perform_operation(soundio, server_info_op)) + panic("get server info failed"); + + pa_threaded_mainloop_signal(ah->main_loop, 0); + + pa_threaded_mainloop_unlock(ah->main_loop); +} + +static void flush_events(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + + if (ah->device_scan_queued) { + ah->device_scan_queued = false; + scan_devices(soundio); + } + + SoundIoDevicesInfo *old_devices_info = nullptr; + bool change = false; + + pa_threaded_mainloop_lock(ah->main_loop); + + if (ah->ready_audio_devices_info) { + old_devices_info = soundio->safe_devices_info; + soundio->safe_devices_info = ah->ready_audio_devices_info; + ah->ready_audio_devices_info = nullptr; + change = true; + } + + pa_threaded_mainloop_unlock(ah->main_loop); + + if (change) + soundio->on_devices_change(soundio); + + if (old_devices_info) { + for (int i = 0; i < old_devices_info->devices.length; i += 1) + soundio_audio_device_unref(old_devices_info->devices.at(i)); + destroy(old_devices_info); + } +} + +static void block_until_ready(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (ah->ready_flag) + return; + pa_threaded_mainloop_lock(ah->main_loop); + while (!ah->ready_flag) { + pa_threaded_mainloop_wait(ah->main_loop); + } + pa_threaded_mainloop_unlock(ah->main_loop); +} + +static void block_until_have_devices(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (ah->have_devices_flag) + return; + pa_threaded_mainloop_lock(ah->main_loop); + while (!ah->have_devices_flag) { + pa_threaded_mainloop_wait(ah->main_loop); + } + pa_threaded_mainloop_unlock(ah->main_loop); +} + +static pa_sample_format_t to_pulseaudio_sample_format(SoundIoSampleFormat sample_format) { + switch (sample_format) { + case SoundIoSampleFormatUInt8: + return PA_SAMPLE_U8; + case SoundIoSampleFormatInt16: + return PA_SAMPLE_S16NE; + case SoundIoSampleFormatInt24: + return PA_SAMPLE_S24NE; + case SoundIoSampleFormatInt32: + return PA_SAMPLE_S32NE; + case SoundIoSampleFormatFloat: + return PA_SAMPLE_FLOAT32NE; + case SoundIoSampleFormatDouble: + panic("cannot use double sample format with pulseaudio"); + case SoundIoSampleFormatInvalid: + panic("invalid sample format"); + } + panic("invalid sample format"); +} + +static pa_channel_position_t to_pulseaudio_channel_pos(SoundIoChannelId channel_id) { + switch (channel_id) { + case SoundIoChannelIdInvalid: + case SoundIoChannelIdCount: + panic("invalid channel id"); + case SoundIoChannelIdFrontLeft: + return PA_CHANNEL_POSITION_FRONT_LEFT; + case SoundIoChannelIdFrontRight: + return PA_CHANNEL_POSITION_FRONT_RIGHT; + case SoundIoChannelIdFrontCenter: + return PA_CHANNEL_POSITION_FRONT_CENTER; + case SoundIoChannelIdLowFrequency: + return PA_CHANNEL_POSITION_LFE; + case SoundIoChannelIdBackLeft: + return PA_CHANNEL_POSITION_REAR_LEFT; + case SoundIoChannelIdBackRight: + return PA_CHANNEL_POSITION_REAR_RIGHT; + case SoundIoChannelIdFrontLeftOfCenter: + return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case SoundIoChannelIdFrontRightOfCenter: + return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case SoundIoChannelIdBackCenter: + return PA_CHANNEL_POSITION_REAR_CENTER; + case SoundIoChannelIdSideLeft: + return PA_CHANNEL_POSITION_SIDE_LEFT; + case SoundIoChannelIdSideRight: + return PA_CHANNEL_POSITION_SIDE_RIGHT; + case SoundIoChannelIdTopCenter: + return PA_CHANNEL_POSITION_TOP_CENTER; + case SoundIoChannelIdTopFrontLeft: + return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case SoundIoChannelIdTopFrontCenter: + return PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case SoundIoChannelIdTopFrontRight: + return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case SoundIoChannelIdTopBackLeft: + return PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case SoundIoChannelIdTopBackCenter: + return PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case SoundIoChannelIdTopBackRight: + return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + } + panic("invalid channel id"); +} + +static pa_channel_map to_pulseaudio_channel_map(const SoundIoChannelLayout *channel_layout) { + pa_channel_map channel_map; + channel_map.channels = channel_layout->channel_count; + + if ((unsigned)channel_layout->channel_count > PA_CHANNELS_MAX) + panic("channel layout greater than pulseaudio max channels"); + + for (int i = 0; i < channel_layout->channel_count; i += 1) + channel_map.map[i] = to_pulseaudio_channel_pos(channel_layout->channels[i]); + + return channel_map; +} + +static void playback_stream_state_callback(pa_stream *stream, void *userdata) { + SoundIoOutputDevice *output_device = (SoundIoOutputDevice*) userdata; + SoundIo *soundio = output_device->device->soundio; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + switch (pa_stream_get_state(stream)) { + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + case PA_STREAM_READY: + opd->stream_ready = true; + pa_threaded_mainloop_signal(ah->main_loop, 0); + break; + case PA_STREAM_FAILED: + panic("pulseaudio stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); + break; + } +} + +static void playback_stream_underflow_callback(pa_stream *stream, void *userdata) { + SoundIoOutputDevice *output_device = (SoundIoOutputDevice*)userdata; + output_device->underrun_callback(output_device); +} + + +static void playback_stream_write_callback(pa_stream *stream, size_t nbytes, void *userdata) { + SoundIoOutputDevice *output_device = (SoundIoOutputDevice*)(userdata); + int frame_count = ((int)nbytes) / output_device->bytes_per_frame; + output_device->write_callback(output_device, frame_count); +} + +static int output_device_init_pa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + SoundIoDevice *device = output_device->device; + opd->stream_ready = false; + + assert(ah->pulse_context); + + pa_threaded_mainloop_lock(ah->main_loop); + + pa_sample_spec sample_spec; + sample_spec.format = to_pulseaudio_sample_format(output_device->sample_format); + sample_spec.rate = device->default_sample_rate; + sample_spec.channels = device->channel_layout.channel_count; + pa_channel_map channel_map = to_pulseaudio_channel_map(&device->channel_layout); + + opd->stream = pa_stream_new(ah->pulse_context, "SoundIo", &sample_spec, &channel_map); + if (!opd->stream) { + pa_threaded_mainloop_unlock(ah->main_loop); + return SoundIoErrorNoMem; + } + pa_stream_set_state_callback(opd->stream, playback_stream_state_callback, output_device); + pa_stream_set_write_callback(opd->stream, playback_stream_write_callback, output_device); + pa_stream_set_underflow_callback(opd->stream, playback_stream_underflow_callback, output_device); + + int bytes_per_second = output_device->bytes_per_frame * device->default_sample_rate; + int buffer_length = output_device->bytes_per_frame * + ceil(output_device->latency * bytes_per_second / (double)output_device->bytes_per_frame); + + opd->buffer_attr.maxlength = buffer_length; + opd->buffer_attr.tlength = buffer_length; + opd->buffer_attr.prebuf = 0; + opd->buffer_attr.minreq = UINT32_MAX; + opd->buffer_attr.fragsize = UINT32_MAX; + + pa_threaded_mainloop_unlock(ah->main_loop); + + return 0; +} + +static void output_device_destroy_pa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + pa_stream *stream = opd->stream; + if (stream) { + pa_threaded_mainloop_lock(ah->main_loop); + + pa_stream_set_write_callback(stream, nullptr, nullptr); + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_underflow_callback(stream, nullptr, nullptr); + pa_stream_disconnect(stream); + + pa_stream_unref(stream); + + pa_threaded_mainloop_unlock(ah->main_loop); + + opd->stream = nullptr; + } +} + +static int output_device_start_pa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + + pa_threaded_mainloop_lock(ah->main_loop); + + + int err = pa_stream_connect_playback(opd->stream, + output_device->device->name, &opd->buffer_attr, + PA_STREAM_ADJUST_LATENCY, nullptr, nullptr); + if (err) { + pa_threaded_mainloop_unlock(ah->main_loop); + return SoundIoErrorOpeningDevice; + } + + while (!opd->stream_ready) + pa_threaded_mainloop_wait(ah->main_loop); + + soundio_output_device_fill_with_silence(output_device); + + pa_threaded_mainloop_unlock(ah->main_loop); + + return 0; +} + +static int output_device_free_count_pa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + return pa_stream_writable_size(opd->stream) / output_device->bytes_per_frame; +} + + +static void output_device_begin_write_pa(SoundIo *soundio, + SoundIoOutputDevice *output_device, char **data, int *frame_count) +{ + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + pa_stream *stream = opd->stream; + size_t byte_count = *frame_count * output_device->bytes_per_frame; + if (pa_stream_begin_write(stream, (void**)data, &byte_count)) + panic("pa_stream_begin_write error: %s", pa_strerror(pa_context_errno(ah->pulse_context))); + + *frame_count = byte_count / output_device->bytes_per_frame; +} + +static void output_device_write_pa(SoundIo *soundio, + SoundIoOutputDevice *output_device, char *data, int frame_count) +{ + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + pa_stream *stream = opd->stream; + size_t byte_count = frame_count * output_device->bytes_per_frame; + if (pa_stream_write(stream, data, byte_count, NULL, 0, PA_SEEK_RELATIVE)) + panic("pa_stream_write error: %s", pa_strerror(pa_context_errno(ah->pulse_context))); +} + +static void output_device_clear_buffer_pa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + SoundIoOutputDevicePulseAudio *opd = (SoundIoOutputDevicePulseAudio *)output_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + pa_stream *stream = opd->stream; + pa_threaded_mainloop_lock(ah->main_loop); + pa_operation *op = pa_stream_flush(stream, NULL, NULL); + if (!op) + panic("pa_stream_flush failed: %s", pa_strerror(pa_context_errno(ah->pulse_context))); + pa_operation_unref(op); + pa_threaded_mainloop_unlock(ah->main_loop); +} + +static void recording_stream_state_callback(pa_stream *stream, void *userdata) { + SoundIoInputDevice *input_device = (SoundIoInputDevice*)userdata; + SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + switch (pa_stream_get_state(stream)) { + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + case PA_STREAM_READY: + ord->stream_ready = true; + break; + case PA_STREAM_FAILED: + panic("pulseaudio stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); + break; + } +} + +static void recording_stream_read_callback(pa_stream *stream, size_t nbytes, void *userdata) { + SoundIoInputDevice *input_device = (SoundIoInputDevice*)userdata; + input_device->read_callback(input_device); +} + +static int input_device_init_pa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + SoundIoDevice *device = input_device->device; + ord->stream_ready = false; + + pa_threaded_mainloop_lock(ah->main_loop); + + pa_sample_spec sample_spec; + sample_spec.format = to_pulseaudio_sample_format(input_device->sample_format); + sample_spec.rate = device->default_sample_rate; + sample_spec.channels = device->channel_layout.channel_count; + + pa_channel_map channel_map = to_pulseaudio_channel_map(&device->channel_layout); + + ord->stream = pa_stream_new(ah->pulse_context, "SoundIo", &sample_spec, &channel_map); + if (!input_device) { + pa_threaded_mainloop_unlock(ah->main_loop); + return SoundIoErrorNoMem; + } + + pa_stream *stream = ord->stream; + + pa_stream_set_state_callback(stream, recording_stream_state_callback, input_device); + pa_stream_set_read_callback(stream, recording_stream_read_callback, input_device); + + int bytes_per_second = input_device->bytes_per_frame * device->default_sample_rate; + int buffer_length = input_device->bytes_per_frame * + ceil(input_device->latency * bytes_per_second / (double)input_device->bytes_per_frame); + + ord->buffer_attr.maxlength = UINT32_MAX; + ord->buffer_attr.tlength = UINT32_MAX; + ord->buffer_attr.prebuf = 0; + ord->buffer_attr.minreq = UINT32_MAX; + ord->buffer_attr.fragsize = buffer_length; + + pa_threaded_mainloop_unlock(ah->main_loop); + + return 0; +} + +static void input_device_destroy_pa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + pa_stream *stream = ord->stream; + if (stream) { + pa_threaded_mainloop_lock(ah->main_loop); + + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_read_callback(stream, nullptr, nullptr); + pa_stream_disconnect(stream); + pa_stream_unref(stream); + + pa_threaded_mainloop_unlock(ah->main_loop); + + ord->stream = nullptr; + } +} + +static int input_device_start_pa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + pa_threaded_mainloop_lock(ah->main_loop); + + int err = pa_stream_connect_record(ord->stream, + input_device->device->name, + &ord->buffer_attr, PA_STREAM_ADJUST_LATENCY); + if (err) { + pa_threaded_mainloop_unlock(ah->main_loop); + return SoundIoErrorOpeningDevice; + } + + pa_threaded_mainloop_unlock(ah->main_loop); + return 0; +} + +static void input_device_peek_pa(SoundIo *soundio, + SoundIoInputDevice *input_device, const char **data, int *frame_count) +{ + SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + pa_stream *stream = ord->stream; + if (ord->stream_ready) { + size_t nbytes; + if (pa_stream_peek(stream, (const void **)data, &nbytes)) + panic("pa_stream_peek error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); + *frame_count = ((int)nbytes) / input_device->bytes_per_frame; + } else { + *data = nullptr; + *frame_count = 0; + } +} + +static void input_device_drop_pa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + pa_stream *stream = ord->stream; + if (pa_stream_drop(stream)) + panic("pa_stream_drop error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); +} + +static void input_device_clear_buffer_pa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + if (!ord->stream_ready) + return; + + pa_stream *stream = ord->stream; + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + + pa_threaded_mainloop_lock(ah->main_loop); + + for (;;) { + const char *data; + size_t nbytes; + if (pa_stream_peek(stream, (const void **)&data, &nbytes)) + panic("pa_stream_peek error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); + + if (nbytes == 0) + break; + + if (pa_stream_drop(stream)) + panic("pa_stream_drop error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); + } + + pa_threaded_mainloop_unlock(ah->main_loop); +} + +static void refresh_audio_devices(SoundIo *soundio) { + block_until_ready(soundio); + soundio_flush_events(soundio); + block_until_have_devices(soundio); +} + +int audio_hardware_init_pulseaudio(SoundIo *soundio) { + SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + + ah->connection_refused = false; + ah->device_scan_queued = false; + ah->ready_flag = false; + ah->have_devices_flag = false; + + ah->main_loop = pa_threaded_mainloop_new(); + if (!ah->main_loop) { + destroy_audio_hardware_pa(soundio); + return SoundIoErrorNoMem; + } + + pa_mainloop_api *main_loop_api = pa_threaded_mainloop_get_api(ah->main_loop); + + ah->props = pa_proplist_new(); + if (!ah->props) { + destroy_audio_hardware_pa(soundio); + return SoundIoErrorNoMem; + } + + // TODO let the API specify this + pa_proplist_sets(ah->props, PA_PROP_APPLICATION_NAME, "libsoundio"); + pa_proplist_sets(ah->props, PA_PROP_APPLICATION_VERSION, SOUNDIO_VERSION_STRING); + pa_proplist_sets(ah->props, PA_PROP_APPLICATION_ID, "me.andrewkelley.libsoundio"); + + ah->pulse_context = pa_context_new_with_proplist(main_loop_api, "SoundIo", ah->props); + if (!ah->pulse_context) { + destroy_audio_hardware_pa(soundio); + return SoundIoErrorNoMem; + } + + pa_context_set_subscribe_callback(ah->pulse_context, subscribe_callback, soundio); + pa_context_set_state_callback(ah->pulse_context, context_state_callback, soundio); + + int err = pa_context_connect(ah->pulse_context, NULL, (pa_context_flags_t)0, NULL); + if (err) { + destroy_audio_hardware_pa(soundio); + return SoundIoErrorInitAudioBackend; + } + + if (ah->connection_refused) { + destroy_audio_hardware_pa(soundio); + return SoundIoErrorInitAudioBackend; + } + + if (pa_threaded_mainloop_start(ah->main_loop)) { + destroy_audio_hardware_pa(soundio); + return SoundIoErrorNoMem; + } + + soundio->destroy = destroy_audio_hardware_pa; + soundio->flush_events = flush_events; + soundio->refresh_audio_devices = refresh_audio_devices; + + soundio->output_device_init = output_device_init_pa; + soundio->output_device_destroy = output_device_destroy_pa; + soundio->output_device_start = output_device_start_pa; + soundio->output_device_free_count = output_device_free_count_pa; + soundio->output_device_begin_write = output_device_begin_write_pa; + soundio->output_device_write = output_device_write_pa; + soundio->output_device_clear_buffer = output_device_clear_buffer_pa; + + soundio->input_device_init = input_device_init_pa; + soundio->input_device_destroy = input_device_destroy_pa; + soundio->input_device_start = input_device_start_pa; + soundio->input_device_peek = input_device_peek_pa; + soundio->input_device_drop = input_device_drop_pa; + soundio->input_device_clear_buffer = input_device_clear_buffer_pa; + + return 0; +} + + diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp new file mode 100644 index 0000000..258322f --- /dev/null +++ b/src/pulseaudio.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_PULSEAUDIO_HPP +#define SOUNDIO_PULSEAUDIO_HPP + +#include +#include +using std::atomic_bool; + +struct SoundIoOutputDevicePulseAudio { + pa_stream *stream; + atomic_bool stream_ready; + pa_buffer_attr buffer_attr; +}; + +struct SoundIoInputDevicePulseAudio { + pa_stream *stream; + atomic_bool stream_ready; + pa_buffer_attr buffer_attr; +}; + +struct SoundIoPulseAudio { + bool connection_refused; + + pa_context *pulse_context; + atomic_bool device_scan_queued; + + // the one that we're working on building + struct SoundIoDevicesInfo *current_audio_devices_info; + char * default_sink_name; + char * default_source_name; + + // this one is ready to be read with flush_events. protected by mutex + struct SoundIoDevicesInfo *ready_audio_devices_info; + + bool have_sink_list; + bool have_source_list; + bool have_default_sink; + + atomic_bool ready_flag; + atomic_bool have_devices_flag; + + pa_threaded_mainloop *main_loop; + pa_proplist *props; +}; + +int soundio_pulseaudio_init(struct SoundIo *soundio); + +#endif diff --git a/src/soundio.c b/src/soundio.c deleted file mode 100644 index 5315d84..0000000 --- a/src/soundio.c +++ /dev/null @@ -1 +0,0 @@ -#include "soundio.h" diff --git a/src/soundio.cpp b/src/soundio.cpp new file mode 100644 index 0000000..65bf3de --- /dev/null +++ b/src/soundio.cpp @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "soundio.h" +#include "util.hpp" +#include "dummy.hpp" +#include "pulseaudio.hpp" + +#include +#include + +static struct SoundIoChannelLayout builtin_channel_layouts[] = { + { + "Mono", + 1, + { + SoundIoChannelIdFrontCenter, + }, + }, + { + "Stereo", + 2, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + }, + }, + { + "2.1", + 3, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdLowFrequency, + }, + }, + { + "3.0", + 3, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + } + }, + { + "3.0 (back)", + 3, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdBackCenter, + } + }, + { + "3.1", + 4, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdLowFrequency, + } + }, + { + "4.0", + 4, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdBackCenter, + } + }, + { + "4.1", + 4, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdLowFrequency, + } + }, + { + "Quad", + 4, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + }, + }, + { + "Quad (side)", + 4, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + } + }, + { + "5.0", + 5, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + } + }, + { + "5.0 (back)", + 5, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + } + }, + { + "5.1", + 6, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdLowFrequency, + } + }, + { + "5.1 (back)", + 6, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + SoundIoChannelIdLowFrequency, + } + }, + { + "6.0", + 6, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdBackCenter, + } + }, + { + "6.0 (front)", + 6, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdFrontLeftOfCenter, + SoundIoChannelIdFrontRightOfCenter, + } + }, + { + "Hexagonal", + 6, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + SoundIoChannelIdBackCenter, + } + }, + { + "6.1", + 7, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdBackCenter, + SoundIoChannelIdLowFrequency, + } + }, + { + "6.1 (back)", + 7, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + SoundIoChannelIdBackCenter, + SoundIoChannelIdLowFrequency, + } + }, + { + "6.1 (front)", + 7, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdFrontLeftOfCenter, + SoundIoChannelIdFrontRightOfCenter, + SoundIoChannelIdLowFrequency, + } + }, + { + "7.0", + 7, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + } + }, + { + "7.0 (front)", + 7, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdFrontLeftOfCenter, + SoundIoChannelIdFrontRightOfCenter, + } + }, + { + "7.1", + 8, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + SoundIoChannelIdLowFrequency, + } + }, + { + "7.1 (wide)", + 8, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdFrontLeftOfCenter, + SoundIoChannelIdFrontRightOfCenter, + SoundIoChannelIdLowFrequency, + } + }, + { + "7.1 (wide) (back)", + 8, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + SoundIoChannelIdFrontLeftOfCenter, + SoundIoChannelIdFrontRightOfCenter, + SoundIoChannelIdLowFrequency, + } + }, + { + "Octagonal", + 8, + { + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + SoundIoChannelIdBackCenter, + } + }, +}; + +int soundio_get_bytes_per_sample(enum SoundIoSampleFormat sample_format) { + switch (sample_format) { + case SoundIoSampleFormatUInt8: return 1; + case SoundIoSampleFormatInt16: return 2; + case SoundIoSampleFormatInt24: return 3; + case SoundIoSampleFormatInt32: return 4; + case SoundIoSampleFormatFloat: return 4; + case SoundIoSampleFormatDouble: return 8; + case SoundIoSampleFormatInvalid: panic("invalid sample format"); + } + panic("invalid sample format"); +} + +const char * soundio_sample_format_string(enum SoundIoSampleFormat sample_format) { + switch (sample_format) { + case SoundIoSampleFormatUInt8: return "unsigned 8-bit integer"; + case SoundIoSampleFormatInt16: return "signed 16-bit integer"; + case SoundIoSampleFormatInt24: return "signed 24-bit integer"; + case SoundIoSampleFormatInt32: return "signed 32-bit integer"; + case SoundIoSampleFormatFloat: return "32-bit float"; + case SoundIoSampleFormatDouble: return "64-bit float"; + case SoundIoSampleFormatInvalid: return "invalid sample format"; + } + panic("invalid sample format"); +} + + +bool soundio_channel_layout_equal( + const struct SoundIoChannelLayout *a, + const struct SoundIoChannelLayout *b) +{ + if (a->channel_count != b->channel_count) + return false; + + for (int i = 0; i < a->channel_count; i += 1) { + if (a->channels[i] != b->channels[i]) + return false; + } + + return true; +} + +const char *soundio_get_channel_name(enum SoundIoChannelId id) { + switch (id) { + case SoundIoChannelIdInvalid: return "(Invalid Channel)"; + case SoundIoChannelIdCount: return "(Invalid Channel)"; + + case SoundIoChannelIdFrontLeft: return "Front Left"; + case SoundIoChannelIdFrontRight: return "Front Right"; + case SoundIoChannelIdFrontCenter: return "Front Center"; + case SoundIoChannelIdLowFrequency: return "Low Frequency"; + case SoundIoChannelIdBackLeft: return "Back Left"; + case SoundIoChannelIdBackRight: return "Back Right"; + case SoundIoChannelIdFrontLeftOfCenter: return "Front Left of Center"; + case SoundIoChannelIdFrontRightOfCenter: return "Front Right of Center"; + case SoundIoChannelIdBackCenter: return "Back Center"; + case SoundIoChannelIdSideLeft: return "Side Left"; + case SoundIoChannelIdSideRight: return "Side Right"; + case SoundIoChannelIdTopCenter: return "Top Center"; + case SoundIoChannelIdTopFrontLeft: return "Top Front Left"; + case SoundIoChannelIdTopFrontCenter: return "Top Front Center"; + case SoundIoChannelIdTopFrontRight: return "Top Front Right"; + case SoundIoChannelIdTopBackLeft: return "Top Back Left"; + case SoundIoChannelIdTopBackCenter: return "Top Back Center"; + case SoundIoChannelIdTopBackRight: return "Top Back Right"; + } + return "(Invalid Channel)"; +} + +int soundio_channel_layout_builtin_count(void) { + return array_length(builtin_channel_layouts); +} + +const struct SoundIoChannelLayout *soundio_channel_layout_get_builtin(int index) { + assert(index >= 0); + assert(index <= array_length(builtin_channel_layouts)); + return &builtin_channel_layouts[index]; +} + +void soundio_debug_print_channel_layout(const struct SoundIoChannelLayout *layout) { + if (layout->name) { + fprintf(stderr, "%s\n", layout->name); + } else { + fprintf(stderr, "%s", soundio_get_channel_name(layout->channels[0])); + for (int i = 1; i < layout->channel_count; i += 1) { + fprintf(stderr, ", %s", soundio_get_channel_name(layout->channels[i])); + } + fprintf(stderr, "\n"); + } +} + +int soundio_channel_layout_find_channel( + const struct SoundIoChannelLayout *layout, enum SoundIoChannelId channel) +{ + for (int i = 0; i < layout->channel_count; i += 1) { + if (layout->channels[i] == channel) + return i; + } + return -1; +} + +const char *soundio_error_string(int error) { + switch ((enum SoundIoError)error) { + case SoundIoErrorNone: return "(no error)"; + case SoundIoErrorNoMem: return "out of memory"; + case SoundIoErrorInitAudioBackend: return "unable to initialize audio backend"; + case SoundIoErrorSystemResources: return "system resource not available"; + case SoundIoErrorOpeningDevice: return "unable to open device"; + } + panic("invalid error enum value: %d", error); +} + +const char *soundio_backend_name(enum SoundIoBackend backend) { + switch (backend) { + case SoundIoBackendPulseAudio: return "PulseAudio"; + case SoundIoBackendDummy: return "Dummy"; + } + panic("invalid backend enum value: %d", (int)backend); +} + +void soundio_destroy(struct SoundIo *soundio) { + if (!soundio) + return; + + if (soundio->destroy) + soundio->destroy(soundio); + + destroy(soundio); +} + +int soundio_create(struct SoundIo **out_soundio) { + *out_soundio = NULL; + + struct SoundIo *soundio = create(); + if (!soundio) { + soundio_destroy(soundio); + return SoundIoErrorNoMem; + } + + int err; + + err = soundio_pulseaudio_init(soundio); + if (err != SoundIoErrorInitAudioBackend) { + soundio_destroy(soundio); + return err; + } + + err = soundio_dummy_init(soundio); + if (err) { + soundio_destroy(soundio); + return err; + } + + *out_soundio = soundio; + return 0; +} + +void soundio_flush_events(struct SoundIo *soundio) { + if (soundio->flush_events) + soundio->flush_events(soundio); +} diff --git a/src/soundio.h b/src/soundio.h index 5d7417b..3cb3dba 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -9,17 +9,310 @@ #define SOUNDIO_SOUNDIO_H #include "config.h" +#include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +struct SoundIo; +struct SoundIoDevicesInfo; + +enum SoundIoError { + SoundIoErrorNone, + SoundIoErrorNoMem, + SoundIoErrorInitAudioBackend, + SoundIoErrorSystemResources, + SoundIoErrorOpeningDevice, +}; + +enum SoundIoChannelId { + SoundIoChannelIdInvalid, + SoundIoChannelIdFrontLeft, + SoundIoChannelIdFrontRight, + SoundIoChannelIdFrontCenter, + SoundIoChannelIdLowFrequency, + SoundIoChannelIdBackLeft, + SoundIoChannelIdBackRight, + SoundIoChannelIdFrontLeftOfCenter, + SoundIoChannelIdFrontRightOfCenter, + SoundIoChannelIdBackCenter, + SoundIoChannelIdSideLeft, + SoundIoChannelIdSideRight, + SoundIoChannelIdTopCenter, + SoundIoChannelIdTopFrontLeft, + SoundIoChannelIdTopFrontCenter, + SoundIoChannelIdTopFrontRight, + SoundIoChannelIdTopBackLeft, + SoundIoChannelIdTopBackCenter, + SoundIoChannelIdTopBackRight, + + SoundIoChannelIdCount, +}; + +#define SOUNDIO_MAX_CHANNELS 32 +struct SoundIoChannelLayout { + const char *name; + int channel_count; + enum SoundIoChannelId channels[SOUNDIO_MAX_CHANNELS]; +}; + +enum SoundIoChannelLayoutId { + SoundIoChannelLayoutIdMono, + SoundIoChannelLayoutIdStereo, + SoundIoChannelLayoutId2Point1, + SoundIoChannelLayoutId3Point0, + SoundIoChannelLayoutId3Point0Back, + SoundIoChannelLayoutId3Point1, + SoundIoChannelLayoutId4Point0, + SoundIoChannelLayoutId4Point1, + SoundIoChannelLayoutIdQuad, + SoundIoChannelLayoutIdQuadSide, + SoundIoChannelLayoutId5Point0, + SoundIoChannelLayoutId5Point0Back, + SoundIoChannelLayoutId5Point1, + SoundIoChannelLayoutId5Point1Back, + SoundIoChannelLayoutId6Point0, + SoundIoChannelLayoutId6Point0Front, + SoundIoChannelLayoutIdHexagonal, + SoundIoChannelLayoutId6Point1, + SoundIoChannelLayoutId6Point1Back, + SoundIoChannelLayoutId6Point1Front, + SoundIoChannelLayoutId7Point0, + SoundIoChannelLayoutId7Point0Front, + SoundIoChannelLayoutId7Point1, + SoundIoChannelLayoutId7Point1Wide, + SoundIoChannelLayoutId7Point1WideBack, + SoundIoChannelLayoutIdOctagonal, +}; + enum SoundIoBackend { SoundIoBackendPulseAudio, SoundIoBackendDummy, }; +enum SoundIoDevicePurpose { + SoundIoDevicePurposeInput, + SoundIoDevicePurposeOutput, +}; + +// always native-endian +enum SoundIoSampleFormat { + SoundIoSampleFormatUInt8, + SoundIoSampleFormatInt16, + SoundIoSampleFormatInt24, + SoundIoSampleFormatInt32, + SoundIoSampleFormatFloat, + SoundIoSampleFormatDouble, + SoundIoSampleFormatInvalid, +}; + +struct SoundIoDevice { + struct SoundIo *soundio; + char *name; + char *description; + struct SoundIoChannelLayout channel_layout; + enum SoundIoSampleFormat default_sample_format; + double default_latency; + int default_sample_rate; + enum SoundIoDevicePurpose purpose; + int ref_count; +}; + +struct SoundIoOutputDevice { + void *backend_data; + struct SoundIoDevice *device; + enum SoundIoSampleFormat sample_format; + double latency; + int bytes_per_frame; + + void *userdata; + void (*underrun_callback)(struct SoundIoOutputDevice *); + void (*write_callback)(struct SoundIoOutputDevice *, int frame_count); +}; + +struct SoundIoInputDevice { + void *backend_data; + struct SoundIoDevice *device; + enum SoundIoSampleFormat sample_format; + double latency; + int bytes_per_frame; + + void *userdata; + void (*read_callback)(struct SoundIoInputDevice *); +}; + +struct SoundIo { + enum SoundIoBackend current_backend; + void *backend_data; + + // safe to read without a mutex from a single thread + struct SoundIoDevicesInfo *safe_devices_info; + + void *userdata; + void (*on_devices_change)(struct SoundIo *); + void (*on_events_signal)(struct SoundIo *); + + void (*destroy)(struct SoundIo *); + void (*flush_events)(struct SoundIo *); + void (*refresh_audio_devices)(struct SoundIo *); + + int (*output_device_init)(struct SoundIo *, struct SoundIoOutputDevice *); + void (*output_device_destroy)(struct SoundIo *, struct SoundIoOutputDevice *); + int (*output_device_start)(struct SoundIo *, struct SoundIoOutputDevice *); + int (*output_device_free_count)(struct SoundIo *, struct SoundIoOutputDevice *); + void (*output_device_begin_write)(struct SoundIo *, struct SoundIoOutputDevice *, + char **data, int *frame_count); + void (*output_device_write)(struct SoundIo *, struct SoundIoOutputDevice *, + char *data, int frame_count); + void (*output_device_clear_buffer)(struct SoundIo *, struct SoundIoOutputDevice *); + + + int (*input_device_init)(struct SoundIo *, struct SoundIoInputDevice *); + void (*input_device_destroy)(struct SoundIo *, struct SoundIoInputDevice *); + int (*input_device_start)(struct SoundIo *, struct SoundIoInputDevice *); + void (*input_device_peek)(struct SoundIo *, struct SoundIoInputDevice *, + const char **data, int *frame_count); + void (*input_device_drop)(struct SoundIo *, struct SoundIoInputDevice *); + void (*input_device_clear_buffer)(struct SoundIo *, struct SoundIoInputDevice *); +}; + +// Main Context + +// Create a SoundIo context. +// Returns an error code. +int soundio_create(struct SoundIo **out_soundio); + +void soundio_destroy(struct SoundIo *soundio); + +const char *soundio_error_string(int error); +const char *soundio_backend_name(enum SoundIoBackend backend); + +// when you call this, the on_devices_change and on_events_signal callbacks +// might be called. This is the only time those functions will be called. +void soundio_flush_events(struct SoundIo *soundio); + +// flushes events as they occur, blocks until you call soundio_wakeup +// be ready for spurious wakeups +void soundio_wait_events(struct SoundIo *soundio); + +// makes soundio_wait_events stop blocking +void soundio_wakeup(struct SoundIo *soundio); + + + +// Channel Layouts + +bool soundio_channel_layout_equal(const struct SoundIoChannelLayout *a, + const struct SoundIoChannelLayout *b); + +const char *soundio_get_channel_name(enum SoundIoChannelId id); + +int soundio_channel_layout_builtin_count(void); +const struct SoundIoChannelLayout *soundio_channel_layout_get_builtin(int index); + +void soundio_debug_print_channel_layout(const struct SoundIoChannelLayout *layout); + +int soundio_channel_layout_find_channel( + const struct SoundIoChannelLayout *layout, enum SoundIoChannelId channel); + + + +// Sample Formats + +int soundio_get_bytes_per_sample(enum SoundIoSampleFormat sample_format); + +static inline int soundio_get_bytes_per_frame(enum SoundIoSampleFormat sample_format, int channel_count) { + return soundio_get_bytes_per_sample(sample_format) * channel_count; +} + +static inline int soundio_get_bytes_per_second(enum SoundIoSampleFormat sample_format, + int channel_count, int sample_rate) +{ + return soundio_get_bytes_per_frame(sample_format, channel_count) * sample_rate; +} + +const char * soundio_sample_format_string(enum SoundIoSampleFormat sample_format); + + + +// Devices + +// returns -1 on error +int soundio_get_input_device_count(struct SoundIo *soundio); +int soundio_get_output_device_count(struct SoundIo *soundio); + +// returns NULL on error +// call soundio_audio_device_unref when you no longer have a reference to the pointer. +struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index); +struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index); + +// returns the index of the default input device, or -1 on error +int soundio_get_default_input_device_index(struct SoundIo *soundio); + +// returns the index of the default output device, or -1 on error +int soundio_get_default_output_device_index(struct SoundIo *soundio); + +void soundio_audio_device_ref(struct SoundIoDevice *device); +void soundio_audio_device_unref(struct SoundIoDevice *device); + +// the name is the identifier for the device. UTF-8 encoded +const char *soundio_audio_device_name(const struct SoundIoDevice *device); + +// UTF-8 encoded +const char *soundio_audio_device_description(const struct SoundIoDevice *device); + +const struct SoundIoChannelLayout *soundio_audio_device_channel_layout(const struct SoundIoDevice *device); +int soundio_audio_device_sample_rate(const struct SoundIoDevice *device); + +bool soundio_audio_device_equal( + const struct SoundIoDevice *a, + const struct SoundIoDevice *b); +enum SoundIoDevicePurpose soundio_device_purpose(const struct SoundIoDevice *device); + + + +// Output Devices + +int soundio_output_device_create(struct SoundIoDevice *audio_device, + enum SoundIoSampleFormat sample_format, + double latency, void *userdata, + void (*write_callback)(struct SoundIoOutputDevice *, int), + void (*underrun_callback)(struct SoundIoOutputDevice *), + struct SoundIoOutputDevice **out_output_device); +void soundio_output_device_destroy(struct SoundIoOutputDevice *device); + +int soundio_output_device_start(struct SoundIoOutputDevice *device); + +void soundio_output_device_fill_with_silence(struct SoundIoOutputDevice *device); + + +// number of frames available to write +int soundio_output_device_free_count(struct SoundIoOutputDevice *device); +void soundio_output_device_begin_write(struct SoundIoOutputDevice *device, + char **data, int *frame_count); +void soundio_output_device_write(struct SoundIoOutputDevice *device, + char *data, int frame_count); + +void soundio_output_device_clear_buffer(struct SoundIoOutputDevice *device); + + + +// Input Devices + +int soundio_input_device_create(struct SoundIoDevice *audio_device, + enum SoundIoSampleFormat sample_format, double latency, void *userdata, + void (*read_callback)(struct SoundIoOutputDevice *), + struct SoundIoOutputDevice **out_input_device); +void soundio_input_device_destroy(struct SoundIoOutputDevice *device); + +int soundio_input_device_start(struct SoundIoOutputDevice *device); +void soundio_input_device_peek(struct SoundIoOutputDevice *device, + const char **data, int *frame_count); +void soundio_input_device_drop(struct SoundIoOutputDevice *device); + +void soundio_input_device_clear_buffer(struct SoundIoOutputDevice *device); #ifdef __cplusplus } diff --git a/src/soundio.hpp b/src/soundio.hpp new file mode 100644 index 0000000..c8a78d7 --- /dev/null +++ b/src/soundio.hpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_SOUNDIO_HPP +#define SOUNDIO_SOUNDIO_HPP + +#include "soundio.h" +#include "list.hpp" + +struct SoundIoDevicesInfo { + SoundIoList devices; + // can be -1 when default device is unknown + int default_output_index; + int default_input_index; +}; + +#endif diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..8854bb8 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include +#include +#include + +#include "util.hpp" + +void panic(const char *format, ...) { + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + fprintf(stderr, "\n"); + va_end(ap); + abort(); +} diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 0000000..bab26dd --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_UTIL_HPP +#define SOUNDIO_UTIL_HPP + +#include +#include +#include + +template +__attribute__((malloc)) static inline T *allocate_nonzero(size_t count) { + T *ptr = reinterpret_cast(malloc(count * sizeof(T))); + if (ptr) { + for (size_t i = 0; i < count; i++) + new (&ptr[i]) T; + } + return ptr; +} + +template +__attribute__((malloc)) static inline T *allocate(size_t count) { + T *ptr = reinterpret_cast(calloc(count, sizeof(T))); + if (ptr) { + for (size_t i = 0; i < count; i++) + new (&ptr[i]) T; + } + return ptr; +} + +template +static inline T * reallocate_nonzero(T * old, size_t old_count, size_t new_count) { + assert(old_count <= new_count); + T * new_ptr = reinterpret_cast(realloc(old, new_count * sizeof(T))); + if (new_ptr) { + for (size_t i = old_count; i < new_count; i += 1) + new (&new_ptr[i]) T; + } + return new_ptr; +} + +template +static inline void deallocate(T * ptr, size_t count) { + if (ptr) { + for (size_t i = 0; i < count; i += 1) + ptr[i].~T(); + } + // keep this outside the if so that the if statement can be optimized out + // completely if T has no destructor + free(ptr); +} + +template +__attribute__((malloc)) static inline T * create_nonzero(Args... args) { + T * ptr = reinterpret_cast(malloc(sizeof(T))); + if (ptr) + new (ptr) T(args...); + return ptr; +} + +template +__attribute__((malloc)) static inline T * create(Args... args) { + T * ptr = reinterpret_cast(calloc(1, sizeof(T))); + if (ptr) + new (ptr) T(args...); + return ptr; +} + +template +static inline void destroy(T * ptr) { + if (ptr) + ptr[0].~T(); + // keep this outside the if so that the if statement can be optimized out + // completely if T has no destructor + free(ptr); +} + +void panic(const char *format, ...) + __attribute__((cold)) + __attribute__ ((noreturn)) + __attribute__ ((format (printf, 1, 2))); + +template +constexpr long array_length(const T (&)[n]) { + return n; +} + +template +static inline T max(T a, T b) { + return (a >= b) ? a : b; +} + +template +static inline T min(T a, T b) { + return (a <= b) ? a : b; +} + +#endif