mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2025-01-03 14:15:47 +00:00
extracted code from Genesis
This commit is contained in:
parent
0011571794
commit
e409400f04
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
52
README.md
52
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))
|
||||
|
|
|
@ -4,7 +4,7 @@ with import <nixpkgs> {}; {
|
|||
buildInputs = [
|
||||
alsaLib
|
||||
cmake
|
||||
gcc49
|
||||
gcc5
|
||||
libjack2
|
||||
libpulseaudio
|
||||
];
|
||||
|
|
87
example/list_devices.c
Normal file
87
example/list_devices.c
Normal file
|
@ -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 <soundio.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#include "soundio.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
12
example/sine.c
Normal file
12
example/sine.c
Normal file
|
@ -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;
|
||||
}
|
281
src/dummy.cpp
Normal file
281
src/dummy.cpp
Normal file
|
@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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<SoundIoDevicesInfo>();
|
||||
soundio->safe_devices_info->default_input_index = 0;
|
||||
soundio->safe_devices_info->default_output_index = 0;
|
||||
|
||||
// create output device
|
||||
{
|
||||
SoundIoDevice *device = create<SoundIoDevice>();
|
||||
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<SoundIoDevice>();
|
||||
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;
|
||||
}
|
||||
|
34
src/dummy.hpp
Normal file
34
src/dummy.hpp
Normal file
|
@ -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 <atomic>
|
||||
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
|
||||
|
||||
|
75
src/dummy_ring_buffer.cpp
Normal file
75
src/dummy_ring_buffer.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
#include "dummy_ring_buffer.hpp"
|
||||
#include "soundio.hpp"
|
||||
#include <math.h>
|
||||
|
||||
int soundio_dummy_ring_buffer_create(int requested_capacity, struct SoundIoDummyRingBuffer **out) {
|
||||
*out = nullptr;
|
||||
SoundIoDummyRingBuffer *rb = create<SoundIoDummyRingBuffer>();
|
||||
|
||||
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<char>(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);
|
||||
}
|
||||
|
38
src/dummy_ring_buffer.hpp
Normal file
38
src/dummy_ring_buffer.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef SOUNDIO_DUMMY_RING_BUFFER_HPP
|
||||
#define SOUNDIO_DUMMY_RING_BUFFER_HPP
|
||||
|
||||
#include "util.hpp"
|
||||
#include <atomic>
|
||||
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
|
||||
|
143
src/list.hpp
Normal file
143
src/list.hpp
Normal file
|
@ -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 <assert.h>
|
||||
|
||||
template<typename T>
|
||||
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
|
251
src/os.cpp
Normal file
251
src/os.cpp
Normal file
|
@ -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 <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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<SoundIoOsThread>();
|
||||
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<SoundIoOsMutex>();
|
||||
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<SoundIoOsCond>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
39
src/os.hpp
Normal file
39
src/os.hpp
Normal file
|
@ -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 <stdbool.h>
|
||||
|
||||
|
||||
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
|
865
src/pulseaudio.cpp
Normal file
865
src/pulseaudio.cpp
Normal file
|
@ -0,0 +1,865 @@
|
|||
#include "pulseaudio.hpp"
|
||||
#include "soundio.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
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<SoundIoDevice>();
|
||||
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<SoundIoDevice>();
|
||||
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<SoundIoDevicesInfo>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
54
src/pulseaudio.hpp
Normal file
54
src/pulseaudio.hpp
Normal file
|
@ -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 <pulse/pulseaudio.h>
|
||||
#include <atomic>
|
||||
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
|
|
@ -1 +0,0 @@
|
|||
#include "soundio.h"
|
474
src/soundio.cpp
Normal file
474
src/soundio.cpp
Normal file
|
@ -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 <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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<SoundIo>();
|
||||
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);
|
||||
}
|
293
src/soundio.h
293
src/soundio.h
|
@ -9,17 +9,310 @@
|
|||
#define SOUNDIO_SOUNDIO_H
|
||||
|
||||
#include "config.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
}
|
||||
|
|
21
src/soundio.hpp
Normal file
21
src/soundio.hpp
Normal file
|
@ -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<SoundIoDevice *> devices;
|
||||
// can be -1 when default device is unknown
|
||||
int default_output_index;
|
||||
int default_input_index;
|
||||
};
|
||||
|
||||
#endif
|
21
src/util.cpp
Normal file
21
src/util.cpp
Normal file
|
@ -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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#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();
|
||||
}
|
102
src/util.hpp
Normal file
102
src/util.hpp
Normal file
|
@ -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 <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <new>
|
||||
|
||||
template<typename T>
|
||||
__attribute__((malloc)) static inline T *allocate_nonzero(size_t count) {
|
||||
T *ptr = reinterpret_cast<T*>(malloc(count * sizeof(T)));
|
||||
if (ptr) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
new (&ptr[i]) T;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__attribute__((malloc)) static inline T *allocate(size_t count) {
|
||||
T *ptr = reinterpret_cast<T*>(calloc(count, sizeof(T)));
|
||||
if (ptr) {
|
||||
for (size_t i = 0; i < count; i++)
|
||||
new (&ptr[i]) T;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<T*>(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<typename T>
|
||||
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<typename T, typename... Args>
|
||||
__attribute__((malloc)) static inline T * create_nonzero(Args... args) {
|
||||
T * ptr = reinterpret_cast<T*>(malloc(sizeof(T)));
|
||||
if (ptr)
|
||||
new (ptr) T(args...);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
__attribute__((malloc)) static inline T * create(Args... args) {
|
||||
T * ptr = reinterpret_cast<T*>(calloc(1, sizeof(T)));
|
||||
if (ptr)
|
||||
new (ptr) T(args...);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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 <typename T, long n>
|
||||
constexpr long array_length(const T (&)[n]) {
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T max(T a, T b) {
|
||||
return (a >= b) ? a : b;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T min(T a, T b) {
|
||||
return (a <= b) ? a : b;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue