From 3c7a0a7a7cda7aead0f473051b4a549dd42ffbac Mon Sep 17 00:00:00 2001 From: BERTHAUT Florent Date: Sat, 14 Dec 2019 16:30:27 +0100 Subject: [PATCH] Added first files --- .gitmodules | 6 + SConstruct | 114 + bin/gdpd.gdns | 9 + bin/libgdpd.gdnlib | 18 + src/gdlibrary.cpp | 16 + src/gdpd.cpp | 170 + src/gdpd.hpp | 59 + src/godot-cpp | 1 + src/libpd | 1 + src/rtaudio/RtAudio.cpp | 10636 ++++++++++++++++++++++++++++++++++++++ src/rtaudio/RtAudio.h | 1203 +++++ src/rtaudio/RtAudio.os | Bin 0 -> 1493776 bytes 12 files changed, 12233 insertions(+) create mode 100644 .gitmodules create mode 100644 SConstruct create mode 100644 bin/gdpd.gdns create mode 100644 bin/libgdpd.gdnlib create mode 100644 src/gdlibrary.cpp create mode 100644 src/gdpd.cpp create mode 100644 src/gdpd.hpp create mode 160000 src/godot-cpp create mode 160000 src/libpd create mode 100644 src/rtaudio/RtAudio.cpp create mode 100644 src/rtaudio/RtAudio.h create mode 100644 src/rtaudio/RtAudio.os diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2e03585 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/libpd"] + path = src/libpd + url = https://github.com/libpd/libpd.git +[submodule "src/godot-cpp"] + path = src/godot-cpp + url = https://github.com/GodotNativeTools/godot-cpp diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..cdc0b7e --- /dev/null +++ b/SConstruct @@ -0,0 +1,114 @@ +#!python +import os, subprocess + +opts = Variables([], ARGUMENTS) + +# Gets the standard flags CC, CCX, etc. +env = DefaultEnvironment() + +# Define our options +opts.Add(EnumVariable('target', "Compilation target", 'debug', ['d', 'debug', 'r', 'release'])) +opts.Add(EnumVariable('platform', "Compilation platform", '', ['', 'windows', 'x11', 'linux', 'osx'])) +opts.Add(EnumVariable('p', "Compilation target, alias for 'platform'", '', ['', 'windows', 'x11', 'linux', 'osx'])) +opts.Add(BoolVariable('use_llvm', "Use the LLVM / Clang compiler", 'no')) +opts.Add(PathVariable('target_path', 'The path where the lib is installed.', 'bin/')) +opts.Add(PathVariable('target_name', 'The library name.', 'libgdpd', PathVariable.PathAccept)) + +# Local dependency paths, adapt them to your setup +godot_headers_path = "src/godot-cpp/godot_headers/" +cpp_bindings_path = "src/godot-cpp/" +cpp_library = "libgodot-cpp" + +# only support 64 at this time.. +bits = 64 + +# Updates the environment with the option variables. +opts.Update(env) + +# Process some arguments +if env['use_llvm']: + env['CC'] = 'clang' + env['CXX'] = 'clang++' + +if env['p'] != '': + env['platform'] = env['p'] + +if env['platform'] == '': + print("No valid target platform selected.") + quit(); + +# For the reference: +# - CCFLAGS are compilation flags shared between C and C++ +# - CFLAGS are for C-specific compilation flags +# - CXXFLAGS are for C++-specific compilation flags +# - CPPFLAGS are for pre-processor flags +# - CPPDEFINES are for pre-processor defines +# - LINKFLAGS are for linking flags + +# Check our platform specifics +if env['platform'] == "osx": + env['target_path'] += 'osx/' + cpp_library += '.osx' + if env['target'] in ('debug', 'd'): + env.Append(CCFLAGS=['-g', '-O2', '-arch', 'x86_64']) + env.Append(LINKFLAGS=['-arch', 'x86_64']) + else: + env.Append(CCFLAGS=['-g', '-O3', '-arch', 'x86_64']) + env.Append(LINKFLAGS=['-arch', 'x86_64']) + +elif env['platform'] in ('x11', 'linux'): + env['target_path'] += 'x11/' + cpp_library += '.linux' + env.Append(CPPDEFINES=['__UNIX_JACK__']) + env.Append(LINKFLAGS=['-ljack','-pthread']) + if env['target'] in ('debug', 'd'): + env.Append(CCFLAGS=['-fPIC', '-g3', '-Og']) + env.Append(CFLAGS=['-std=c11']) + env.Append(CXXFLAGS=['-std=c++17']) + else: + env.Append(CCFLAGS=['-fPIC', '-g', '-O3']) + env.Append(CFLAGS=['-std=c11']) + env.Append(CXXFLAGS=['-std=c++17']) + +elif env['platform'] == "windows": + env['target_path'] += 'win64/' + cpp_library += '.windows' + # This makes sure to keep the session environment variables on windows, + # that way you can run scons in a vs 2017 prompt and it will find all the required tools + env.Append(ENV=os.environ) + + env.Append(CPPDEFINES=['WIN32', '_WIN32', '_WINDOWS', '_CRT_SECURE_NO_WARNINGS']) + env.Append(CCFLAGS=['-W3', '-GR']) + if env['target'] in ('debug', 'd'): + env.Append(CPPDEFINES=['_DEBUG']) + env.Append(CCFLAGS=['-EHsc', '-MDd', '-ZI']) + env.Append(LINKFLAGS=['-DEBUG']) + else: + env.Append(CPPDEFINES=['NDEBUG']) + env.Append(CCFLAGS=['-O2', '-EHsc', '-MD']) + +if env['target'] in ('debug', 'd'): + cpp_library += '.debug' +else: + cpp_library += '.release' + +cpp_library += '.' + str(bits) + +# make sure our binding library is properly includes +env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/', 'src/libpd/cpp','src/libpd/pure-data/src', 'src/libpd/libpd_wrapper', 'src/libpd/libpd_wrapper/util', 'src/rtaudio']) +env.Append(LIBPATH=[cpp_bindings_path + 'bin/']) +env.Append(LIBS=[cpp_library]) +env.Append(CFLAGS=['-DUSEAPI_DUMMY', '-DPD', '-DHAVE_UNISTD_H', '-D_GNU_SOURCE']) + +# tweak this if you want to use different folders, or more folders, to store your source code in. +env.Append(CPPPATH=['src/']) + +#sources = Glob('src/*.cpp') +sources = Glob('src/*.cpp') + Glob('src/rtaudio/*.cpp') + Glob('src/libpd/libpd_wrapper/*.c') + Glob('src/libpd/libpd_wrapper/util/*.c') + Glob('src/libpd/pure-data/extra/**/*.c') + Glob('src/libpd/pure-data/src/[xmgz]_*.c') + Glob('src/libpd/pure-data/src/d_[acgmorsu]*.c') + Glob('src/libpd/pure-data/src/d_dac.c') + Glob('src/libpd/pure-data/src/d_delay.c') + Glob('src/libpd/pure-data/src/d_fft.c') + Glob('src/libpd/pure-data/src/d_fft_fftsg.c') + Glob('src/libpd/pure-data/src/d_filter.c') + Glob('src/libpd/pure-data/src/s_audio.c') + Glob('src/libpd/pure-data/src/s_audio_dummy.c') + Glob('src/libpd/pure-data/src/s_print.c') + Glob('src/libpd/pure-data/src/s_path.c') + Glob('src/libpd/pure-data/src/s_main.c') + Glob('src/libpd/pure-data/src/s_inter.c') + Glob('src/libpd/pure-data/src/s_utf8.c') + Glob('src/libpd/pure-data/src/s_loader.c') + +library = env.SharedLibrary(target=env['target_path'] + env['target_name'] , source=sources) + +Default(library) + +# Generates help for the -h scons option. +Help(opts.GenerateHelpText(env)) diff --git a/bin/gdpd.gdns b/bin/gdpd.gdns new file mode 100644 index 0000000..4a291d7 --- /dev/null +++ b/bin/gdpd.gdns @@ -0,0 +1,9 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/gdpd/bin/libgdpd.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "gdpd" +class_name = "Gdpd" +library = ExtResource( 1 ) +_sections_unfolded = [ "Resource" ] diff --git a/bin/libgdpd.gdnlib b/bin/libgdpd.gdnlib new file mode 100644 index 0000000..99933eb --- /dev/null +++ b/bin/libgdpd.gdnlib @@ -0,0 +1,18 @@ +[general] + +singleton=false +load_once=true +symbol_prefix="godot_" +reloadable=true + +[entry] + +X11.64="res://addons/gdpd/bin/x11/libgdpd.so" +Windows.64="res://addons/gdpd/bin/win/libgdpd.dll" +OSX.64="res://addons/gdpd/bin/osx/libgdpd.dylib" + +[dependencies] + +X11.64=[] +Windows.64=[ ] +OSX.64=[ ] diff --git a/src/gdlibrary.cpp b/src/gdlibrary.cpp new file mode 100644 index 0000000..0381d24 --- /dev/null +++ b/src/gdlibrary.cpp @@ -0,0 +1,16 @@ +#include "gdpd.hpp" + +extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o) { + godot::Godot::gdnative_init(o); +} + +extern "C" void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o) { + godot::Godot::gdnative_terminate(o); +} + +extern "C" void GDN_EXPORT godot_nativescript_init(void *handle) { + godot::Godot::nativescript_init(handle); + + godot::register_class(); +} + diff --git a/src/gdpd.cpp b/src/gdpd.cpp new file mode 100644 index 0000000..ddabb74 --- /dev/null +++ b/src/gdpd.cpp @@ -0,0 +1,170 @@ +#include "gdpd.hpp" + +using namespace godot; + +void Gdpd::_register_methods() { + register_method("init", &Gdpd::init); + register_method("openfile", &Gdpd::openfile); + register_method("closefile", &Gdpd::closefile); + register_method("has_message", &Gdpd::has_message); + register_method("get_next", &Gdpd::get_next); + register_method("start_message", &Gdpd::start_message); + register_method("add_symbol", &Gdpd::add_symbol); + register_method("add_float", &Gdpd::add_float); + register_method("finish_list", &Gdpd::finish_list); +} + +int Gdpd::audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData){ + // pass audio samples to/from libpd + int ticks = nBufferFrames / 64; + libpd_process_float(ticks, (float*)inputBuffer, (float*)outputBuffer); + return 0; +} + + +Gdpd::Gdpd() { +} + +void Gdpd::_init() { + +} + +Gdpd::~Gdpd() { + // add your cleanup here +} + +int Gdpd::init(int nbInputs, int nbOutputs, int sampleRate) { + + + if(!m_pd.init(nbInputs, nbOutputs, sampleRate, true)) { + Godot::print("GDPD : Error starting libpd"); + return 1; + } + + /* + int bufsize = 128; + m_inBuf = new float[bufsize * nbInputs]; + m_outBuf = new float[bufsize * nbOutputs]; + */ + + //create message array + m_messages = new Array(); + + //create message hook + m_pd.subscribe("to_gdpd"); + m_pd.setReceiver(this); + + //start dsp + m_pd.computeAudio(true); + + //intialize rtaudio + if(m_audio.getDeviceCount()==0){ + Godot::print("There are no available sound devices."); + } + + RtAudio::StreamParameters outParams, inParams; + unsigned int sr = m_audio.getDeviceInfo(outParams.deviceId).preferredSampleRate; + outParams.deviceId = m_audio.getDefaultOutputDevice(); + inParams.deviceId = m_audio.getDefaultOutputDevice(); + outParams.nChannels = nbInputs; + inParams.nChannels = nbOutputs; + m_bufferFrames = 128; + + RtAudio::StreamOptions options; + options.streamName = "gdpd"; + options.flags = RTAUDIO_SCHEDULE_REALTIME; + if(m_audio.getCurrentApi() != RtAudio::MACOSX_CORE) { + options.flags |= RTAUDIO_MINIMIZE_LATENCY; // CoreAudio doesn't seem to like this + } + try { + m_audio.openStream(&outParams, &inParams, RTAUDIO_FLOAT32, + sr, &m_bufferFrames, &audioCallback, + &m_pd, &options); + m_audio.startStream(); + } + catch(RtAudioError& e) { + Godot::print(e.getMessage().c_str()); + } + + + Godot::print("GDPD : Initialized"); + + return 0; +} + +void Gdpd::openfile(godot::String baseStr, godot::String dirStr) { + std::wstring baseWs = baseStr.unicode_str(); + std::string baseS(baseWs.begin(), baseWs.end()); + std::wstring dirWs = dirStr.unicode_str(); + std::string dirS(dirWs.begin(), dirWs.end()); + + libpd_openfile(baseS.c_str(), dirS.c_str()); + + Godot::print("GDPD : Opened patch"); +} + +void Gdpd::closefile() { + m_pd.closePatch(m_patch); +} + +bool Gdpd::has_message() { + //receive new messages + m_pd.receiveMessages(); + + //return if more than one message + int size = m_messages->size(); + return size>0; +} + +Array Gdpd::get_next() { + Array msg = m_messages->pop_front(); + return msg; +} + +int Gdpd::blocksize() { + int blocksize = libpd_blocksize(); + return blocksize; +} + +int Gdpd::start_message(int nbValues) { + int res = libpd_start_message(nbValues); + return res; +} + +void Gdpd::add_symbol(String symbStr) { + std::wstring symbWs = symbStr.unicode_str(); + std::string symbS(symbWs.begin(), symbWs.end()); + libpd_add_symbol(symbS.c_str()); +} + +void Gdpd::add_float(float val) { + libpd_add_float(val); +} + +int Gdpd::finish_list(String destStr) { + std::wstring destWs = destStr.unicode_str(); + std::string destS(destWs.begin(), destWs.end()); + int res = libpd_finish_list(destS.c_str()); + return res; +} + + +void Gdpd::print(const std::string& message) { + Godot::print(message.c_str()); +} + +void Gdpd::receiveList(const std::string& dest, const pd::List& list) { + Array gdlist; + + for(int i = 0; i < list.len(); ++i) { + if(list.isFloat(i)) { + gdlist.push_back(list.getFloat(i)); + } + else if(list.isSymbol(i)) { + String symbStr(list.getSymbol(i).c_str()); + gdlist.push_back(symbStr); + } + } + + m_messages->push_back(gdlist); +} diff --git a/src/gdpd.hpp b/src/gdpd.hpp new file mode 100644 index 0000000..51c6edf --- /dev/null +++ b/src/gdpd.hpp @@ -0,0 +1,59 @@ +#ifndef GDPD_H +#define GDPD_H + +#include +#include +#include + +#include +#include + +#include "PdBase.hpp" +#include "PdReceiver.hpp" +#include "RtAudio.h" + +namespace godot { + +class Gdpd : public godot::AudioStreamPlayer, public pd::PdReceiver { + GODOT_CLASS(Gdpd, AudioStreamPlayer) + +private: + float *m_inBuf; + float *m_outBuf; + Array* m_messages; + pd::PdBase m_pd; + pd::Patch m_patch; + RtAudio m_audio; + unsigned int m_bufferFrames; + +public: + static void _register_methods(); + + Gdpd(); + ~Gdpd(); + + void _init(); + + //libpd functions + int init(int nbInputs, int nbOutputs, int sampleRate); + void openfile(String basename, String dirname); + void closefile(); + bool has_message(); + Array get_next(); + int blocksize(); + int start_message(int nbValues); + void add_symbol(String symbStr); + void add_float(float val); + int finish_list(String destStr); + + //libpd hooks + void print(const std::string& message); + void receiveList(const std::string& dest, const pd::List& list); + + static int audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData); + +}; + +} + +#endif diff --git a/src/godot-cpp b/src/godot-cpp new file mode 160000 index 0000000..3ee07f6 --- /dev/null +++ b/src/godot-cpp @@ -0,0 +1 @@ +Subproject commit 3ee07f652bbbe91630a8346e3fe39a05f0f1d76a diff --git a/src/libpd b/src/libpd new file mode 160000 index 0000000..e07d211 --- /dev/null +++ b/src/libpd @@ -0,0 +1 @@ +Subproject commit e07d211d7b7e5f8dd6d2e3ddce557ae453161a14 diff --git a/src/rtaudio/RtAudio.cpp b/src/rtaudio/RtAudio.cpp new file mode 100644 index 0000000..0837d98 --- /dev/null +++ b/src/rtaudio/RtAudio.cpp @@ -0,0 +1,10636 @@ +/************************************************************************/ +/*! \class RtAudio + \brief Realtime audio i/o C++ classes. + + RtAudio provides a common API (Application Programming Interface) + for realtime audio input/output across Linux (native ALSA, Jack, + and OSS), Macintosh OS X (CoreAudio and Jack), and Windows + (DirectSound, ASIO and WASAPI) operating systems. + + RtAudio GitHub site: https://github.com/thestk/rtaudio + RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ + + RtAudio: realtime audio i/o C++ classes + Copyright (c) 2001-2019 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/************************************************************************/ + +// RtAudio: Version 5.1.0 + +#include "RtAudio.h" +#include +#include +#include +#include +#include +#include + +// Static variable definitions. +const unsigned int RtApi::MAX_SAMPLE_RATES = 14; +const unsigned int RtApi::SAMPLE_RATES[] = { + 4000, 5512, 8000, 9600, 11025, 16000, 22050, + 32000, 44100, 48000, 88200, 96000, 176400, 192000 +}; + +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) + #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) + #define MUTEX_DESTROY(A) DeleteCriticalSection(A) + #define MUTEX_LOCK(A) EnterCriticalSection(A) + #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) + + #include "tchar.h" + + static std::string convertCharPointerToStdString(const char *text) + { + return std::string(text); + } + + static std::string convertCharPointerToStdString(const wchar_t *text) + { + int length = WideCharToMultiByte(CP_UTF8, 0, text, -1, NULL, 0, NULL, NULL); + std::string s( length-1, '\0' ); + WideCharToMultiByte(CP_UTF8, 0, text, -1, &s[0], length, NULL, NULL); + return s; + } + +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) + // pthread API + #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) + #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) + #define MUTEX_LOCK(A) pthread_mutex_lock(A) + #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) +#else + #define MUTEX_INITIALIZE(A) abs(*A) // dummy definitions + #define MUTEX_DESTROY(A) abs(*A) // dummy definitions +#endif + +// *************************************************** // +// +// RtAudio definitions. +// +// *************************************************** // + +std::string RtAudio :: getVersion( void ) +{ + return RTAUDIO_VERSION; +} + +// Define API names and display names. +// Must be in same order as API enum. +extern "C" { +const char* rtaudio_api_names[][2] = { + { "unspecified" , "Unknown" }, + { "alsa" , "ALSA" }, + { "pulse" , "Pulse" }, + { "oss" , "OpenSoundSystem" }, + { "jack" , "Jack" }, + { "core" , "CoreAudio" }, + { "wasapi" , "WASAPI" }, + { "asio" , "ASIO" }, + { "ds" , "DirectSound" }, + { "dummy" , "Dummy" }, +}; +const unsigned int rtaudio_num_api_names = + sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]); + +// The order here will control the order of RtAudio's API search in +// the constructor. +extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { +#if defined(__UNIX_JACK__) + RtAudio::UNIX_JACK, +#endif +#if defined(__LINUX_PULSE__) + RtAudio::LINUX_PULSE, +#endif +#if defined(__LINUX_ALSA__) + RtAudio::LINUX_ALSA, +#endif +#if defined(__LINUX_OSS__) + RtAudio::LINUX_OSS, +#endif +#if defined(__WINDOWS_ASIO__) + RtAudio::WINDOWS_ASIO, +#endif +#if defined(__WINDOWS_WASAPI__) + RtAudio::WINDOWS_WASAPI, +#endif +#if defined(__WINDOWS_DS__) + RtAudio::WINDOWS_DS, +#endif +#if defined(__MACOSX_CORE__) + RtAudio::MACOSX_CORE, +#endif +#if defined(__RTAUDIO_DUMMY__) + RtAudio::RTAUDIO_DUMMY, +#endif + RtAudio::UNSPECIFIED, +}; +extern "C" const unsigned int rtaudio_num_compiled_apis = + sizeof(rtaudio_compiled_apis)/sizeof(rtaudio_compiled_apis[0])-1; +} + +// This is a compile-time check that rtaudio_num_api_names == RtAudio::NUM_APIS. +// If the build breaks here, check that they match. +template class StaticAssert { private: StaticAssert() {} }; +template<> class StaticAssert{ public: StaticAssert() {} }; +class StaticAssertions { StaticAssertions() { + StaticAssert(); +}}; + +void RtAudio :: getCompiledApi( std::vector &apis ) +{ + apis = std::vector(rtaudio_compiled_apis, + rtaudio_compiled_apis + rtaudio_num_compiled_apis); +} + +std::string RtAudio :: getApiName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return ""; + return rtaudio_api_names[api][0]; +} + +std::string RtAudio :: getApiDisplayName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return "Unknown"; + return rtaudio_api_names[api][1]; +} + +RtAudio::Api RtAudio :: getCompiledApiByName( const std::string &name ) +{ + unsigned int i=0; + for (i = 0; i < rtaudio_num_compiled_apis; ++i) + if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][0]) + return rtaudio_compiled_apis[i]; + return RtAudio::UNSPECIFIED; +} + +void RtAudio :: openRtApi( RtAudio::Api api ) +{ + if ( rtapi_ ) + delete rtapi_; + rtapi_ = 0; + +#if defined(__UNIX_JACK__) + if ( api == UNIX_JACK ) + rtapi_ = new RtApiJack(); +#endif +#if defined(__LINUX_ALSA__) + if ( api == LINUX_ALSA ) + rtapi_ = new RtApiAlsa(); +#endif +#if defined(__LINUX_PULSE__) + if ( api == LINUX_PULSE ) + rtapi_ = new RtApiPulse(); +#endif +#if defined(__LINUX_OSS__) + if ( api == LINUX_OSS ) + rtapi_ = new RtApiOss(); +#endif +#if defined(__WINDOWS_ASIO__) + if ( api == WINDOWS_ASIO ) + rtapi_ = new RtApiAsio(); +#endif +#if defined(__WINDOWS_WASAPI__) + if ( api == WINDOWS_WASAPI ) + rtapi_ = new RtApiWasapi(); +#endif +#if defined(__WINDOWS_DS__) + if ( api == WINDOWS_DS ) + rtapi_ = new RtApiDs(); +#endif +#if defined(__MACOSX_CORE__) + if ( api == MACOSX_CORE ) + rtapi_ = new RtApiCore(); +#endif +#if defined(__RTAUDIO_DUMMY__) + if ( api == RTAUDIO_DUMMY ) + rtapi_ = new RtApiDummy(); +#endif +} + +RtAudio :: RtAudio( RtAudio::Api api ) +{ + rtapi_ = 0; + + if ( api != UNSPECIFIED ) { + // Attempt to open the specified API. + openRtApi( api ); + if ( rtapi_ ) return; + + // No compiled support for specified API value. Issue a debug + // warning and continue as if no API was specified. + std::cerr << "\nRtAudio: no compiled support for specified API argument!\n" << std::endl; + } + + // Iterate through the compiled APIs and return as soon as we find + // one with at least one device or we reach the end of the list. + std::vector< RtAudio::Api > apis; + getCompiledApi( apis ); + for ( unsigned int i=0; igetDeviceCount() ) break; + } + + if ( rtapi_ ) return; + + // It should not be possible to get here because the preprocessor + // definition __RTAUDIO_DUMMY__ is automatically defined if no + // API-specific definitions are passed to the compiler. But just in + // case something weird happens, we'll thow an error. + std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; + throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); +} + +RtAudio :: ~RtAudio() +{ + if ( rtapi_ ) + delete rtapi_; +} + +void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) +{ + return rtapi_->openStream( outputParameters, inputParameters, format, + sampleRate, bufferFrames, callback, + userData, options, errorCallback ); +} + +// *************************************************** // +// +// Public RtApi definitions (see end of file for +// private or protected utility functions). +// +// *************************************************** // + +RtApi :: RtApi() +{ + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; + stream_.apiHandle = 0; + stream_.userBuffer[0] = 0; + stream_.userBuffer[1] = 0; + MUTEX_INITIALIZE( &stream_.mutex ); + showWarnings_ = true; + firstErrorOccurred_ = false; +} + +RtApi :: ~RtApi() +{ + MUTEX_DESTROY( &stream_.mutex ); +} + +void RtApi :: openStream( RtAudio::StreamParameters *oParams, + RtAudio::StreamParameters *iParams, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, + RtAudioCallback callback, void *userData, + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) +{ + if ( stream_.state != STREAM_CLOSED ) { + errorText_ = "RtApi::openStream: a stream is already open!"; + error( RtAudioError::INVALID_USE ); + return; + } + + // Clear stream information potentially left from a previously open stream. + clearStreamInfo(); + + if ( oParams && oParams->nChannels < 1 ) { + errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( iParams && iParams->nChannels < 1 ) { + errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( oParams == NULL && iParams == NULL ) { + errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; + error( RtAudioError::INVALID_USE ); + return; + } + + if ( formatBytes(format) == 0 ) { + errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; + error( RtAudioError::INVALID_USE ); + return; + } + + unsigned int nDevices = getDeviceCount(); + unsigned int oChannels = 0; + if ( oParams ) { + oChannels = oParams->nChannels; + if ( oParams->deviceId >= nDevices ) { + errorText_ = "RtApi::openStream: output device parameter value is invalid."; + error( RtAudioError::INVALID_USE ); + return; + } + } + + unsigned int iChannels = 0; + if ( iParams ) { + iChannels = iParams->nChannels; + if ( iParams->deviceId >= nDevices ) { + errorText_ = "RtApi::openStream: input device parameter value is invalid."; + error( RtAudioError::INVALID_USE ); + return; + } + } + + bool result; + + if ( oChannels > 0 ) { + + result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, + sampleRate, format, bufferFrames, options ); + if ( result == false ) { + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + if ( iChannels > 0 ) { + + result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, + sampleRate, format, bufferFrames, options ); + if ( result == false ) { + if ( oChannels > 0 ) closeStream(); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.callbackInfo.callback = (void *) callback; + stream_.callbackInfo.userData = userData; + stream_.callbackInfo.errorCallback = (void *) errorCallback; + + if ( options ) options->numberOfBuffers = stream_.nBuffers; + stream_.state = STREAM_STOPPED; +} + +unsigned int RtApi :: getDefaultInputDevice( void ) +{ + // Should be implemented in subclasses if possible. + return 0; +} + +unsigned int RtApi :: getDefaultOutputDevice( void ) +{ + // Should be implemented in subclasses if possible. + return 0; +} + +void RtApi :: closeStream( void ) +{ + // MUST be implemented in subclasses! + return; +} + +bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) +{ + // MUST be implemented in subclasses! + return FAILURE; +} + +void RtApi :: tickStreamTime( void ) +{ + // Subclasses that do not provide their own implementation of + // getStreamTime should call this function once per buffer I/O to + // provide basic stream time support. + + stream_.streamTime += ( stream_.bufferSize * 1.0 / stream_.sampleRate ); + +#if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); +#endif +} + +long RtApi :: getStreamLatency( void ) +{ + verifyStream(); + + long totalLatency = 0; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + totalLatency = stream_.latency[0]; + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) + totalLatency += stream_.latency[1]; + + return totalLatency; +} + +double RtApi :: getStreamTime( void ) +{ + verifyStream(); + +#if defined( HAVE_GETTIMEOFDAY ) + // Return a very accurate estimate of the stream time by + // adding in the elapsed time since the last tick. + struct timeval then; + struct timeval now; + + if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 ) + return stream_.streamTime; + + gettimeofday( &now, NULL ); + then = stream_.lastTickTimestamp; + return stream_.streamTime + + ((now.tv_sec + 0.000001 * now.tv_usec) - + (then.tv_sec + 0.000001 * then.tv_usec)); +#else + return stream_.streamTime; +#endif +} + +void RtApi :: setStreamTime( double time ) +{ + verifyStream(); + + if ( time >= 0.0 ) + stream_.streamTime = time; +#if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); +#endif +} + +unsigned int RtApi :: getStreamSampleRate( void ) +{ + verifyStream(); + + return stream_.sampleRate; +} + + +// *************************************************** // +// +// OS/API-specific methods. +// +// *************************************************** // + +#if defined(__MACOSX_CORE__) + +// The OS X CoreAudio API is designed to use a separate callback +// procedure for each of its audio devices. A single RtAudio duplex +// stream using two different devices is supported here, though it +// cannot be guaranteed to always behave correctly because we cannot +// synchronize these two callbacks. +// +// A property listener is installed for over/underrun information. +// However, no functionality is currently provided to allow property +// listeners to trigger user handlers because it is unclear what could +// be done if a critical stream parameter (buffer size, sample rate, +// device disconnect) notification arrived. The listeners entail +// quite a bit of extra code and most likely, a user program wouldn't +// be prepared for the result anyway. However, we do provide a flag +// to the client callback function to inform of an over/underrun. + +// A structure to hold various information related to the CoreAudio API +// implementation. +struct CoreHandle { + AudioDeviceID id[2]; // device ids +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceIOProcID procId[2]; +#endif + UInt32 iStream[2]; // device stream index (or first if using multiple) + UInt32 nStreams[2]; // number of streams to use + bool xrun[2]; + char *deviceBuffer; + pthread_cond_t condition; + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + + CoreHandle() + :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +RtApiCore:: RtApiCore() +{ +#if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) + // This is a largely undocumented but absolutely necessary + // requirement starting with OS-X 10.6. If not called, queries and + // updates to various audio device properties are not handled + // correctly. + CFRunLoopRef theRunLoop = NULL; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); + if ( result != noErr ) { + errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; + error( RtAudioError::WARNING ); + } +#endif +} + +RtApiCore :: ~RtApiCore() +{ + // The subclass destructor gets called before the base class + // destructor, so close an existing stream before deallocating + // apiDeviceId memory. + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiCore :: getDeviceCount( void ) +{ + // Find out how many audio devices there are, if any. + UInt32 dataSize; + AudioObjectPropertyAddress propertyAddress = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; + error( RtAudioError::WARNING ); + return 0; + } + + return dataSize / sizeof( AudioDeviceID ); +} + +unsigned int RtApiCore :: getDefaultInputDevice( void ) +{ + unsigned int nDevices = getDeviceCount(); + if ( nDevices <= 1 ) return 0; + + AudioDeviceID id; + UInt32 dataSize = sizeof( AudioDeviceID ); + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; + error( RtAudioError::WARNING ); + return 0; + } + + dataSize *= nDevices; + AudioDeviceID deviceList[ nDevices ]; + property.mSelector = kAudioHardwarePropertyDevices; + result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; + error( RtAudioError::WARNING ); + return 0; + } + + for ( unsigned int i=0; i= nDevices ) { + errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + AudioDeviceID deviceList[ nDevices ]; + UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, + 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; + error( RtAudioError::WARNING ); + return info; + } + + AudioDeviceID id = deviceList[ device ]; + + // Get the device name. + info.name.erase(); + CFStringRef cfname; + dataSize = sizeof( CFStringRef ); + property.mSelector = kAudioObjectPropertyManufacturer; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + //const char *mname = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); + int length = CFStringGetLength(cfname); + char *mname = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, mname, length * 3 + 1, kCFStringEncodingUTF8); +#else + CFStringGetCString(cfname, mname, length * 3 + 1, CFStringGetSystemEncoding()); +#endif + info.name.append( (const char *)mname, strlen(mname) ); + info.name.append( ": " ); + CFRelease( cfname ); + free(mname); + + property.mSelector = kAudioObjectPropertyName; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &cfname ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + //const char *name = CFStringGetCStringPtr( cfname, CFStringGetSystemEncoding() ); + length = CFStringGetLength(cfname); + char *name = (char *)malloc(length * 3 + 1); +#if defined( UNICODE ) || defined( _UNICODE ) + CFStringGetCString(cfname, name, length * 3 + 1, kCFStringEncodingUTF8); +#else + CFStringGetCString(cfname, name, length * 3 + 1, CFStringGetSystemEncoding()); +#endif + info.name.append( (const char *)name, strlen(name) ); + CFRelease( cfname ); + free(name); + + // Get the output stream "configuration". + AudioBufferList *bufferList = nil; + property.mSelector = kAudioDevicePropertyStreamConfiguration; + property.mScope = kAudioDevicePropertyScopeOutput; + // property.mElement = kAudioObjectPropertyElementWildcard; + dataSize = 0; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; + error( RtAudioError::WARNING ); + return info; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if ( result != noErr || dataSize == 0 ) { + free( bufferList ); + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get output channel information. + unsigned int i, nStreams = bufferList->mNumberBuffers; + for ( i=0; imBuffers[i].mNumberChannels; + free( bufferList ); + + // Get the input stream "configuration". + property.mScope = kAudioDevicePropertyScopeInput; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; + error( RtAudioError::WARNING ); + return info; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if (result != noErr || dataSize == 0) { + free( bufferList ); + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get input channel information. + nStreams = bufferList->mNumberBuffers; + for ( i=0; imBuffers[i].mNumberChannels; + free( bufferList ); + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Probe the device sample rates. + bool isInput = false; + if ( info.outputChannels == 0 ) isInput = true; + + // Determine the supported sample rates. + property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; + if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != kAudioHardwareNoError || dataSize == 0 ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + UInt32 nRanges = dataSize / sizeof( AudioValueRange ); + AudioValueRange rangeList[ nRanges ]; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList ); + if ( result != kAudioHardwareNoError ) { + errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // The sample rate reporting mechanism is a bit of a mystery. It + // seems that it can either return individual rates or a range of + // rates. I assume that if the min / max range values are the same, + // then that represents a single supported rate and if the min / max + // range values are different, the device supports an arbitrary + // range of values (though there might be multiple ranges, so we'll + // use the most conservative range). + Float64 minimumRate = 1.0, maximumRate = 10000000000.0; + bool haveValueRange = false; + info.sampleRates.clear(); + for ( UInt32 i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = tmpSr; + + } else { + haveValueRange = true; + if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; + if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; + } + } + + if ( haveValueRange ) { + for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) { + info.sampleRates.push_back( SAMPLE_RATES[k] ); + + if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + } + } + } + + // Sort and remove any redundant values + std::sort( info.sampleRates.begin(), info.sampleRates.end() ); + info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); + + if ( info.sampleRates.size() == 0 ) { + errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // CoreAudio always uses 32-bit floating point data for PCM streams. + // Thus, any other "physical" formats supported by the device are of + // no interest to the client. + info.nativeFormats = RTAUDIO_FLOAT32; + + if ( info.outputChannels > 0 ) + if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; + if ( info.inputChannels > 0 ) + if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; + + info.probed = true; + return info; +} + +static OSStatus callbackHandler( AudioDeviceID inDevice, + const AudioTimeStamp* /*inNow*/, + const AudioBufferList* inInputData, + const AudioTimeStamp* /*inInputTime*/, + AudioBufferList* outOutputData, + const AudioTimeStamp* /*inOutputTime*/, + void* infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiCore *object = (RtApiCore *) info->object; + if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) + return kAudioHardwareUnspecifiedError; + else + return kAudioHardwareNoError; +} + +static OSStatus xrunListener( AudioObjectID /*inDevice*/, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void* handlePointer ) +{ + CoreHandle *handle = (CoreHandle *) handlePointer; + for ( UInt32 i=0; ixrun[1] = true; + else + handle->xrun[0] = true; + } + } + + return kAudioHardwareNoError; +} + +static OSStatus rateListener( AudioObjectID inDevice, + UInt32 /*nAddresses*/, + const AudioObjectPropertyAddress /*properties*/[], + void* ratePointer ) +{ + Float64 *rate = (Float64 *) ratePointer; + UInt32 dataSize = sizeof( Float64 ); + AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); + return kAudioHardwareNoError; +} + +bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + // Get device ID + unsigned int nDevices = getDeviceCount(); + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiCore::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiCore::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + AudioDeviceID deviceList[ nDevices ]; + UInt32 dataSize = sizeof( AudioDeviceID ) * nDevices; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, + 0, NULL, &dataSize, (void *) &deviceList ); + if ( result != noErr ) { + errorText_ = "RtApiCore::probeDeviceOpen: OS-X system error getting device IDs."; + return FAILURE; + } + + AudioDeviceID id = deviceList[ device ]; + + // Setup for stream mode. + bool isInput = false; + if ( mode == INPUT ) { + isInput = true; + property.mScope = kAudioDevicePropertyScopeInput; + } + else + property.mScope = kAudioDevicePropertyScopeOutput; + + // Get the stream "configuration". + AudioBufferList *bufferList = nil; + dataSize = 0; + property.mSelector = kAudioDevicePropertyStreamConfiguration; + result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); + if ( result != noErr || dataSize == 0 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration info for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Allocate the AudioBufferList. + bufferList = (AudioBufferList *) malloc( dataSize ); + if ( bufferList == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: memory error allocating AudioBufferList."; + return FAILURE; + } + + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); + if (result != noErr || dataSize == 0) { + free( bufferList ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream configuration for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Search for one or more streams that contain the desired number of + // channels. CoreAudio devices can have an arbitrary number of + // streams and each stream can have an arbitrary number of channels. + // For each stream, a single buffer of interleaved samples is + // provided. RtAudio prefers the use of one stream of interleaved + // data or multiple consecutive single-channel streams. However, we + // now support multiple consecutive multi-channel streams of + // interleaved data as well. + UInt32 iStream, offsetCounter = firstChannel; + UInt32 nStreams = bufferList->mNumberBuffers; + bool monoMode = false; + bool foundStream = false; + + // First check that the device supports the requested number of + // channels. + UInt32 deviceChannels = 0; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + + if ( deviceChannels < ( channels + firstChannel ) ) { + free( bufferList ); + errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << device << ") does not support the requested channel count."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Look for a single stream meeting our needs. + UInt32 firstStream, streamCount = 1, streamChannels = 0, channelOffset = 0; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + if ( streamChannels >= channels + offsetCounter ) { + firstStream = iStream; + channelOffset = offsetCounter; + foundStream = true; + break; + } + if ( streamChannels > offsetCounter ) break; + offsetCounter -= streamChannels; + } + + // If we didn't find a single stream above, then we should be able + // to meet the channel specification with multiple streams. + if ( foundStream == false ) { + monoMode = true; + offsetCounter = firstChannel; + for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; + if ( streamChannels > offsetCounter ) break; + offsetCounter -= streamChannels; + } + + firstStream = iStream; + channelOffset = offsetCounter; + Int32 channelCounter = channels + offsetCounter - streamChannels; + + if ( streamChannels > 1 ) monoMode = false; + while ( channelCounter > 0 ) { + streamChannels = bufferList->mBuffers[++iStream].mNumberChannels; + if ( streamChannels > 1 ) monoMode = false; + channelCounter -= streamChannels; + streamCount++; + } + } + + free( bufferList ); + + // Determine the buffer size. + AudioValueRange bufferRange; + dataSize = sizeof( AudioValueRange ); + property.mSelector = kAudioDevicePropertyBufferFrameSizeRange; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); + + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMinimum; + else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned long) bufferRange.mMaximum; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned long) bufferRange.mMinimum; + + // Set the buffer size. For multiple streams, I'm assuming we only + // need to make this setting for the master channel. + UInt32 theSize = (UInt32) *bufferSize; + dataSize = sizeof( UInt32 ); + property.mSelector = kAudioDevicePropertyBufferFrameSize; + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); + + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // If attempting to setup a duplex stream, the bufferSize parameter + // MUST be the same in both directions! + *bufferSize = theSize; + if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 1; + + // Try to set "hog" mode ... it's not clear to me this is working. + if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { + pid_t hog_pid; + dataSize = sizeof( hog_pid ); + property.mSelector = kAudioDevicePropertyHogMode; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + if ( hog_pid != getpid() ) { + hog_pid = getpid(); + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + } + + // Check and if necessary, change the sample rate for the device. + Float64 nominalRate; + dataSize = sizeof( Float64 ); + property.mSelector = kAudioDevicePropertyNominalSampleRate; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Only change the sample rate if off by more than 1 Hz. + if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { + + // Set a property listener for the sample rate change + Float64 reportedRate = 0.0; + AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + nominalRate = (Float64) sampleRate; + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); + if ( result != noErr ) { + AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Now wait until the reported nominal rate is what we just set. + UInt32 microCounter = 0; + while ( reportedRate != nominalRate ) { + microCounter += 5000; + if ( microCounter > 5000000 ) break; + usleep( 5000 ); + } + + // Remove the property listener. + AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + + if ( microCounter > 5000000 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Now set the stream format for all streams. Also, check the + // physical format of the device and change that if necessary. + AudioStreamBasicDescription description; + dataSize = sizeof( AudioStreamBasicDescription ); + property.mSelector = kAudioStreamPropertyVirtualFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the sample rate and data format id. However, only make the + // change if the sample rate is not within 1.0 of the desired + // rate and the format is not linear pcm. + bool updateFormat = false; + if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { + description.mSampleRate = (Float64) sampleRate; + updateFormat = true; + } + + if ( description.mFormatID != kAudioFormatLinearPCM ) { + description.mFormatID = kAudioFormatLinearPCM; + updateFormat = true; + } + + if ( updateFormat ) { + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Now check the physical format. + property.mSelector = kAudioStreamPropertyPhysicalFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + //std::cout << "Current physical stream format:" << std::endl; + //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; + //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; + //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; + //std::cout << " sample rate = " << description.mSampleRate << std::endl; + + if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { + description.mFormatID = kAudioFormatLinearPCM; + //description.mSampleRate = (Float64) sampleRate; + AudioStreamBasicDescription testDescription = description; + UInt32 formatFlags; + + // We'll try higher bit rates first and then work our way down. + std::vector< std::pair > physicalFormats; + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; + physicalFormats.push_back( std::pair( 32, formatFlags ) ); + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; + physicalFormats.push_back( std::pair( 32, formatFlags ) ); + physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed + formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); + physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low + formatFlags |= kAudioFormatFlagIsAlignedHigh; + physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; + physicalFormats.push_back( std::pair( 16, formatFlags ) ); + physicalFormats.push_back( std::pair( 8, formatFlags ) ); + + bool setPhysicalFormat = false; + for( unsigned int i=0; iflags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + if ( monoMode == true ) stream_.deviceInterleaved[mode] = false; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( streamCount == 1 ) { + if ( stream_.nUserChannels[mode] > 1 && + stream_.userInterleaved != stream_.deviceInterleaved[mode] ) + stream_.doConvertBuffer[mode] = true; + } + else if ( monoMode && stream_.userInterleaved ) + stream_.doConvertBuffer[mode] = true; + + // Allocate our CoreHandle structure for the stream. + CoreHandle *handle = 0; + if ( stream_.apiHandle == 0 ) { + try { + handle = new CoreHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory."; + goto error; + } + + if ( pthread_cond_init( &handle->condition, NULL ) ) { + errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + stream_.apiHandle = (void *) handle; + } + else + handle = (CoreHandle *) stream_.apiHandle; + handle->iStream[mode] = firstStream; + handle->nStreams[mode] = streamCount; + handle->id[mode] = id; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + // stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) ); + memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + // If possible, we will make use of the CoreAudio stream buffers as + // "device buffers". However, we can't do this if using multiple + // streams. + if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.sampleRate = sampleRate; + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + stream_.callbackInfo.object = (void *) this; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) { + if ( streamCount > 1 ) setConvertInfo( mode, 0 ); + else setConvertInfo( mode, channelOffset ); + } + + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device ) + // Only one callback procedure per device. + stream_.mode = DUPLEX; + else { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] ); +#else + // deprecated in favor of AudioDeviceCreateIOProcID() + result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); +#endif + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << device << ")."; + errorText_ = errorStream_.str(); + goto error; + } + if ( stream_.mode == OUTPUT && mode == INPUT ) + stream_.mode = DUPLEX; + else + stream_.mode = mode; + } + + // Setup the device property listener for over/underload. + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiCore :: closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if (handle) { + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing property listener!"; + error( RtAudioError::WARNING ); + } + } + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], callbackHandler ); +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); +#else + // deprecated in favor of AudioDeviceDestroyIOProcID() + AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); +#endif + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + if (handle) { + AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + property.mSelector = kAudioDeviceProcessorOverload; + property.mScope = kAudioObjectPropertyScopeGlobal; + if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { + errorText_ = "RtApiCore::closeStream(): error removing property listener!"; + error( RtAudioError::WARNING ); + } + } + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], callbackHandler ); +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); +#else + // deprecated in favor of AudioDeviceDestroyIOProcID() + AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); +#endif + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + // Destroy pthread condition variable. + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiCore :: startStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiCore::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + OSStatus result = noErr; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + result = AudioDeviceStart( handle->id[0], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || + ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + + result = AudioDeviceStart( handle->id[1], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + handle->drainCounter = 0; + handle->internalDrain = false; + stream_.state = STREAM_RUNNING; + + unlock: + if ( result == noErr ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiCore :: stopStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + OSStatus result = noErr; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled + } + + result = AudioDeviceStop( handle->id[0], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { + + result = AudioDeviceStop( handle->id[1], callbackHandler ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + stream_.state = STREAM_STOPPED; + + unlock: + if ( result == noErr ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiCore :: abortStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is better to handle it this way because the +// callbackEvent() function probably should return before the AudioDeviceStop() +// function is called. +static void *coreStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiCore *object = (RtApiCore *) info->object; + + object->stopStream(); + pthread_exit( NULL ); +} + +bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, + const AudioBufferList *inBufferList, + const AudioBufferList *outBufferList ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + CoreHandle *handle = (CoreHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == true ) + pthread_create( &threadId, NULL, coreStopStream, info ); + else // external call to stopStream() + pthread_cond_signal( &handle->condition ); + return SUCCESS; + } + + AudioDeviceID outputDevice = handle->id[0]; + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream or duplex mode AND the input/output devices are + // different AND this function is called for the input device. + if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + abortStream(); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + if ( handle->nStreams[0] == 1 ) { + memset( outBufferList->mBuffers[handle->iStream[0]].mData, + 0, + outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); + } + else { // fill multiple streams with zeros + for ( unsigned int i=0; inStreams[0]; i++ ) { + memset( outBufferList->mBuffers[handle->iStream[0]+i].mData, + 0, + outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize ); + } + } + } + else if ( handle->nStreams[0] == 1 ) { + if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer + convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData, + stream_.userBuffer[0], stream_.convertInfo[0] ); + } + else { // copy from user buffer + memcpy( outBufferList->mBuffers[handle->iStream[0]].mData, + stream_.userBuffer[0], + outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); + } + } + else { // fill multiple streams + Float32 *inBuffer = (Float32 *) stream_.userBuffer[0]; + if ( stream_.doConvertBuffer[0] ) { + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + inBuffer = (Float32 *) stream_.deviceBuffer; + } + + if ( stream_.deviceInterleaved[0] == false ) { // mono mode + UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize; + for ( unsigned int i=0; imBuffers[handle->iStream[0]+i].mData, + (void *)&inBuffer[i*stream_.bufferSize], bufferBytes ); + } + } + else { // fill multiple multi-channel streams with interleaved data + UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset; + Float32 *out, *in; + + bool inInterleaved = ( stream_.userInterleaved ) ? true : false; + UInt32 inChannels = stream_.nUserChannels[0]; + if ( stream_.doConvertBuffer[0] ) { + inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode + inChannels = stream_.nDeviceChannels[0]; + } + + if ( inInterleaved ) inOffset = 1; + else inOffset = stream_.bufferSize; + + channelsLeft = inChannels; + for ( unsigned int i=0; inStreams[0]; i++ ) { + in = inBuffer; + out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData; + streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels; + + outJump = 0; + // Account for possible channel offset in first stream + if ( i == 0 && stream_.channelOffset[0] > 0 ) { + streamChannels -= stream_.channelOffset[0]; + outJump = stream_.channelOffset[0]; + out += outJump; + } + + // Account for possible unfilled channels at end of the last stream + if ( streamChannels > channelsLeft ) { + outJump = streamChannels - channelsLeft; + streamChannels = channelsLeft; + } + + // Determine input buffer offsets and skips + if ( inInterleaved ) { + inJump = inChannels; + in += inChannels - channelsLeft; + } + else { + inJump = 1; + in += (inChannels - channelsLeft) * inOffset; + } + + for ( unsigned int i=0; idrainCounter ) { + handle->drainCounter++; + goto unlock; + } + + AudioDeviceID inputDevice; + inputDevice = handle->id[1]; + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) { + + if ( handle->nStreams[1] == 1 ) { + if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer + convertBuffer( stream_.userBuffer[1], + (char *) inBufferList->mBuffers[handle->iStream[1]].mData, + stream_.convertInfo[1] ); + } + else { // copy to user buffer + memcpy( stream_.userBuffer[1], + inBufferList->mBuffers[handle->iStream[1]].mData, + inBufferList->mBuffers[handle->iStream[1]].mDataByteSize ); + } + } + else { // read from multiple streams + Float32 *outBuffer = (Float32 *) stream_.userBuffer[1]; + if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer; + + if ( stream_.deviceInterleaved[1] == false ) { // mono mode + UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize; + for ( unsigned int i=0; imBuffers[handle->iStream[1]+i].mData, bufferBytes ); + } + } + else { // read from multiple multi-channel streams + UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset; + Float32 *out, *in; + + bool outInterleaved = ( stream_.userInterleaved ) ? true : false; + UInt32 outChannels = stream_.nUserChannels[1]; + if ( stream_.doConvertBuffer[1] ) { + outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode + outChannels = stream_.nDeviceChannels[1]; + } + + if ( outInterleaved ) outOffset = 1; + else outOffset = stream_.bufferSize; + + channelsLeft = outChannels; + for ( unsigned int i=0; inStreams[1]; i++ ) { + out = outBuffer; + in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData; + streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels; + + inJump = 0; + // Account for possible channel offset in first stream + if ( i == 0 && stream_.channelOffset[1] > 0 ) { + streamChannels -= stream_.channelOffset[1]; + inJump = stream_.channelOffset[1]; + in += inJump; + } + + // Account for possible unread channels at end of the last stream + if ( streamChannels > channelsLeft ) { + inJump = streamChannels - channelsLeft; + streamChannels = channelsLeft; + } + + // Determine output buffer offsets and skips + if ( outInterleaved ) { + outJump = outChannels; + out += outChannels - channelsLeft; + } + else { + outJump = 1; + out += (outChannels - channelsLeft) * outOffset; + } + + for ( unsigned int i=0; iid[0] != handle->id[1] && deviceId == handle->id[0] ) ) + RtApi::tickStreamTime(); + + return SUCCESS; +} + +const char* RtApiCore :: getErrorCode( OSStatus code ) +{ + switch( code ) { + + case kAudioHardwareNotRunningError: + return "kAudioHardwareNotRunningError"; + + case kAudioHardwareUnspecifiedError: + return "kAudioHardwareUnspecifiedError"; + + case kAudioHardwareUnknownPropertyError: + return "kAudioHardwareUnknownPropertyError"; + + case kAudioHardwareBadPropertySizeError: + return "kAudioHardwareBadPropertySizeError"; + + case kAudioHardwareIllegalOperationError: + return "kAudioHardwareIllegalOperationError"; + + case kAudioHardwareBadObjectError: + return "kAudioHardwareBadObjectError"; + + case kAudioHardwareBadDeviceError: + return "kAudioHardwareBadDeviceError"; + + case kAudioHardwareBadStreamError: + return "kAudioHardwareBadStreamError"; + + case kAudioHardwareUnsupportedOperationError: + return "kAudioHardwareUnsupportedOperationError"; + + case kAudioDeviceUnsupportedFormatError: + return "kAudioDeviceUnsupportedFormatError"; + + case kAudioDevicePermissionsError: + return "kAudioDevicePermissionsError"; + + default: + return "CoreAudio unknown error"; + } +} + + //******************** End of __MACOSX_CORE__ *********************// +#endif + +#if defined(__UNIX_JACK__) + +// JACK is a low-latency audio server, originally written for the +// GNU/Linux operating system and now also ported to OS-X. It can +// connect a number of different applications to an audio device, as +// well as allowing them to share audio between themselves. +// +// When using JACK with RtAudio, "devices" refer to JACK clients that +// have ports connected to the server. The JACK server is typically +// started in a terminal as follows: +// +// .jackd -d alsa -d hw:0 +// +// or through an interface program such as qjackctl. Many of the +// parameters normally set for a stream are fixed by the JACK server +// and can be specified when the JACK server is started. In +// particular, +// +// .jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4 +// +// specifies a sample rate of 44100 Hz, a buffer size of 512 sample +// frames, and number of buffers = 4. Once the server is running, it +// is not possible to override these values. If the values are not +// specified in the command-line, the JACK server uses default values. +// +// The JACK server does not have to be running when an instance of +// RtApiJack is created, though the function getDeviceCount() will +// report 0 devices found until JACK has been started. When no +// devices are available (i.e., the JACK server is not running), a +// stream cannot be opened. + +#include +#include +#include + +// A structure to hold various information related to the Jack API +// implementation. +struct JackHandle { + jack_client_t *client; + jack_port_t **ports[2]; + std::string deviceName[2]; + bool xrun[2]; + pthread_cond_t condition; + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + + JackHandle() + :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +#if !defined(__RTAUDIO_DEBUG__) +static void jackSilentError( const char * ) {}; +#endif + +RtApiJack :: RtApiJack() + :shouldAutoconnect_(true) { + // Nothing to do here. +#if !defined(__RTAUDIO_DEBUG__) + // Turn off Jack's internal error reporting. + jack_set_error_function( &jackSilentError ); +#endif +} + +RtApiJack :: ~RtApiJack() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiJack :: getDeviceCount( void ) +{ + // See if we can become a jack client. + jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; + jack_status_t *status = NULL; + jack_client_t *client = jack_client_open( "RtApiJackCount", options, status ); + if ( client == 0 ) return 0; + + const char **ports; + std::string port, previousPort; + unsigned int nChannels = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nChannels ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon + 1 ); + if ( port != previousPort ) { + nDevices++; + previousPort = port; + } + } + } while ( ports[++nChannels] ); + free( ports ); + } + + jack_client_close( client ); + return nDevices; +} + +RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption + jack_status_t *status = NULL; + jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); + if ( client == 0 ) { + errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; + error( RtAudioError::WARNING ); + return info; + } + + const char **ports; + std::string port, previousPort; + unsigned int nPorts = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nPorts ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon ); + if ( port != previousPort ) { + if ( nDevices == device ) info.name = port; + nDevices++; + previousPort = port; + } + } + } while ( ports[++nPorts] ); + free( ports ); + } + + if ( device >= nDevices ) { + jack_client_close( client ); + errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + // Get the current jack server sample rate. + info.sampleRates.clear(); + + info.preferredSampleRate = jack_get_sample_rate( client ); + info.sampleRates.push_back( info.preferredSampleRate ); + + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + info.outputChannels = nChannels; + } + + // Jack "output ports" equal RtAudio input channels. + nChannels = 0; + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + info.inputChannels = nChannels; + } + + if ( info.outputChannels == 0 && info.inputChannels == 0 ) { + jack_client_close(client); + errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; + error( RtAudioError::WARNING ); + return info; + } + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Jack always uses 32-bit floats. + info.nativeFormats = RTAUDIO_FLOAT32; + + // Jack doesn't provide default devices so we'll use the first available one. + if ( device == 0 && info.outputChannels > 0 ) + info.isDefaultOutput = true; + if ( device == 0 && info.inputChannels > 0 ) + info.isDefaultInput = true; + + jack_client_close(client); + info.probed = true; + return info; +} + +static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + + RtApiJack *object = (RtApiJack *) info->object; + if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1; + + return 0; +} + +// This function will be called by a spawned thread when the Jack +// server signals that it is shutting down. It is necessary to handle +// it this way because the jackShutdown() function must return before +// the jack_deactivate() function (in closeStream()) will return. +static void *jackCloseStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiJack *object = (RtApiJack *) info->object; + + object->closeStream(); + + pthread_exit( NULL ); +} +static void jackShutdown( void *infoPointer ) +{ + CallbackInfo *info = (CallbackInfo *) infoPointer; + RtApiJack *object = (RtApiJack *) info->object; + + // Check current stream state. If stopped, then we'll assume this + // was called as a result of a call to RtApiJack::stopStream (the + // deactivation of a client handle causes this function to be called). + // If not, we'll assume the Jack server is shutting down or some + // other problem occurred and we should close the stream. + if ( object->isStreamRunning() == false ) return; + + ThreadHandle threadId; + pthread_create( &threadId, NULL, jackCloseStream, info ); + std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; +} + +static int jackXrun( void *infoPointer ) +{ + JackHandle *handle = *((JackHandle **) infoPointer); + + if ( handle->ports[0] ) handle->xrun[0] = true; + if ( handle->ports[1] ) handle->xrun[1] = true; + + return 0; +} + +bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + JackHandle *handle = (JackHandle *) stream_.apiHandle; + + // Look for jack server and try to become a client (only do once per stream). + jack_client_t *client = 0; + if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) { + jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption; + jack_status_t *status = NULL; + if ( options && !options->streamName.empty() ) + client = jack_client_open( options->streamName.c_str(), jackoptions, status ); + else + client = jack_client_open( "RtApiJack", jackoptions, status ); + if ( client == 0 ) { + errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + } + else { + // The handle must have been created on an earlier pass. + client = handle->client; + } + + const char **ports; + std::string port, previousPort, deviceName; + unsigned int nPorts = 0, nDevices = 0; + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); + if ( ports ) { + // Parse the port names up to the first colon (:). + size_t iColon = 0; + do { + port = (char *) ports[ nPorts ]; + iColon = port.find(":"); + if ( iColon != std::string::npos ) { + port = port.substr( 0, iColon ); + if ( port != previousPort ) { + if ( nDevices == device ) deviceName = port; + nDevices++; + previousPort = port; + } + } + } while ( ports[++nPorts] ); + free( ports ); + } + + if ( device >= nDevices ) { + errorText_ = "RtApiJack::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + unsigned long flag = JackPortIsInput; + if ( mode == INPUT ) flag = JackPortIsOutput; + + if ( ! (options && (options->flags & RTAUDIO_JACK_DONT_CONNECT)) ) { + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + } + // Compare the jack ports for specified client to the requested number of channels. + if ( nChannels < (channels + firstChannel) ) { + errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Check the jack server sample rate. + unsigned int jackRate = jack_get_sample_rate( client ); + if ( sampleRate != jackRate ) { + jack_client_close( client ); + errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.sampleRate = jackRate; + + // Get the latency of the JACK port. + ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); + if ( ports[ firstChannel ] ) { + // Added by Ge Wang + jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); + // the range (usually the min and max are equal) + jack_latency_range_t latrange; latrange.min = latrange.max = 0; + // get the latency range + jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); + // be optimistic, use the min! + stream_.latency[mode] = latrange.min; + //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); + } + free( ports ); + + // The jack server always uses 32-bit floating-point data. + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + stream_.userFormat = format; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // Jack always uses non-interleaved buffers. + stream_.deviceInterleaved[mode] = false; + + // Jack always provides host byte-ordered data. + stream_.doByteSwap[mode] = false; + + // Get the buffer size. The buffer size and number of buffers + // (periods) is set when the jack server is started. + stream_.bufferSize = (int) jack_get_buffer_size( client ); + *bufferSize = stream_.bufferSize; + + stream_.nDeviceChannels[mode] = channels; + stream_.nUserChannels[mode] = channels; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate our JackHandle structure for the stream. + if ( handle == 0 ) { + try { + handle = new JackHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory."; + goto error; + } + + if ( pthread_cond_init(&handle->condition, NULL) ) { + errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + stream_.apiHandle = (void *) handle; + handle->client = client; + } + handle->deviceName[mode] = deviceName; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + if ( mode == OUTPUT ) + bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + else { // mode == INPUT + bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); + if ( bufferBytes < bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Allocate memory for the Jack ports (channels) identifiers. + handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); + if ( handle->ports[mode] == NULL ) { + errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; + goto error; + } + + stream_.device[mode] = device; + stream_.channelOffset[mode] = firstChannel; + stream_.state = STREAM_STOPPED; + stream_.callbackInfo.object = (void *) this; + + if ( stream_.mode == OUTPUT && mode == INPUT ) + // We had already set up the stream for output. + stream_.mode = DUPLEX; + else { + stream_.mode = mode; + jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); + jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); + jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); + } + + // Register our ports. + char label[64]; + if ( mode == OUTPUT ) { + for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); + } + } + else { + for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); + } + } + + // Setup the buffer conversion information structure. We don't use + // buffers to do channel offsets, so we override that parameter + // here. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); + + if ( options && options->flags & RTAUDIO_JACK_DONT_CONNECT ) shouldAutoconnect_ = false; + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->condition ); + jack_client_close( handle->client ); + + if ( handle->ports[0] ) free( handle->ports[0] ); + if ( handle->ports[1] ) free( handle->ports[1] ); + + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +void RtApiJack :: closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiJack::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + if ( handle ) { + + if ( stream_.state == STREAM_RUNNING ) + jack_deactivate( handle->client ); + + jack_client_close( handle->client ); + } + + if ( handle ) { + if ( handle->ports[0] ) free( handle->ports[0] ); + if ( handle->ports[1] ) free( handle->ports[1] ); + pthread_cond_destroy( &handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiJack :: startStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiJack::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + int result = jack_activate( handle->client ); + if ( result ) { + errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; + goto unlock; + } + + const char **ports; + + // Get the list of available ports. + if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { + result = 1; + ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); + if ( ports == NULL) { + errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; + goto unlock; + } + + // Now make the port connections. Since RtAudio wasn't designed to + // allow the user to select particular channels of a device, we'll + // just open the first "nChannels" ports with offset. + for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); + if ( result ) { + free( ports ); + errorText_ = "RtApiJack::startStream(): error connecting output ports!"; + goto unlock; + } + } + free(ports); + } + + if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { + result = 1; + ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); + if ( ports == NULL) { + errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; + goto unlock; + } + + // Now make the port connections. See note above. + for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); + if ( result ) { + free( ports ); + errorText_ = "RtApiJack::startStream(): error connecting input ports!"; + goto unlock; + } + } + free(ports); + } + + handle->drainCounter = 0; + handle->internalDrain = false; + stream_.state = STREAM_RUNNING; + + unlock: + if ( result == 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiJack :: stopStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled + } + } + + jack_deactivate( handle->client ); + stream_.state = STREAM_STOPPED; +} + +void RtApiJack :: abortStream( void ) +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + JackHandle *handle = (JackHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is necessary to handle it this way because the +// callbackEvent() function must return before the jack_deactivate() +// function will return. +static void *jackStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiJack *object = (RtApiJack *) info->object; + + object->stopStream(); + pthread_exit( NULL ); +} + +bool RtApiJack :: callbackEvent( unsigned long nframes ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + if ( stream_.bufferSize != nframes ) { + errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + JackHandle *handle = (JackHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == true ) + pthread_create( &threadId, NULL, jackStopStream, info ); + else + pthread_cond_signal( &handle->condition ); + return SUCCESS; + } + + // Invoke user callback first, to get fresh output data. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + ThreadHandle id; + pthread_create( &id, NULL, jackStopStream, info ); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + jack_default_audio_sample_t *jackbuffer; + unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t ); + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memset( jackbuffer, 0, bufferBytes ); + } + + } + else if ( stream_.doConvertBuffer[0] ) { + + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); + } + } + else { // no buffer conversion + for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); + memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); + } + } + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + if ( stream_.doConvertBuffer[1] ) { + for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); + } + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + } + else { // no buffer conversion + for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); + memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); + } + } + } + + unlock: + RtApi::tickStreamTime(); + return SUCCESS; +} + //******************** End of __UNIX_JACK__ *********************// +#endif + +#if defined(__WINDOWS_ASIO__) // ASIO API on Windows + +// The ASIO API is designed around a callback scheme, so this +// implementation is similar to that used for OS-X CoreAudio and Linux +// Jack. The primary constraint with ASIO is that it only allows +// access to a single driver at a time. Thus, it is not possible to +// have more than one simultaneous RtAudio stream. +// +// This implementation also requires a number of external ASIO files +// and a few global variables. The ASIO callback scheme does not +// allow for the passing of user data, so we must create a global +// pointer to our callbackInfo structure. +// +// On unix systems, we make use of a pthread condition variable. +// Since there is no equivalent in Windows, I hacked something based +// on information found in +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. + +#include "asiosys.h" +#include "asio.h" +#include "iasiothiscallresolver.h" +#include "asiodrivers.h" +#include + +static AsioDrivers drivers; +static ASIOCallbacks asioCallbacks; +static ASIODriverInfo driverInfo; +static CallbackInfo *asioCallbackInfo; +static bool asioXRun; + +struct AsioHandle { + int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + ASIOBufferInfo *bufferInfos; + HANDLE condition; + + AsioHandle() + :drainCounter(0), internalDrain(false), bufferInfos(0) {} +}; + +// Function declarations (definitions at end of section) +static const char* getAsioErrorString( ASIOError result ); +static void sampleRateChanged( ASIOSampleRate sRate ); +static long asioMessages( long selector, long value, void* message, double* opt ); + +RtApiAsio :: RtApiAsio() +{ + // ASIO cannot run on a multi-threaded appartment. You can call + // CoInitialize beforehand, but it must be for appartment threading + // (in which case, CoInitilialize will return S_FALSE here). + coInitialized_ = false; + HRESULT hr = CoInitialize( NULL ); + if ( FAILED(hr) ) { + errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; + error( RtAudioError::WARNING ); + } + coInitialized_ = true; + + drivers.removeCurrentDriver(); + driverInfo.asioVersion = 2; + + // See note in DirectSound implementation about GetDesktopWindow(). + driverInfo.sysRef = GetForegroundWindow(); +} + +RtApiAsio :: ~RtApiAsio() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); + if ( coInitialized_ ) CoUninitialize(); +} + +unsigned int RtApiAsio :: getDeviceCount( void ) +{ + return (unsigned int) drivers.asioGetNumDev(); +} + +RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + // Get device ID + unsigned int nDevices = getDeviceCount(); + if ( nDevices == 0 ) { + errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + // If a stream is already open, we cannot probe other devices. Thus, use the saved results. + if ( stream_.state != STREAM_CLOSED ) { + if ( device >= devices_.size() ) { + errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; + error( RtAudioError::WARNING ); + return info; + } + return devices_[ device ]; + } + + char driverName[32]; + ASIOError result = drivers.asioGetDriverName( (int) device, driverName, 32 ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.name = driverName; + + if ( !drivers.loadDriver( driverName ) ) { + errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + result = ASIOInit( &driverInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Determine the device channel information. + long inputChannels, outputChannels; + result = ASIOGetChannels( &inputChannels, &outputChannels ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.outputChannels = outputChannels; + info.inputChannels = inputChannels; + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // Determine the supported sample rates. + info.sampleRates.clear(); + for ( unsigned int i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[i]; + } + } + + // Determine supported data types ... just check first channel and assume rest are the same. + ASIOChannelInfo channelInfo; + channelInfo.channel = 0; + channelInfo.isInput = true; + if ( info.inputChannels <= 0 ) channelInfo.isInput = false; + result = ASIOGetChannelInfo( &channelInfo ); + if ( result != ASE_OK ) { + drivers.removeCurrentDriver(); + errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + info.nativeFormats = 0; + if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) + info.nativeFormats |= RTAUDIO_SINT16; + else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) + info.nativeFormats |= RTAUDIO_SINT32; + else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) + info.nativeFormats |= RTAUDIO_FLOAT32; + else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) + info.nativeFormats |= RTAUDIO_FLOAT64; + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) + info.nativeFormats |= RTAUDIO_SINT24; + + if ( info.outputChannels > 0 ) + if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; + if ( info.inputChannels > 0 ) + if ( getDefaultInputDevice() == device ) info.isDefaultInput = true; + + info.probed = true; + drivers.removeCurrentDriver(); + return info; +} + +static void bufferSwitch( long index, ASIOBool /*processNow*/ ) +{ + RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; + object->callbackEvent( index ); +} + +void RtApiAsio :: saveDeviceInfo( void ) +{ + devices_.clear(); + + unsigned int nDevices = getDeviceCount(); + devices_.resize( nDevices ); + for ( unsigned int i=0; isaveDeviceInfo(); + + if ( !drivers.loadDriver( driverName ) ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + result = ASIOInit( &driverInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // keep them before any "goto error", they are used for error cleanup + goto device boundary checks + bool buffersAllocated = false; + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + unsigned int nChannels; + + + // Check the device channel count. + long inputChannels, outputChannels; + result = ASIOGetChannels( &inputChannels, &outputChannels ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; + errorText_ = errorStream_.str(); + goto error; + } + + if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) || + ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ")."; + errorText_ = errorStream_.str(); + goto error; + } + stream_.nDeviceChannels[mode] = channels; + stream_.nUserChannels[mode] = channels; + stream_.channelOffset[mode] = firstChannel; + + // Verify the sample rate is supported. + result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + goto error; + } + + // Get the current sample rate + ASIOSampleRate currentRate; + result = ASIOGetSampleRate( ¤tRate ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate."; + errorText_ = errorStream_.str(); + goto error; + } + + // Set the sample rate only if necessary + if ( currentRate != sampleRate ) { + result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + goto error; + } + } + + // Determine the driver data type. + ASIOChannelInfo channelInfo; + channelInfo.channel = 0; + if ( mode == OUTPUT ) channelInfo.isInput = false; + else channelInfo.isInput = true; + result = ASIOGetChannelInfo( &channelInfo ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format."; + errorText_ = errorStream_.str(); + goto error; + } + + // Assuming WINDOWS host is always little-endian. + stream_.doByteSwap[mode] = false; + stream_.userFormat = format; + stream_.deviceFormat[mode] = 0; + if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; + if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; + } + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; + } + + if ( stream_.deviceFormat[mode] == 0 ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + goto error; + } + + // Set the buffer size. For a duplex stream, this will end up + // setting the buffer size based on the input constraints, which + // should be ok. + long minSize, maxSize, preferSize, granularity; + result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size."; + errorText_ = errorStream_.str(); + goto error; + } + + if ( isDuplexInput ) { + // When this is the duplex input (output was opened before), then we have to use the same + // buffersize as the output, because it might use the preferred buffer size, which most + // likely wasn't passed as input to this. The buffer sizes have to be identically anyway, + // So instead of throwing an error, make them equal. The caller uses the reference + // to the "bufferSize" param as usual to set up processing buffers. + + *bufferSize = stream_.bufferSize; + + } else { + if ( *bufferSize == 0 ) *bufferSize = preferSize; + else if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; + else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; + else if ( granularity == -1 ) { + // Make sure bufferSize is a power of two. + int log2_of_min_size = 0; + int log2_of_max_size = 0; + + for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) { + if ( minSize & ((long)1 << i) ) log2_of_min_size = i; + if ( maxSize & ((long)1 << i) ) log2_of_max_size = i; + } + + long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) ); + int min_delta_num = log2_of_min_size; + + for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) { + long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) ); + if (current_delta < min_delta) { + min_delta = current_delta; + min_delta_num = i; + } + } + + *bufferSize = ( (unsigned int)1 << min_delta_num ); + if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; + else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; + } + else if ( granularity != 0 ) { + // Set to an even multiple of granularity, rounding up. + *bufferSize = (*bufferSize + granularity-1) / granularity * granularity; + } + } + + /* + // we don't use it anymore, see above! + // Just left it here for the case... + if ( isDuplexInput && stream_.bufferSize != *bufferSize ) { + errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!"; + goto error; + } + */ + + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 2; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // ASIO always uses non-interleaved buffers. + stream_.deviceInterleaved[mode] = false; + + // Allocate, if necessary, our AsioHandle structure for the stream. + if ( handle == 0 ) { + try { + handle = new AsioHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory."; + goto error; + } + handle->bufferInfos = 0; + + // Create a manual-reset event. + handle->condition = CreateEvent( NULL, // no security + TRUE, // manual-reset + FALSE, // non-signaled initially + NULL ); // unnamed + stream_.apiHandle = (void *) handle; + } + + // Create the ASIO internal buffers. Since RtAudio sets up input + // and output separately, we'll have to dispose of previously + // created output buffers for a duplex stream. + if ( mode == INPUT && stream_.mode == OUTPUT ) { + ASIODisposeBuffers(); + if ( handle->bufferInfos ) free( handle->bufferInfos ); + } + + // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure. + unsigned int i; + nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; + handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) ); + if ( handle->bufferInfos == NULL ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ")."; + errorText_ = errorStream_.str(); + goto error; + } + + ASIOBufferInfo *infos; + infos = handle->bufferInfos; + for ( i=0; iisInput = ASIOFalse; + infos->channelNum = i + stream_.channelOffset[0]; + infos->buffers[0] = infos->buffers[1] = 0; + } + for ( i=0; iisInput = ASIOTrue; + infos->channelNum = i + stream_.channelOffset[1]; + infos->buffers[0] = infos->buffers[1] = 0; + } + + // prepare for callbacks + stream_.sampleRate = sampleRate; + stream_.device[mode] = device; + stream_.mode = isDuplexInput ? DUPLEX : mode; + + // store this class instance before registering callbacks, that are going to use it + asioCallbackInfo = &stream_.callbackInfo; + stream_.callbackInfo.object = (void *) this; + + // Set up the ASIO callback structure and create the ASIO data buffers. + asioCallbacks.bufferSwitch = &bufferSwitch; + asioCallbacks.sampleRateDidChange = &sampleRateChanged; + asioCallbacks.asioMessage = &asioMessages; + asioCallbacks.bufferSwitchTimeInfo = NULL; + result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); + if ( result != ASE_OK ) { + // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges + // but only accept the preferred buffer size as parameter for ASIOCreateBuffers (e.g. Creative's ASIO driver). + // In that case, let's be naïve and try that instead. + *bufferSize = preferSize; + stream_.bufferSize = *bufferSize; + result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); + } + + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers."; + errorText_ = errorStream_.str(); + goto error; + } + buffersAllocated = true; + stream_.state = STREAM_STOPPED; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( isDuplexInput && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Determine device latencies + long inputLatency, outputLatency; + result = ASIOGetLatencies( &inputLatency, &outputLatency ); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING); // warn but don't fail + } + else { + stream_.latency[0] = outputLatency; + stream_.latency[1] = inputLatency; + } + + // Setup the buffer conversion information structure. We don't use + // buffers to do channel offsets, so we override that parameter + // here. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); + + return SUCCESS; + + error: + if ( !isDuplexInput ) { + // the cleanup for error in the duplex input, is done by RtApi::openStream + // So we clean up for single channel only + + if ( buffersAllocated ) + ASIODisposeBuffers(); + + drivers.removeCurrentDriver(); + + if ( handle ) { + CloseHandle( handle->condition ); + if ( handle->bufferInfos ) + free( handle->bufferInfos ); + + delete handle; + stream_.apiHandle = 0; + } + + + if ( stream_.userBuffer[mode] ) { + free( stream_.userBuffer[mode] ); + stream_.userBuffer[mode] = 0; + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + } + + return FAILURE; +}//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void RtApiAsio :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + if ( stream_.state == STREAM_RUNNING ) { + stream_.state = STREAM_STOPPED; + ASIOStop(); + } + ASIODisposeBuffers(); + drivers.removeCurrentDriver(); + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + if ( handle ) { + CloseHandle( handle->condition ); + if ( handle->bufferInfos ) + free( handle->bufferInfos ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +bool stopThreadCalled = false; + +void RtApiAsio :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiAsio::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + ASIOError result = ASIOStart(); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device."; + errorText_ = errorStream_.str(); + goto unlock; + } + + handle->drainCounter = 0; + handle->internalDrain = false; + ResetEvent( handle->condition ); + stream_.state = STREAM_RUNNING; + asioXRun = false; + + unlock: + stopThreadCalled = false; + + if ( result == ASE_OK ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAsio :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + WaitForSingleObject( handle->condition, INFINITE ); // block until signaled + } + } + + stream_.state = STREAM_STOPPED; + + ASIOError result = ASIOStop(); + if ( result != ASE_OK ) { + errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device."; + errorText_ = errorStream_.str(); + } + + if ( result == ASE_OK ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAsio :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + // The following lines were commented-out because some behavior was + // noted where the device buffers need to be zeroed to avoid + // continuing sound, even when the device buffers are completely + // disposed. So now, calling abort is the same as calling stop. + // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + // handle->drainCounter = 2; + stopStream(); +} + +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is necessary to handle it this way because the +// callbackEvent() function must return before the ASIOStop() +// function will return. +static unsigned __stdcall asioStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiAsio *object = (RtApiAsio *) info->object; + + object->stopStream(); + _endthreadex( 0 ); + return 0; +} + +bool RtApiAsio :: callbackEvent( long bufferIndex ) +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return FAILURE; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal if finished. + if ( handle->drainCounter > 3 ) { + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == false ) + SetEvent( handle->condition ); + else { // spawn a thread to stop the stream + unsigned threadId; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, + &stream_.callbackInfo, 0, &threadId ); + } + return SUCCESS; + } + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && asioXRun == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + asioXRun = false; + } + if ( stream_.mode != OUTPUT && asioXRun == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + asioXRun = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + unsigned threadId; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, + &stream_.callbackInfo, 0, &threadId ); + return SUCCESS; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + unsigned int nChannels, bufferBytes, i, j; + nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] ); + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes ); + } + + } + else if ( stream_.doConvertBuffer[0] ) { + + convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( stream_.deviceBuffer, + stream_.bufferSize * stream_.nDeviceChannels[0], + stream_.deviceFormat[0] ); + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memcpy( handle->bufferInfos[i].buffers[bufferIndex], + &stream_.deviceBuffer[j++*bufferBytes], bufferBytes ); + } + + } + else { + + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( stream_.userBuffer[0], + stream_.bufferSize * stream_.nUserChannels[0], + stream_.userFormat ); + + for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) + memcpy( handle->bufferInfos[i].buffers[bufferIndex], + &stream_.userBuffer[0][bufferBytes*j++], bufferBytes ); + } + + } + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]); + + if (stream_.doConvertBuffer[1]) { + + // Always interleave ASIO input data. + for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) + memcpy( &stream_.deviceBuffer[j++*bufferBytes], + handle->bufferInfos[i].buffers[bufferIndex], + bufferBytes ); + } + + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( stream_.deviceBuffer, + stream_.bufferSize * stream_.nDeviceChannels[1], + stream_.deviceFormat[1] ); + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + + } + else { + for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) { + memcpy( &stream_.userBuffer[1][bufferBytes*j++], + handle->bufferInfos[i].buffers[bufferIndex], + bufferBytes ); + } + } + + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( stream_.userBuffer[1], + stream_.bufferSize * stream_.nUserChannels[1], + stream_.userFormat ); + } + } + + unlock: + // The following call was suggested by Malte Clasen. While the API + // documentation indicates it should not be required, some device + // drivers apparently do not function correctly without it. + ASIOOutputReady(); + + RtApi::tickStreamTime(); + return SUCCESS; +} + +static void sampleRateChanged( ASIOSampleRate sRate ) +{ + // The ASIO documentation says that this usually only happens during + // external sync. Audio processing is not stopped by the driver, + // actual sample rate might not have even changed, maybe only the + // sample rate status of an AES/EBU or S/PDIF digital input at the + // audio device. + + RtApi *object = (RtApi *) asioCallbackInfo->object; + try { + object->stopStream(); + } + catch ( RtAudioError &exception ) { + std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; + return; + } + + std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; +} + +static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) +{ + long ret = 0; + + switch( selector ) { + case kAsioSelectorSupported: + if ( value == kAsioResetRequest + || value == kAsioEngineVersion + || value == kAsioResyncRequest + || value == kAsioLatenciesChanged + // The following three were added for ASIO 2.0, you don't + // necessarily have to support them. + || value == kAsioSupportsTimeInfo + || value == kAsioSupportsTimeCode + || value == kAsioSupportsInputMonitor) + ret = 1L; + break; + case kAsioResetRequest: + // Defer the task and perform the reset of the driver during the + // next "safe" situation. You cannot reset the driver right now, + // as this code is called from the driver. Reset the driver is + // done by completely destruct is. I.e. ASIOStop(), + // ASIODisposeBuffers(), Destruction Afterwards you initialize the + // driver again. + std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; + ret = 1L; + break; + case kAsioResyncRequest: + // This informs the application that the driver encountered some + // non-fatal data loss. It is used for synchronization purposes + // of different media. Added mainly to work around the Win16Mutex + // problems in Windows 95/98 with the Windows Multimedia system, + // which could lose data because the Mutex was held too long by + // another thread. However a driver can issue it in other + // situations, too. + // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl; + asioXRun = true; + ret = 1L; + break; + case kAsioLatenciesChanged: + // This will inform the host application that the drivers were + // latencies changed. Beware, it this does not mean that the + // buffer sizes have changed! You might need to update internal + // delay data. + std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl; + ret = 1L; + break; + case kAsioEngineVersion: + // Return the supported ASIO version of the host application. If + // a host application does not implement this selector, ASIO 1.0 + // is assumed by the driver. + ret = 2L; + break; + case kAsioSupportsTimeInfo: + // Informs the driver whether the + // asioCallbacks.bufferSwitchTimeInfo() callback is supported. + // For compatibility with ASIO 1.0 drivers the host application + // should always support the "old" bufferSwitch method, too. + ret = 0; + break; + case kAsioSupportsTimeCode: + // Informs the driver whether application is interested in time + // code info. If an application does not need to know about time + // code, the driver has less work to do. + ret = 0; + break; + } + return ret; +} + +static const char* getAsioErrorString( ASIOError result ) +{ + struct Messages + { + ASIOError value; + const char*message; + }; + + static const Messages m[] = + { + { ASE_NotPresent, "Hardware input or output is not present or available." }, + { ASE_HWMalfunction, "Hardware is malfunctioning." }, + { ASE_InvalidParameter, "Invalid input parameter." }, + { ASE_InvalidMode, "Invalid mode." }, + { ASE_SPNotAdvancing, "Sample position not advancing." }, + { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." }, + { ASE_NoMemory, "Not enough memory to complete the request." } + }; + + for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i ) + if ( m[i].value == result ) return m[i].message; + + return "Unknown error."; +} + +//******************** End of __WINDOWS_ASIO__ *********************// +#endif + + +#if defined(__WINDOWS_WASAPI__) // Windows WASAPI API + +// Authored by Marcus Tomlinson , April 2014 +// - Introduces support for the Windows WASAPI API +// - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required +// - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface +// - Includes automatic internal conversion of sample rate and buffer size between hardware and the user + +#ifndef INITGUID + #define INITGUID +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifndef MF_E_TRANSFORM_NEED_MORE_INPUT + #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) +#endif + +#ifndef MFSTARTUP_NOSOCKET + #define MFSTARTUP_NOSOCKET 0x1 +#endif + +#ifdef _MSC_VER + #pragma comment( lib, "ksuser" ) + #pragma comment( lib, "mfplat.lib" ) + #pragma comment( lib, "mfuuid.lib" ) + #pragma comment( lib, "wmcodecdspuuid" ) +#endif + +//============================================================================= + +#define SAFE_RELEASE( objectPtr )\ +if ( objectPtr )\ +{\ + objectPtr->Release();\ + objectPtr = NULL;\ +} + +typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); + +//----------------------------------------------------------------------------- + +// WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. +// Therefore we must perform all necessary conversions to user buffers in order to satisfy these +// requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to +// provide intermediate storage for read / write synchronization. +class WasapiBuffer +{ +public: + WasapiBuffer() + : buffer_( NULL ), + bufferSize_( 0 ), + inIndex_( 0 ), + outIndex_( 0 ) {} + + ~WasapiBuffer() { + free( buffer_ ); + } + + // sets the length of the internal ring buffer + void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { + free( buffer_ ); + + buffer_ = ( char* ) calloc( bufferSize, formatBytes ); + + bufferSize_ = bufferSize; + inIndex_ = 0; + outIndex_ = 0; + } + + // attempt to push a buffer into the ring buffer at the current "in" index + bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relOutIndex = outIndex_; + unsigned int inIndexEnd = inIndex_ + bufferSize; + if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { + relOutIndex += bufferSize_; + } + + // the "IN" index CAN BEGIN at the "OUT" index + // the "IN" index CANNOT END at the "OUT" index + if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) { + return false; // not enough space between "in" index and "out" index + } + + // copy buffer from external to internal + int fromZeroSize = inIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromInSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); + memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); + memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); + memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); + memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); + memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); + memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); + break; + } + + // update "in" index + inIndex_ += bufferSize; + inIndex_ %= bufferSize_; + + return true; + } + + // attempt to pull a buffer from the ring buffer from the current "out" index + bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) + { + if ( !buffer || // incoming buffer is NULL + bufferSize == 0 || // incoming buffer has no data + bufferSize > bufferSize_ ) // incoming buffer too large + { + return false; + } + + unsigned int relInIndex = inIndex_; + unsigned int outIndexEnd = outIndex_ + bufferSize; + if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { + relInIndex += bufferSize_; + } + + // the "OUT" index CANNOT BEGIN at the "IN" index + // the "OUT" index CAN END at the "IN" index + if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) { + return false; // not enough space between "out" index and "in" index + } + + // copy buffer from internal to external + int fromZeroSize = outIndex_ + bufferSize - bufferSize_; + fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; + int fromOutSize = bufferSize - fromZeroSize; + + switch( format ) + { + case RTAUDIO_SINT8: + memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); + memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); + break; + case RTAUDIO_SINT16: + memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); + memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); + break; + case RTAUDIO_SINT24: + memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); + memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); + break; + case RTAUDIO_SINT32: + memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); + memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); + break; + case RTAUDIO_FLOAT32: + memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); + memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); + break; + case RTAUDIO_FLOAT64: + memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); + memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); + break; + } + + // update "out" index + outIndex_ += bufferSize; + outIndex_ %= bufferSize_; + + return true; + } + +private: + char* buffer_; + unsigned int bufferSize_; + unsigned int inIndex_; + unsigned int outIndex_; +}; + +//----------------------------------------------------------------------------- + +// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate +// between HW and the user. The WasapiResampler class is used to perform this conversion between +// HwIn->UserIn and UserOut->HwOut during the stream callback loop. +class WasapiResampler +{ +public: + WasapiResampler( bool isFloat, unsigned int bitsPerSample, unsigned int channelCount, + unsigned int inSampleRate, unsigned int outSampleRate ) + : _bytesPerSample( bitsPerSample / 8 ) + , _channelCount( channelCount ) + , _sampleRatio( ( float ) outSampleRate / inSampleRate ) + , _transformUnk( NULL ) + , _transform( NULL ) + , _mediaType( NULL ) + , _inputMediaType( NULL ) + , _outputMediaType( NULL ) + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + , _resamplerProps( NULL ) + #endif + { + // 1. Initialization + + MFStartup( MF_VERSION, MFSTARTUP_NOSOCKET ); + + // 2. Create Resampler Transform Object + + CoCreateInstance( CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, + IID_IUnknown, ( void** ) &_transformUnk ); + + _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); + _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality + #endif + + // 3. Specify input / output format + + MFCreateMediaType( &_mediaType ); + _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); + _mediaType->SetGUID( MF_MT_SUBTYPE, isFloat ? MFAudioFormat_Float : MFAudioFormat_PCM ); + _mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, _bytesPerSample * channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); + _mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE ); + + MFCreateMediaType( &_inputMediaType ); + _mediaType->CopyAllItems( _inputMediaType ); + + _transform->SetInputType( 0, _inputMediaType, 0 ); + + MFCreateMediaType( &_outputMediaType ); + _mediaType->CopyAllItems( _outputMediaType ); + + _outputMediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate ); + _outputMediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * outSampleRate ); + + _transform->SetOutputType( 0, _outputMediaType, 0 ); + + // 4. Send stream start messages to Resampler + + _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); + } + + ~WasapiResampler() + { + // 8. Send stream stop messages to Resampler + + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); + + // 9. Cleanup + + MFShutdown(); + + SAFE_RELEASE( _transformUnk ); + SAFE_RELEASE( _transform ); + SAFE_RELEASE( _mediaType ); + SAFE_RELEASE( _inputMediaType ); + SAFE_RELEASE( _outputMediaType ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + SAFE_RELEASE( _resamplerProps ); + #endif + } + + void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount ) + { + unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; + if ( _sampleRatio == 1 ) + { + // no sample rate conversion required + memcpy( outBuffer, inBuffer, inputBufferSize ); + outSampleCount = inSampleCount; + return; + } + + unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + + IMFMediaBuffer* rInBuffer; + IMFSample* rInSample; + BYTE* rInByteBuffer = NULL; + + // 5. Create Sample object from input data + + MFCreateMemoryBuffer( inputBufferSize, &rInBuffer ); + + rInBuffer->Lock( &rInByteBuffer, NULL, NULL ); + memcpy( rInByteBuffer, inBuffer, inputBufferSize ); + rInBuffer->Unlock(); + rInByteBuffer = NULL; + + rInBuffer->SetCurrentLength( inputBufferSize ); + + MFCreateSample( &rInSample ); + rInSample->AddBuffer( rInBuffer ); + + // 6. Pass input data to Resampler + + _transform->ProcessInput( 0, rInSample, 0 ); + + SAFE_RELEASE( rInBuffer ); + SAFE_RELEASE( rInSample ); + + // 7. Perform sample rate conversion + + IMFMediaBuffer* rOutBuffer = NULL; + BYTE* rOutByteBuffer = NULL; + + MFT_OUTPUT_DATA_BUFFER rOutDataBuffer; + DWORD rStatus; + DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput + + // 7.1 Create Sample object for output data + + memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); + MFCreateSample( &( rOutDataBuffer.pSample ) ); + MFCreateMemoryBuffer( rBytes, &rOutBuffer ); + rOutDataBuffer.pSample->AddBuffer( rOutBuffer ); + rOutDataBuffer.dwStreamID = 0; + rOutDataBuffer.dwStatus = 0; + rOutDataBuffer.pEvents = NULL; + + // 7.2 Get output data from Resampler + + if ( _transform->ProcessOutput( 0, 1, &rOutDataBuffer, &rStatus ) == MF_E_TRANSFORM_NEED_MORE_INPUT ) + { + outSampleCount = 0; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + return; + } + + // 7.3 Write output data to outBuffer + + SAFE_RELEASE( rOutBuffer ); + rOutDataBuffer.pSample->ConvertToContiguousBuffer( &rOutBuffer ); + rOutBuffer->GetCurrentLength( &rBytes ); + + rOutBuffer->Lock( &rOutByteBuffer, NULL, NULL ); + memcpy( outBuffer, rOutByteBuffer, rBytes ); + rOutBuffer->Unlock(); + rOutByteBuffer = NULL; + + outSampleCount = rBytes / _bytesPerSample / _channelCount; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + } + +private: + unsigned int _bytesPerSample; + unsigned int _channelCount; + float _sampleRatio; + + IUnknown* _transformUnk; + IMFTransform* _transform; + IMFMediaType* _mediaType; + IMFMediaType* _inputMediaType; + IMFMediaType* _outputMediaType; + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + IWMResamplerProps* _resamplerProps; + #endif +}; + +//----------------------------------------------------------------------------- + +// A structure to hold various information related to the WASAPI implementation. +struct WasapiHandle +{ + IAudioClient* captureAudioClient; + IAudioClient* renderAudioClient; + IAudioCaptureClient* captureClient; + IAudioRenderClient* renderClient; + HANDLE captureEvent; + HANDLE renderEvent; + + WasapiHandle() + : captureAudioClient( NULL ), + renderAudioClient( NULL ), + captureClient( NULL ), + renderClient( NULL ), + captureEvent( NULL ), + renderEvent( NULL ) {} +}; + +//============================================================================= + +RtApiWasapi::RtApiWasapi() + : coInitialized_( false ), deviceEnumerator_( NULL ) +{ + // WASAPI can run either apartment or multi-threaded + HRESULT hr = CoInitialize( NULL ); + if ( !FAILED( hr ) ) + coInitialized_ = true; + + // Instantiate device enumerator + hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, + CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), + ( void** ) &deviceEnumerator_ ); + + // If this runs on an old Windows, it will fail. Ignore and proceed. + if ( FAILED( hr ) ) + deviceEnumerator_ = NULL; +} + +//----------------------------------------------------------------------------- + +RtApiWasapi::~RtApiWasapi() +{ + if ( stream_.state != STREAM_CLOSED ) + closeStream(); + + SAFE_RELEASE( deviceEnumerator_ ); + + // If this object previously called CoInitialize() + if ( coInitialized_ ) + CoUninitialize(); +} + +//============================================================================= + +unsigned int RtApiWasapi::getDeviceCount( void ) +{ + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + + if ( !deviceEnumerator_ ) + return 0; + + // Count capture devices + errorText_.clear(); + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceCount: Unable to retrieve render device count."; + goto Exit; + } + +Exit: + // release all references + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + + if ( errorText_.empty() ) + return captureDeviceCount + renderDeviceCount; + + error( RtAudioError::DRIVER_ERROR ); + return 0; +} + +//----------------------------------------------------------------------------- + +RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + std::string defaultDeviceName; + bool isCaptureDevice = false; + + PROPVARIANT deviceNameProp; + PROPVARIANT defaultDeviceNameProp; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + IMMDevice* defaultDevicePtr = NULL; + IAudioClient* audioClient = NULL; + IPropertyStore* devicePropStore = NULL; + IPropertyStore* defaultDevicePropStore = NULL; + + WAVEFORMATEX* deviceFormat = NULL; + WAVEFORMATEX* closestMatchFormat = NULL; + + // probed + info.probed = false; + + // Count capture devices + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device count."; + goto Exit; + } + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Invalid device index."; + errorType = RtAudioError::INVALID_USE; + goto Exit; + } + + // determine whether index falls within capture or render devices + if ( device >= renderDeviceCount ) { + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve capture device handle."; + goto Exit; + } + isCaptureDevice = true; + } + else { + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve render device handle."; + goto Exit; + } + isCaptureDevice = false; + } + + // get default device name + if ( isCaptureDevice ) { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &defaultDevicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default capture device handle."; + goto Exit; + } + } + else { + hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &defaultDevicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default render device handle."; + goto Exit; + } + } + + hr = defaultDevicePtr->OpenPropertyStore( STGM_READ, &defaultDevicePropStore ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open default device property store."; + goto Exit; + } + PropVariantInit( &defaultDeviceNameProp ); + + hr = defaultDevicePropStore->GetValue( PKEY_Device_FriendlyName, &defaultDeviceNameProp ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve default device property: PKEY_Device_FriendlyName."; + goto Exit; + } + + defaultDeviceName = convertCharPointerToStdString(defaultDeviceNameProp.pwszVal); + + // name + hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to open device property store."; + goto Exit; + } + + PropVariantInit( &deviceNameProp ); + + hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; + goto Exit; + } + + info.name =convertCharPointerToStdString(deviceNameProp.pwszVal); + + // is default + if ( isCaptureDevice ) { + info.isDefaultInput = info.name == defaultDeviceName; + info.isDefaultOutput = false; + } + else { + info.isDefaultInput = false; + info.isDefaultOutput = info.name == defaultDeviceName; + } + + // channel count + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device audio client."; + goto Exit; + } + + hr = audioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format."; + goto Exit; + } + + if ( isCaptureDevice ) { + info.inputChannels = deviceFormat->nChannels; + info.outputChannels = 0; + info.duplexChannels = 0; + } + else { + info.inputChannels = 0; + info.outputChannels = deviceFormat->nChannels; + info.duplexChannels = 0; + } + + // sample rates + info.sampleRates.clear(); + + // allow support for all sample rates as we have a built-in sample rate converter + for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { + info.sampleRates.push_back( SAMPLE_RATES[i] ); + } + info.preferredSampleRate = deviceFormat->nSamplesPerSec; + + // native format + info.nativeFormats = 0; + + if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) + { + if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_FLOAT32; + } + else if ( deviceFormat->wBitsPerSample == 64 ) { + info.nativeFormats |= RTAUDIO_FLOAT64; + } + } + else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || + ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) + { + if ( deviceFormat->wBitsPerSample == 8 ) { + info.nativeFormats |= RTAUDIO_SINT8; + } + else if ( deviceFormat->wBitsPerSample == 16 ) { + info.nativeFormats |= RTAUDIO_SINT16; + } + else if ( deviceFormat->wBitsPerSample == 24 ) { + info.nativeFormats |= RTAUDIO_SINT24; + } + else if ( deviceFormat->wBitsPerSample == 32 ) { + info.nativeFormats |= RTAUDIO_SINT32; + } + } + + // probed + info.probed = true; + +Exit: + // release all references + PropVariantClear( &deviceNameProp ); + PropVariantClear( &defaultDeviceNameProp ); + + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + SAFE_RELEASE( defaultDevicePtr ); + SAFE_RELEASE( audioClient ); + SAFE_RELEASE( devicePropStore ); + SAFE_RELEASE( defaultDevicePropStore ); + + CoTaskMemFree( deviceFormat ); + CoTaskMemFree( closestMatchFormat ); + + if ( !errorText_.empty() ) + error( errorType ); + return info; +} + +//----------------------------------------------------------------------------- + +unsigned int RtApiWasapi::getDefaultOutputDevice( void ) +{ + for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { + if ( getDeviceInfo( i ).isDefaultOutput ) { + return i; + } + } + + return 0; +} + +//----------------------------------------------------------------------------- + +unsigned int RtApiWasapi::getDefaultInputDevice( void ) +{ + for ( unsigned int i = 0; i < getDeviceCount(); i++ ) { + if ( getDeviceInfo( i ).isDefaultInput ) { + return i; + } + } + + return 0; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::closeStream( void ) +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiWasapi::closeStream: No open stream to close."; + error( RtAudioError::WARNING ); + return; + } + + if ( stream_.state != STREAM_STOPPED ) + stopStream(); + + // clean up stream memory + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) + + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureClient ) + SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderClient ) + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); + + if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) + CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); + + delete ( WasapiHandle* ) stream_.apiHandle; + stream_.apiHandle = NULL; + + for ( int i = 0; i < 2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + // update stream state + stream_.state = STREAM_CLOSED; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::startStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiWasapi::startStream: The stream is already running."; + error( RtAudioError::WARNING ); + return; + } + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + // update stream state + stream_.state = STREAM_RUNNING; + + // create WASAPI stream thread + stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); + + if ( !stream_.callbackInfo.thread ) { + errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; + error( RtAudioError::THREAD_ERROR ); + } + else { + SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); + ResumeThread( ( void* ) stream_.callbackInfo.thread ); + } +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::stopStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::stopStream: The stream is already stopped."; + error( RtAudioError::WARNING ); + return; + } + + // inform stream thread by setting stream state to STREAM_STOPPING + stream_.state = STREAM_STOPPING; + + // wait until stream thread is stopped + while( stream_.state != STREAM_STOPPED ) { + Sleep( 1 ); + } + + // Wait for the last buffer to play before stopping. + Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; + error( RtAudioError::THREAD_ERROR ); + return; + } + + stream_.callbackInfo.thread = (ThreadHandle) NULL; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::abortStream( void ) +{ + verifyStream(); + + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiWasapi::abortStream: The stream is already stopped."; + error( RtAudioError::WARNING ); + return; + } + + // inform stream thread by setting stream state to STREAM_STOPPING + stream_.state = STREAM_STOPPING; + + // wait until stream thread is stopped + while ( stream_.state != STREAM_STOPPED ) { + Sleep( 1 ); + } + + // close thread handle + if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { + errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; + error( RtAudioError::THREAD_ERROR ); + return; + } + + stream_.callbackInfo.thread = (ThreadHandle) NULL; +} + +//----------------------------------------------------------------------------- + +bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ) +{ + bool methodResult = FAILURE; + unsigned int captureDeviceCount = 0; + unsigned int renderDeviceCount = 0; + + IMMDeviceCollection* captureDevices = NULL; + IMMDeviceCollection* renderDevices = NULL; + IMMDevice* devicePtr = NULL; + WAVEFORMATEX* deviceFormat = NULL; + unsigned int bufferBytes; + stream_.state = STREAM_STOPPED; + + // create API Handle if not already created + if ( !stream_.apiHandle ) + stream_.apiHandle = ( void* ) new WasapiHandle(); + + // Count capture devices + errorText_.clear(); + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device collection."; + goto Exit; + } + + hr = captureDevices->GetCount( &captureDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device count."; + goto Exit; + } + + // Count render devices + hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device collection."; + goto Exit; + } + + hr = renderDevices->GetCount( &renderDeviceCount ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device count."; + goto Exit; + } + + // validate device index + if ( device >= captureDeviceCount + renderDeviceCount ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Invalid device index."; + goto Exit; + } + + // if device index falls within capture devices + if ( device >= renderDeviceCount ) { + if ( mode != INPUT ) { + errorType = RtAudioError::INVALID_USE; + errorText_ = "RtApiWasapi::probeDeviceOpen: Capture device selected as output device."; + goto Exit; + } + + // retrieve captureAudioClient from devicePtr + IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + + hr = captureDevices->Item( device - renderDeviceCount, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &captureAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device audio client."; + goto Exit; + } + + hr = captureAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + + // if device index falls within render devices and is configured for loopback + if ( device < renderDeviceCount && mode == INPUT ) + { + // if renderAudioClient is not initialised, initialise it now + IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + if ( !renderAudioClient ) + { + probeDeviceOpen( device, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); + } + + // retrieve captureAudioClient from devicePtr + IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &captureAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; + goto Exit; + } + + hr = captureAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + + // if device index falls within render devices and is configured for output + if ( device < renderDeviceCount && mode == OUTPUT ) + { + // if renderAudioClient is already initialised, don't initialise it again + IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + if ( renderAudioClient ) + { + methodResult = SUCCESS; + goto Exit; + } + + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &renderAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; + goto Exit; + } + + hr = renderAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + + // fill stream data + if ( ( stream_.mode == OUTPUT && mode == INPUT ) || + ( stream_.mode == INPUT && mode == OUTPUT ) ) { + stream_.mode = DUPLEX; + } + else { + stream_.mode = mode; + } + + stream_.device[mode] = device; + stream_.doByteSwap[mode] = false; + stream_.sampleRate = sampleRate; + stream_.bufferSize = *bufferSize; + stream_.nBuffers = 1; + stream_.nUserChannels[mode] = channels; + stream_.channelOffset[mode] = firstChannel; + stream_.userFormat = format; + stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; + + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) + stream_.userInterleaved = false; + else + stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] || + stream_.nUserChannels[0] != stream_.nDeviceChannels[0] || + stream_.nUserChannels[1] != stream_.nDeviceChannels[1] ) + stream_.doConvertBuffer[mode] = true; + else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + if ( stream_.doConvertBuffer[mode] ) + setConvertInfo( mode, 0 ); + + // Allocate necessary internal buffers + bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); + + stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); + if ( !stream_.userBuffer[mode] ) { + errorType = RtAudioError::MEMORY_ERROR; + errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; + goto Exit; + } + + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) + stream_.callbackInfo.priority = 15; + else + stream_.callbackInfo.priority = 0; + + ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback + ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode + + methodResult = SUCCESS; + +Exit: + //clean up + SAFE_RELEASE( captureDevices ); + SAFE_RELEASE( renderDevices ); + SAFE_RELEASE( devicePtr ); + CoTaskMemFree( deviceFormat ); + + // if method failed, close the stream + if ( methodResult == FAILURE ) + closeStream(); + + if ( !errorText_.empty() ) + error( errorType ); + return methodResult; +} + +//============================================================================= + +DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); + + return 0; +} + +DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) +{ + if ( wasapiPtr ) + ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); + + return 0; +} + +//----------------------------------------------------------------------------- + +void RtApiWasapi::wasapiThread() +{ + // as this is a new thread, we must CoInitialize it + CoInitialize( NULL ); + + HRESULT hr; + + IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; + IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; + HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; + HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; + + WAVEFORMATEX* captureFormat = NULL; + WAVEFORMATEX* renderFormat = NULL; + float captureSrRatio = 0.0f; + float renderSrRatio = 0.0f; + WasapiBuffer captureBuffer; + WasapiBuffer renderBuffer; + WasapiResampler* captureResampler = NULL; + WasapiResampler* renderResampler = NULL; + + // declare local stream variables + RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; + BYTE* streamBuffer = NULL; + unsigned long captureFlags = 0; + unsigned int bufferFrameCount = 0; + unsigned int numFramesPadding = 0; + unsigned int convBufferSize = 0; + bool loopbackEnabled = stream_.device[INPUT] == stream_.device[OUTPUT]; + bool callbackPushed = true; + bool callbackPulled = false; + bool callbackStopped = false; + int callbackResult = 0; + + // convBuffer is used to store converted buffers between WASAPI and the user + char* convBuffer = NULL; + unsigned int convBuffSize = 0; + unsigned int deviceBuffSize = 0; + + std::string errorText; + RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; + + // Attempt to assign "Pro Audio" characteristic to thread + HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); + if ( AvrtDll ) { + DWORD taskIndex = 0; + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = + ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); + FreeLibrary( AvrtDll ); + } + + // start capture stream if applicable + if ( captureAudioClient ) { + hr = captureAudioClient->GetMixFormat( &captureFormat ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + goto Exit; + } + + // init captureResampler + captureResampler = new WasapiResampler( stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[INPUT] ) * 8, stream_.nDeviceChannels[INPUT], + captureFormat->nSamplesPerSec, stream_.sampleRate ); + + captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); + + if ( !captureClient ) { + hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + captureFormat, + NULL ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + goto Exit; + } + + hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), + ( void** ) &captureClient ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; + goto Exit; + } + + // don't configure captureEvent if in loopback mode + if ( !loopbackEnabled ) + { + // configure captureEvent to trigger on every available capture buffer + captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !captureEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; + goto Exit; + } + + hr = captureAudioClient->SetEventHandle( captureEvent ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; + + // reset the capture stream + hr = captureAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; + goto Exit; + } + + // start the capture stream + hr = captureAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start capture stream."; + goto Exit; + } + } + + unsigned int inBufferSize = 0; + hr = captureAudioClient->GetBufferSize( &inBufferSize ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; + goto Exit; + } + + // scale outBufferSize according to stream->user sample rate ratio + unsigned int outBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; + inBufferSize *= stream_.nDeviceChannels[INPUT]; + + // set captureBuffer size + captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); + } + + // start render stream if applicable + if ( renderAudioClient ) { + hr = renderAudioClient->GetMixFormat( &renderFormat ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + goto Exit; + } + + // init renderResampler + renderResampler = new WasapiResampler( stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[OUTPUT] ) * 8, stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, renderFormat->nSamplesPerSec ); + + renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); + + if ( !renderClient ) { + hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, + renderFormat, + NULL ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + goto Exit; + } + + hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), + ( void** ) &renderClient ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; + goto Exit; + } + + // configure renderEvent to trigger on every available render buffer + renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !renderEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; + goto Exit; + } + + hr = renderAudioClient->SetEventHandle( renderEvent ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to set render event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; + ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; + + // reset the render stream + hr = renderAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to reset render stream."; + goto Exit; + } + + // start the render stream + hr = renderAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start render stream."; + goto Exit; + } + } + + unsigned int outBufferSize = 0; + hr = renderAudioClient->GetBufferSize( &outBufferSize ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; + goto Exit; + } + + // scale inBufferSize according to user->stream sample rate ratio + unsigned int inBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; + outBufferSize *= stream_.nDeviceChannels[OUTPUT]; + + // set renderBuffer size + renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); + } + + // malloc buffer memory + if ( stream_.mode == INPUT ) + { + using namespace std; // for ceilf + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + } + else if ( stream_.mode == OUTPUT ) + { + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); + } + else if ( stream_.mode == DUPLEX ) + { + convBuffSize = std::max( ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); + } + + convBuffSize *= 2; // allow overflow for *SrRatio remainders + convBuffer = ( char* ) calloc( convBuffSize, 1 ); + stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); + if ( !convBuffer || !stream_.deviceBuffer ) { + errorType = RtAudioError::MEMORY_ERROR; + errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; + goto Exit; + } + + // stream process loop + while ( stream_.state != STREAM_STOPPING ) { + if ( !callbackPulled ) { + // Callback Input + // ============== + // 1. Pull callback buffer from inputBuffer + // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count + // Convert callback buffer to user format + + if ( captureAudioClient ) + { + int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); + if ( captureSrRatio != 1 ) + { + // account for remainders + samplesToPull--; + } + + convBufferSize = 0; + while ( convBufferSize < stream_.bufferSize ) + { + // Pull callback buffer from inputBuffer + callbackPulled = captureBuffer.pullBuffer( convBuffer, + samplesToPull * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ); + + if ( !callbackPulled ) + { + break; + } + + // Convert callback buffer to user sample rate + unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + unsigned int convSamples = 0; + + captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, + convBuffer, + samplesToPull, + convSamples ); + + convBufferSize += convSamples; + samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples + } + + if ( callbackPulled ) + { + if ( stream_.doConvertBuffer[INPUT] ) { + // Convert callback buffer to user format + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + else { + // no further conversion, simple copy deviceBuffer to userBuffer + memcpy( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); + } + } + } + else { + // if there is no capture stream, set callbackPulled flag + callbackPulled = true; + } + + // Execute Callback + // ================ + // 1. Execute user callback method + // 2. Handle return value from callback + + // if callback has not requested the stream to stop + if ( callbackPulled && !callbackStopped ) { + // Execute user callback method + callbackResult = callback( stream_.userBuffer[OUTPUT], + stream_.userBuffer[INPUT], + stream_.bufferSize, + getStreamTime(), + captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, + stream_.callbackInfo.userData ); + + // tick stream time + RtApi::tickStreamTime(); + + // Handle return value from callback + if ( callbackResult == 1 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); + if ( !threadHandle ) { + errorType = RtAudioError::THREAD_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; + goto Exit; + } + else if ( !CloseHandle( threadHandle ) ) { + errorType = RtAudioError::THREAD_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; + goto Exit; + } + + callbackStopped = true; + } + else if ( callbackResult == 2 ) { + // instantiate a thread to stop this thread + HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); + if ( !threadHandle ) { + errorType = RtAudioError::THREAD_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; + goto Exit; + } + else if ( !CloseHandle( threadHandle ) ) { + errorType = RtAudioError::THREAD_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; + goto Exit; + } + + callbackStopped = true; + } + } + } + + // Callback Output + // =============== + // 1. Convert callback buffer to stream format + // 2. Convert callback buffer to stream sample rate and channel count + // 3. Push callback buffer into outputBuffer + + if ( renderAudioClient && callbackPulled ) + { + // if the last call to renderBuffer.PushBuffer() was successful + if ( callbackPushed || convBufferSize == 0 ) + { + if ( stream_.doConvertBuffer[OUTPUT] ) + { + // Convert callback buffer to stream format + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + + } + else { + // no further conversion, simple copy userBuffer to deviceBuffer + memcpy( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.bufferSize * stream_.nUserChannels[OUTPUT] * formatBytes( stream_.userFormat ) ); + } + + // Convert callback buffer to stream sample rate + renderResampler->Convert( convBuffer, + stream_.deviceBuffer, + stream_.bufferSize, + convBufferSize ); + } + + // Push callback buffer into outputBuffer + callbackPushed = renderBuffer.pushBuffer( convBuffer, + convBufferSize * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ); + } + else { + // if there is no render stream, set callbackPushed flag + callbackPushed = true; + } + + // Stream Capture + // ============== + // 1. Get capture buffer from stream + // 2. Push capture buffer into inputBuffer + // 3. If 2. was successful: Release capture buffer + + if ( captureAudioClient ) { + // if the callback input buffer was not pulled from captureBuffer, wait for next capture event + if ( !callbackPulled ) { + WaitForSingleObject( loopbackEnabled ? renderEvent : captureEvent, INFINITE ); + } + + // Get capture buffer from stream + hr = captureClient->GetBuffer( &streamBuffer, + &bufferFrameCount, + &captureFlags, NULL, NULL ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; + goto Exit; + } + + if ( bufferFrameCount != 0 ) { + // Push capture buffer into inputBuffer + if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ) ) + { + // Release capture buffer + hr = captureClient->ReleaseBuffer( bufferFrameCount ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + } + else + { + // Inform WASAPI that capture was unsuccessful + hr = captureClient->ReleaseBuffer( 0 ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + goto Exit; + } + } + } + + // Stream Render + // ============= + // 1. Get render buffer from stream + // 2. Pull next buffer from outputBuffer + // 3. If 2. was successful: Fill render buffer with next buffer + // Release render buffer + + if ( renderAudioClient ) { + // if the callback output buffer was not pushed to renderBuffer, wait for next render event + if ( callbackPulled && !callbackPushed ) { + WaitForSingleObject( renderEvent, INFINITE ); + } + + // Get render buffer from stream + hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; + goto Exit; + } + + hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; + goto Exit; + } + + bufferFrameCount -= numFramesPadding; + + if ( bufferFrameCount != 0 ) { + hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; + goto Exit; + } + + // Pull next buffer from outputBuffer + // Fill render buffer with next buffer + if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, + bufferFrameCount * stream_.nDeviceChannels[OUTPUT], + stream_.deviceFormat[OUTPUT] ) ) + { + // Release render buffer + hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + } + else + { + // Inform WASAPI that render was unsuccessful + hr = renderClient->ReleaseBuffer( 0, 0 ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + goto Exit; + } + } + } + + // if the callback buffer was pushed renderBuffer reset callbackPulled flag + if ( callbackPushed ) { + // unsetting the callbackPulled flag lets the stream know that + // the audio device is ready for another callback output buffer. + callbackPulled = false; + } + + } + +Exit: + // clean up + CoTaskMemFree( captureFormat ); + CoTaskMemFree( renderFormat ); + + free ( convBuffer ); + delete renderResampler; + delete captureResampler; + + CoUninitialize(); + + // update stream state + stream_.state = STREAM_STOPPED; + + if ( !errorText.empty() ) + { + errorText_ = errorText; + error( errorType ); + } +} + +//******************** End of __WINDOWS_WASAPI__ *********************// +#endif + + +#if defined(__WINDOWS_DS__) // Windows DirectSound API + +// Modified by Robin Davies, October 2005 +// - Improvements to DirectX pointer chasing. +// - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. +// - Auto-call CoInitialize for DSOUND and ASIO platforms. +// Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 +// Changed device query structure for RtAudio 4.0.7, January 2010 + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__MINGW32__) + // missing from latest mingw winapi +#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ +#define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ +#define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ +#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ +#endif + +#define MINIMUM_DEVICE_BUFFER_SIZE 32768 + +#ifdef _MSC_VER // if Microsoft Visual C++ +#pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually. +#endif + +static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize ) +{ + if ( pointer > bufferSize ) pointer -= bufferSize; + if ( laterPointer < earlierPointer ) laterPointer += bufferSize; + if ( pointer < earlierPointer ) pointer += bufferSize; + return pointer >= earlierPointer && pointer < laterPointer; +} + +// A structure to hold various information related to the DirectSound +// API implementation. +struct DsHandle { + unsigned int drainCounter; // Tracks callback counts when draining + bool internalDrain; // Indicates if stop is initiated from callback or not. + void *id[2]; + void *buffer[2]; + bool xrun[2]; + UINT bufferPointer[2]; + DWORD dsBufferSize[2]; + DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. + HANDLE condition; + + DsHandle() + :drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } +}; + +// Declarations for utility functions, callbacks, and structures +// specific to the DirectSound implementation. +static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, + LPCTSTR description, + LPCTSTR module, + LPVOID lpContext ); + +static const char* getErrorString( int code ); + +static unsigned __stdcall callbackHandler( void *ptr ); + +struct DsDevice { + LPGUID id[2]; + bool validId[2]; + bool found; + std::string name; + + DsDevice() + : found(false) { validId[0] = false; validId[1] = false; } +}; + +struct DsProbeData { + bool isInput; + std::vector* dsDevices; +}; + +RtApiDs :: RtApiDs() +{ + // Dsound will run both-threaded. If CoInitialize fails, then just + // accept whatever the mainline chose for a threading model. + coInitialized_ = false; + HRESULT hr = CoInitialize( NULL ); + if ( !FAILED( hr ) ) coInitialized_ = true; +} + +RtApiDs :: ~RtApiDs() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); + if ( coInitialized_ ) CoUninitialize(); // balanced call. +} + +// The DirectSound default output is always the first device. +unsigned int RtApiDs :: getDefaultOutputDevice( void ) +{ + return 0; +} + +// The DirectSound default input is always the first input device, +// which is the first capture device enumerated. +unsigned int RtApiDs :: getDefaultInputDevice( void ) +{ + return 0; +} + +unsigned int RtApiDs :: getDeviceCount( void ) +{ + // Set query flag for previously found devices to false, so that we + // can check for any devices that have disappeared. + for ( unsigned int i=0; i(dsDevices.size()); +} + +RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + if ( dsDevices.size() == 0 ) { + // Force a query of all devices + getDeviceCount(); + if ( dsDevices.size() == 0 ) { + errorText_ = "RtApiDs::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + } + + if ( device >= dsDevices.size() ) { + errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + HRESULT result; + if ( dsDevices[ device ].validId[0] == false ) goto probeInput; + + LPDIRECTSOUND output; + DSCAPS outCaps; + result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto probeInput; + } + + outCaps.dwSize = sizeof( outCaps ); + result = output->GetCaps( &outCaps ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto probeInput; + } + + // Get output channel information. + info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + // Get sample rate information. + info.sampleRates.clear(); + for ( unsigned int k=0; k= (unsigned int) outCaps.dwMinSecondarySampleRate && + SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) { + info.sampleRates.push_back( SAMPLE_RATES[k] ); + + if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + } + } + + // Get format information. + if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16; + if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8; + + output->Release(); + + if ( getDefaultOutputDevice() == device ) + info.isDefaultOutput = true; + + if ( dsDevices[ device ].validId[1] == false ) { + info.name = dsDevices[ device ].name; + info.probed = true; + return info; + } + + probeInput: + + LPDIRECTSOUNDCAPTURE input; + result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + DSCCAPS inCaps; + inCaps.dwSize = sizeof( inCaps ); + result = input->GetCaps( &inCaps ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get input channel information. + info.inputChannels = inCaps.dwChannels; + + // Get sample rate and format information. + std::vector rates; + if ( inCaps.dwChannels >= 2 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8; + + if ( info.nativeFormats & RTAUDIO_SINT16 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 ); + } + else if ( info.nativeFormats & RTAUDIO_SINT8 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 ); + } + } + else if ( inCaps.dwChannels == 1 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16; + if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8; + if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8; + + if ( info.nativeFormats & RTAUDIO_SINT16 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 ); + } + else if ( info.nativeFormats & RTAUDIO_SINT8 ) { + if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 ); + if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 ); + if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 ); + if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 ); + } + } + else info.inputChannels = 0; // technically, this would be an error + + input->Release(); + + if ( info.inputChannels == 0 ) return info; + + // Copy the supported rates to the info structure but avoid duplication. + bool found; + for ( unsigned int i=0; i 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + if ( device == 0 ) info.isDefaultInput = true; + + // Copy name and return. + info.name = dsDevices[ device ].name; + info.probed = true; + return info; +} + +bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + if ( channels + firstChannel > 2 ) { + errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device."; + return FAILURE; + } + + size_t nDevices = dsDevices.size(); + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiDs::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + if ( mode == OUTPUT ) { + if ( dsDevices[ device ].validId[0] == false ) { + errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support output!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + else { // mode == INPUT + if ( dsDevices[ device ].validId[1] == false ) { + errorStream_ << "RtApiDs::probeDeviceOpen: device (" << device << ") does not support input!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // According to a note in PortAudio, using GetDesktopWindow() + // instead of GetForegroundWindow() is supposed to avoid problems + // that occur when the application's window is not the foreground + // window. Also, if the application window closes before the + // DirectSound buffer, DirectSound can crash. In the past, I had + // problems when using GetDesktopWindow() but it seems fine now + // (January 2010). I'll leave it commented here. + // HWND hWnd = GetForegroundWindow(); + HWND hWnd = GetDesktopWindow(); + + // Check the numberOfBuffers parameter and limit the lowest value to + // two. This is a judgement call and a value of two is probably too + // low for capture, but it should work for playback. + int nBuffers = 0; + if ( options ) nBuffers = options->numberOfBuffers; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2; + if ( nBuffers < 2 ) nBuffers = 3; + + // Check the lower range of the user-specified buffer size and set + // (arbitrarily) to a lower bound of 32. + if ( *bufferSize < 32 ) *bufferSize = 32; + + // Create the wave format structure. The data format setting will + // be determined later. + WAVEFORMATEX waveFormat; + ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) ); + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nChannels = channels + firstChannel; + waveFormat.nSamplesPerSec = (unsigned long) sampleRate; + + // Determine the device buffer size. By default, we'll use the value + // defined above (32K), but we will grow it to make allowances for + // very large software buffer sizes. + DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; + DWORD dsPointerLeadTime = 0; + + void *ohandle = 0, *bhandle = 0; + HRESULT result; + if ( mode == OUTPUT ) { + + LPDIRECTSOUND output; + result = DirectSoundCreate( dsDevices[ device ].id[0], &output, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + DSCAPS outCaps; + outCaps.dwSize = sizeof( outCaps ); + result = output->GetCaps( &outCaps ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check channel information. + if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { + errorStream_ << "RtApiDs::getDeviceInfo: the output device (" << dsDevices[ device ].name << ") does not support stereo playback."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check format information. Use 16-bit format unless not + // supported or user requests 8-bit. + if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && + !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) { + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + stream_.userFormat = format; + + // Update wave format structure and buffer information. + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; + + // If the user wants an even bigger buffer, increase the device buffer size accordingly. + while ( dsPointerLeadTime * 2U > dsBufferSize ) + dsBufferSize *= 2; + + // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. + // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE ); + // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. + result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Even though we will write to the secondary buffer, we need to + // access the primary buffer to set the correct output format + // (since the default is 8-bit, 22 kHz!). Setup the DS primary + // buffer description. + DSBUFFERDESC bufferDescription; + ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSBUFFERDESC ); + bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + + // Obtain the primary buffer + LPDIRECTSOUNDBUFFER buffer; + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the primary DS buffer sound format. + result = buffer->SetFormat( &waveFormat ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Setup the secondary DS buffer description. + ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSBUFFERDESC ); + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GLOBALFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCHARDWARE ); // Force hardware mixing + bufferDescription.dwBufferBytes = dsBufferSize; + bufferDescription.lpwfxFormat = &waveFormat; + + // Try to create the secondary DS buffer. If that doesn't work, + // try to use software mixing. Otherwise, there's a problem. + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | + DSBCAPS_GLOBALFOCUS | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_LOCSOFTWARE ); // Force software mixing + result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + output->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Get the buffer size ... might be different from what we specified. + DSBCAPS dsbcaps; + dsbcaps.dwSize = sizeof( DSBCAPS ); + result = buffer->GetCaps( &dsbcaps ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + dsBufferSize = dsbcaps.dwBufferBytes; + + // Lock the DS buffer + LPVOID audioPtr; + DWORD dataLen; + result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + output->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + ohandle = (void *) output; + bhandle = (void *) buffer; + } + + if ( mode == INPUT ) { + + LPDIRECTSOUNDCAPTURE input; + result = DirectSoundCaptureCreate( dsDevices[ device ].id[1], &input, NULL ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + DSCCAPS inCaps; + inCaps.dwSize = sizeof( inCaps ); + result = input->GetCaps( &inCaps ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check channel information. + if ( inCaps.dwChannels < channels + firstChannel ) { + errorText_ = "RtApiDs::getDeviceInfo: the input device does not support requested input channels."; + return FAILURE; + } + + // Check format information. Use 16-bit format unless user + // requests 8-bit. + DWORD deviceFormats; + if ( channels + firstChannel == 2 ) { + deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; + if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + else { // assume 16-bit is supported + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + } + else { // channel == 1 + deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; + if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { + waveFormat.wBitsPerSample = 8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + else { // assume 16-bit is supported + waveFormat.wBitsPerSample = 16; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + } + stream_.userFormat = format; + + // Update wave format structure and buffer information. + waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; + + // If the user wants an even bigger buffer, increase the device buffer size accordingly. + while ( dsPointerLeadTime * 2U > dsBufferSize ) + dsBufferSize *= 2; + + // Setup the secondary DS buffer description. + DSCBUFFERDESC bufferDescription; + ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) ); + bufferDescription.dwSize = sizeof( DSCBUFFERDESC ); + bufferDescription.dwFlags = 0; + bufferDescription.dwReserved = 0; + bufferDescription.dwBufferBytes = dsBufferSize; + bufferDescription.lpwfxFormat = &waveFormat; + + // Create the capture buffer. + LPDIRECTSOUNDCAPTUREBUFFER buffer; + result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); + if ( FAILED( result ) ) { + input->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Get the buffer size ... might be different from what we specified. + DSCBCAPS dscbcaps; + dscbcaps.dwSize = sizeof( DSCBCAPS ); + result = buffer->GetCaps( &dscbcaps ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + dsBufferSize = dscbcaps.dwBufferBytes; + + // NOTE: We could have a problem here if this is a duplex stream + // and the play and capture hardware buffer sizes are different + // (I'm actually not sure if that is a problem or not). + // Currently, we are not verifying that. + + // Lock the capture buffer + LPVOID audioPtr; + DWORD dataLen; + result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Zero the buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + input->Release(); + buffer->Release(); + errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices[ device ].name << ")!"; + errorText_ = errorStream_.str(); + return FAILURE; + } + + ohandle = (void *) input; + bhandle = (void *) buffer; + } + + // Set various stream parameters + DsHandle *handle = 0; + stream_.nDeviceChannels[mode] = channels + firstChannel; + stream_.nUserChannels[mode] = channels; + stream_.bufferSize = *bufferSize; + stream_.channelOffset[mode] = firstChannel; + stream_.deviceInterleaved[mode] = true; + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + + // Set flag for buffer conversion + stream_.doConvertBuffer[mode] = false; + if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode]) + stream_.doConvertBuffer[mode] = true; + if (stream_.userFormat != stream_.deviceFormat[mode]) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers + long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= (long) bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + // Allocate our DsHandle structures for the stream. + if ( stream_.apiHandle == 0 ) { + try { + handle = new DsHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory."; + goto error; + } + + // Create a manual-reset event. + handle->condition = CreateEvent( NULL, // no security + TRUE, // manual-reset + FALSE, // non-signaled initially + NULL ); // unnamed + stream_.apiHandle = (void *) handle; + } + else + handle = (DsHandle *) stream_.apiHandle; + handle->id[mode] = ohandle; + handle->buffer[mode] = bhandle; + handle->dsBufferSize[mode] = dsBufferSize; + handle->dsPointerLeadTime[mode] = dsPointerLeadTime; + + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + if ( stream_.mode == OUTPUT && mode == INPUT ) + // We had already set up an output stream. + stream_.mode = DUPLEX; + else + stream_.mode = mode; + stream_.nBuffers = nBuffers; + stream_.sampleRate = sampleRate; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup the callback thread. + if ( stream_.callbackInfo.isRunning == false ) { + unsigned threadId; + stream_.callbackInfo.isRunning = true; + stream_.callbackInfo.object = (void *) this; + stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler, + &stream_.callbackInfo, 0, &threadId ); + if ( stream_.callbackInfo.thread == 0 ) { + errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!"; + goto error; + } + + // Boost DS thread priority + SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST ); + } + return SUCCESS; + + error: + if ( handle ) { + if ( handle->buffer[0] ) { // the object pointer can be NULL and valid + LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + if ( buffer ) buffer->Release(); + object->Release(); + } + if ( handle->buffer[1] ) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + if ( buffer ) buffer->Release(); + object->Release(); + } + CloseHandle( handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiDs :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiDs::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + // Stop the callback thread. + stream_.callbackInfo.isRunning = false; + WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE ); + CloseHandle( (HANDLE) stream_.callbackInfo.thread ); + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + if ( handle ) { + if ( handle->buffer[0] ) { // the object pointer can be NULL and valid + LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + if ( buffer ) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + if ( handle->buffer[1] ) { + LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + if ( buffer ) { + buffer->Stop(); + buffer->Release(); + } + object->Release(); + } + CloseHandle( handle->condition ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiDs :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiDs::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + + // Increase scheduler frequency on lesser windows (a side-effect of + // increasing timer accuracy). On greater windows (Win2K or later), + // this is already in effect. + timeBeginPeriod( 1 ); + + buffersRolling = false; + duplexPrerollBytes = 0; + + if ( stream_.mode == DUPLEX ) { + // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize. + duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] ); + } + + HRESULT result = 0; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = buffer->Play( 0, 0, DSBPLAY_LOOPING ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + result = buffer->Start( DSCBSTART_LOOPING ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + handle->drainCounter = 0; + handle->internalDrain = false; + ResetEvent( handle->condition ); + stream_.state = STREAM_RUNNING; + + unlock: + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiDs :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + HRESULT result = 0; + LPVOID audioPtr; + DWORD dataLen; + DsHandle *handle = (DsHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( handle->drainCounter == 0 ) { + handle->drainCounter = 2; + WaitForSingleObject( handle->condition, INFINITE ); // block until signaled + } + + stream_.state = STREAM_STOPPED; + + MUTEX_LOCK( &stream_.mutex ); + + // Stop the buffer and clear memory + LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = buffer->Stop(); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // If we start playing again, we must begin at beginning of buffer. + handle->bufferPointer[0] = 0; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + audioPtr = NULL; + dataLen = 0; + + stream_.state = STREAM_STOPPED; + + if ( stream_.mode != DUPLEX ) + MUTEX_LOCK( &stream_.mutex ); + + result = buffer->Stop(); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Lock the buffer and clear it so that if we start to play again, + // we won't have old data playing. + result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // Zero the DS buffer + ZeroMemory( audioPtr, dataLen ); + + // Unlock the DS buffer + result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!"; + errorText_ = errorStream_.str(); + goto unlock; + } + + // If we start recording again, we must begin at beginning of buffer. + handle->bufferPointer[1] = 0; + } + + unlock: + timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. + MUTEX_UNLOCK( &stream_.mutex ); + + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiDs :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + DsHandle *handle = (DsHandle *) stream_.apiHandle; + handle->drainCounter = 2; + + stopStream(); +} + +void RtApiDs :: callbackEvent() +{ + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { + Sleep( 50 ); // sleep 50 milliseconds + return; + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; + DsHandle *handle = (DsHandle *) stream_.apiHandle; + + // Check if we were draining the stream and signal is finished. + if ( handle->drainCounter > stream_.nBuffers + 2 ) { + + stream_.state = STREAM_STOPPING; + if ( handle->internalDrain == false ) + SetEvent( handle->condition ); + else + stopStream(); + return; + } + + // Invoke user callback to get fresh output data UNLESS we are + // draining stream. + if ( handle->drainCounter == 0 ) { + RtAudioCallback callback = (RtAudioCallback) info->callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; + abortStream(); + return; + } + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; + handle->internalDrain = true; + } + } + + HRESULT result; + DWORD currentWritePointer, safeWritePointer; + DWORD currentReadPointer, safeReadPointer; + UINT nextWritePointer; + + LPVOID buffer1 = NULL; + LPVOID buffer2 = NULL; + DWORD bufferSize1 = 0; + DWORD bufferSize2 = 0; + + char *buffer; + long bufferBytes; + + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + if ( buffersRolling == false ) { + if ( stream_.mode == DUPLEX ) { + //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); + + // It takes a while for the devices to get rolling. As a result, + // there's no guarantee that the capture and write device pointers + // will move in lockstep. Wait here for both devices to start + // rolling, and then set our buffer pointers accordingly. + // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 + // bytes later than the write buffer. + + // Stub: a serious risk of having a pre-emptive scheduling round + // take place between the two GetCurrentPosition calls... but I'm + // really not sure how to solve the problem. Temporarily boost to + // Realtime priority, maybe; but I'm not sure what priority the + // DirectSound service threads run at. We *should* be roughly + // within a ms or so of correct. + + LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + + DWORD startSafeWritePointer, startSafeReadPointer; + + result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + while ( true ) { + result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; + Sleep( 1 ); + } + + //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); + + handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; + if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; + handle->bufferPointer[1] = safeReadPointer; + } + else if ( stream_.mode == OUTPUT ) { + + // Set the proper nextWritePosition after initial startup. + LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; + if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; + } + + buffersRolling = true; + } + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; + + if ( handle->drainCounter > 1 ) { // write zeros to the output stream + bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; + bufferBytes *= formatBytes( stream_.userFormat ); + memset( stream_.userBuffer[0], 0, bufferBytes ); + } + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0]; + bufferBytes *= formatBytes( stream_.deviceFormat[0] ); + } + else { + buffer = stream_.userBuffer[0]; + bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; + bufferBytes *= formatBytes( stream_.userFormat ); + } + + // No byte swapping necessary in DirectSound implementation. + + // Ahhh ... windoze. 16-bit data is signed but 8-bit data is + // unsigned. So, we need to convert our signed 8-bit data here to + // unsigned. + if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 ) + for ( int i=0; idsBufferSize[0]; + nextWritePointer = handle->bufferPointer[0]; + + DWORD endWrite, leadPointer; + while ( true ) { + // Find out where the read and "safe write" pointers are. + result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + // We will copy our output buffer into the region between + // safeWritePointer and leadPointer. If leadPointer is not + // beyond the next endWrite position, wait until it is. + leadPointer = safeWritePointer + handle->dsPointerLeadTime[0]; + //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; + if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize; + if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset + endWrite = nextWritePointer + bufferBytes; + + // Check whether the entire write region is behind the play pointer. + if ( leadPointer >= endWrite ) break; + + // If we are here, then we must wait until the leadPointer advances + // beyond the end of our next write region. We use the + // Sleep() function to suspend operation until that happens. + double millis = ( endWrite - leadPointer ) * 1000.0; + millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + } + + if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) + || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { + // We've strayed into the forbidden zone ... resync the read pointer. + handle->xrun[0] = true; + nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; + if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize; + handle->bufferPointer[0] = nextWritePointer; + endWrite = nextWritePointer + bufferBytes; + } + + // Lock free space in the buffer + result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + // Copy our buffer into the DS buffer + CopyMemory( buffer1, buffer, bufferSize1 ); + if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 ); + + // Update our buffer offset and unlock sound buffer + dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; + handle->bufferPointer[0] = nextWritePointer; + } + + // Don't bother draining input + if ( handle->drainCounter ) { + handle->drainCounter++; + goto unlock; + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1]; + bufferBytes *= formatBytes( stream_.deviceFormat[1] ); + } + else { + buffer = stream_.userBuffer[1]; + bufferBytes = stream_.bufferSize * stream_.nUserChannels[1]; + bufferBytes *= formatBytes( stream_.userFormat ); + } + + LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; + long nextReadPointer = handle->bufferPointer[1]; + DWORD dsBufferSize = handle->dsBufferSize[1]; + + // Find out where the write and "safe read" pointers are. + result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset + DWORD endRead = nextReadPointer + bufferBytes; + + // Handling depends on whether we are INPUT or DUPLEX. + // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, + // then a wait here will drag the write pointers into the forbidden zone. + // + // In DUPLEX mode, rather than wait, we will back off the read pointer until + // it's in a safe position. This causes dropouts, but it seems to be the only + // practical way to sync up the read and write pointers reliably, given the + // the very complex relationship between phase and increment of the read and write + // pointers. + // + // In order to minimize audible dropouts in DUPLEX mode, we will + // provide a pre-roll period of 0.5 seconds in which we return + // zeros from the read buffer while the pointers sync up. + + if ( stream_.mode == DUPLEX ) { + if ( safeReadPointer < endRead ) { + if ( duplexPrerollBytes <= 0 ) { + // Pre-roll time over. Be more agressive. + int adjustment = endRead-safeReadPointer; + + handle->xrun[1] = true; + // Two cases: + // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, + // and perform fine adjustments later. + // - small adjustments: back off by twice as much. + if ( adjustment >= 2*bufferBytes ) + nextReadPointer = safeReadPointer-2*bufferBytes; + else + nextReadPointer = safeReadPointer-bufferBytes-adjustment; + + if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; + + } + else { + // In pre=roll time. Just do it. + nextReadPointer = safeReadPointer - bufferBytes; + while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; + } + endRead = nextReadPointer + bufferBytes; + } + } + else { // mode == INPUT + while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { + // See comments for playback. + double millis = (endRead - safeReadPointer) * 1000.0; + millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); + if ( millis < 1.0 ) millis = 1.0; + Sleep( (DWORD) millis ); + + // Wake up and find out where we are now. + result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset + } + } + + // Lock free space in the buffer + result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1, + &bufferSize1, &buffer2, &bufferSize2, 0 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + + if ( duplexPrerollBytes <= 0 ) { + // Copy our buffer into the DS buffer + CopyMemory( buffer, buffer1, bufferSize1 ); + if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 ); + } + else { + memset( buffer, 0, bufferSize1 ); + if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 ); + duplexPrerollBytes -= bufferSize1 + bufferSize2; + } + + // Update our buffer offset and unlock sound buffer + nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize; + dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); + if ( FAILED( result ) ) { + errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + handle->bufferPointer[1] = nextReadPointer; + + // No byte swapping necessary in DirectSound implementation. + + // If necessary, convert 8-bit data from unsigned to signed. + if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 ) + for ( int j=0; jobject; + bool* isRunning = &info->isRunning; + + while ( *isRunning == true ) { + object->callbackEvent(); + } + + _endthreadex( 0 ); + return 0; +} + +static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, + LPCTSTR description, + LPCTSTR /*module*/, + LPVOID lpContext ) +{ + struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; + std::vector& dsDevices = *probeInfo.dsDevices; + + HRESULT hr; + bool validDevice = false; + if ( probeInfo.isInput == true ) { + DSCCAPS caps; + LPDIRECTSOUNDCAPTURE object; + + hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); + if ( hr != DS_OK ) return TRUE; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if ( hr == DS_OK ) { + if ( caps.dwChannels > 0 && caps.dwFormats > 0 ) + validDevice = true; + } + object->Release(); + } + else { + DSCAPS caps; + LPDIRECTSOUND object; + hr = DirectSoundCreate( lpguid, &object, NULL ); + if ( hr != DS_OK ) return TRUE; + + caps.dwSize = sizeof(caps); + hr = object->GetCaps( &caps ); + if ( hr == DS_OK ) { + if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) + validDevice = true; + } + object->Release(); + } + + // If good device, then save its name and guid. + std::string name = convertCharPointerToStdString( description ); + //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) + if ( lpguid == NULL ) + name = "Default Device"; + if ( validDevice ) { + for ( unsigned int i=0; i +#include + + // A structure to hold various information related to the ALSA API + // implementation. +struct AlsaHandle { + snd_pcm_t *handles[2]; + bool synchronized; + bool xrun[2]; + pthread_cond_t runnable_cv; + bool runnable; + + AlsaHandle() + :synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } +}; + +static void *alsaCallbackHandler( void * ptr ); + +RtApiAlsa :: RtApiAlsa() +{ + // Nothing to do here. +} + +RtApiAlsa :: ~RtApiAlsa() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiAlsa :: getDeviceCount( void ) +{ + unsigned nDevices = 0; + int result, subdevice, card; + char name[64]; + snd_ctl_t *handle = 0; + + // Count cards and devices + card = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &handle, name, 0 ); + if ( result < 0 ) { + handle = 0; + errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto nextcard; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( handle, &subdevice ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + break; + } + if ( subdevice < 0 ) + break; + nDevices++; + } + nextcard: + if ( handle ) + snd_ctl_close( handle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &handle, "default", 0 ); + if (result == 0) { + nDevices++; + snd_ctl_close( handle ); + } + + return nDevices; +} + +RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + unsigned nDevices = 0; + int result, subdevice, card; + char name[64]; + snd_ctl_t *chandle = 0; + + // Count cards and devices + card = -1; + subdevice = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); + if ( result < 0 ) { + chandle = 0; + errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto nextcard; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( chandle, &subdevice ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + break; + } + if ( subdevice < 0 ) break; + if ( nDevices == device ) { + sprintf( name, "hw:%d,%d", card, subdevice ); + goto foundDevice; + } + nDevices++; + } + nextcard: + if ( chandle ) + snd_ctl_close( chandle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + nDevices++; + } + + if ( nDevices == 0 ) { + errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + foundDevice: + + // If a stream is already open, we cannot probe the stream devices. + // Thus, use the saved results. + if ( stream_.state != STREAM_CLOSED && + ( stream_.device[0] == device || stream_.device[1] == device ) ) { + snd_ctl_close( chandle ); + if ( device >= devices_.size() ) { + errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; + error( RtAudioError::WARNING ); + return info; + } + return devices_[ device ]; + } + + int openMode = SND_PCM_ASYNC; + snd_pcm_stream_t stream; + snd_pcm_info_t *pcminfo; + snd_pcm_info_alloca( &pcminfo ); + snd_pcm_t *phandle; + snd_pcm_hw_params_t *params; + snd_pcm_hw_params_alloca( ¶ms ); + + // First try for playback unless default device (which has subdev -1) + stream = SND_PCM_STREAM_PLAYBACK; + snd_pcm_info_set_stream( pcminfo, stream ); + if ( subdevice != -1 ) { + snd_pcm_info_set_device( pcminfo, subdevice ); + snd_pcm_info_set_subdevice( pcminfo, 0 ); + + result = snd_ctl_pcm_info( chandle, pcminfo ); + if ( result < 0 ) { + // Device probably doesn't support playback. + goto captureProbe; + } + } + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + + // Get output channel information. + unsigned int value; + result = snd_pcm_hw_params_get_channels_max( params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + goto captureProbe; + } + info.outputChannels = value; + snd_pcm_close( phandle ); + + captureProbe: + stream = SND_PCM_STREAM_CAPTURE; + snd_pcm_info_set_stream( pcminfo, stream ); + + // Now try for capture unless default device (with subdev = -1) + if ( subdevice != -1 ) { + result = snd_ctl_pcm_info( chandle, pcminfo ); + snd_ctl_close( chandle ); + if ( result < 0 ) { + // Device probably doesn't support capture. + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + } + else + snd_ctl_close( chandle ); + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + + result = snd_pcm_hw_params_get_channels_max( params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } + info.inputChannels = value; + snd_pcm_close( phandle ); + + // If device opens for both playback and capture, we determine the channels. + if ( info.outputChannels > 0 && info.inputChannels > 0 ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + + // ALSA doesn't provide default devices so we'll use the first available one. + if ( device == 0 && info.outputChannels > 0 ) + info.isDefaultOutput = true; + if ( device == 0 && info.inputChannels > 0 ) + info.isDefaultInput = true; + + probeParameters: + // At this point, we just need to figure out the supported data + // formats and sample rates. We'll proceed by opening the device in + // the direction with the maximum number of channels, or playback if + // they are equal. This might limit our sample rate options, but so + // be it. + + if ( info.outputChannels >= info.inputChannels ) + stream = SND_PCM_STREAM_PLAYBACK; + else + stream = SND_PCM_STREAM_CAPTURE; + snd_pcm_info_set_stream( pcminfo, stream ); + + result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // The device is open ... fill the parameter structure. + result = snd_pcm_hw_params_any( phandle, params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Test our discrete set of sample rate values. + info.sampleRates.clear(); + for ( unsigned int i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[i]; + } + } + if ( info.sampleRates.size() == 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe the supported data formats ... we don't care about endian-ness just yet + snd_pcm_format_t format; + info.nativeFormats = 0; + format = SND_PCM_FORMAT_S8; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT8; + format = SND_PCM_FORMAT_S16; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT16; + format = SND_PCM_FORMAT_S24; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT24; + format = SND_PCM_FORMAT_S32; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_SINT32; + format = SND_PCM_FORMAT_FLOAT; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_FLOAT32; + format = SND_PCM_FORMAT_FLOAT64; + if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) + info.nativeFormats |= RTAUDIO_FLOAT64; + + // Check that we have at least one supported format + if ( info.nativeFormats == 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Get the device name + char *cardname; + result = snd_card_get_name( card, &cardname ); + if ( result >= 0 ) { + sprintf( name, "hw:%s,%d", cardname, subdevice ); + free( cardname ); + } + info.name = name; + + // That's all ... close the device and return + snd_pcm_close( phandle ); + info.probed = true; + return info; +} + +void RtApiAlsa :: saveDeviceInfo( void ) +{ + devices_.clear(); + + unsigned int nDevices = getDeviceCount(); + devices_.resize( nDevices ); + for ( unsigned int i=0; iflags & RTAUDIO_ALSA_USE_DEFAULT ) + snprintf(name, sizeof(name), "%s", "default"); + else { + // Count cards and devices + card = -1; + snd_card_next( &card ); + while ( card >= 0 ) { + sprintf( name, "hw:%d", card ); + result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + subdevice = -1; + while( 1 ) { + result = snd_ctl_pcm_next_device( chandle, &subdevice ); + if ( result < 0 ) break; + if ( subdevice < 0 ) break; + if ( nDevices == device ) { + sprintf( name, "hw:%d,%d", card, subdevice ); + snd_ctl_close( chandle ); + goto foundDevice; + } + nDevices++; + } + snd_ctl_close( chandle ); + snd_card_next( &card ); + } + + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + snd_ctl_close( chandle ); + goto foundDevice; + } + nDevices++; + } + snd_ctl_close( chandle ); + + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + } + + foundDevice: + + // The getDeviceInfo() function will not work for a device that is + // already open. Thus, we'll probe the system before opening a + // stream and save the results for use by getDeviceInfo(). + if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) // only do once + this->saveDeviceInfo(); + + snd_pcm_stream_t stream; + if ( mode == OUTPUT ) + stream = SND_PCM_STREAM_PLAYBACK; + else + stream = SND_PCM_STREAM_CAPTURE; + + snd_pcm_t *phandle; + int openMode = SND_PCM_ASYNC; + result = snd_pcm_open( &phandle, name, stream, openMode ); + if ( result < 0 ) { + if ( mode == OUTPUT ) + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; + else + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Fill the parameter structure. + snd_pcm_hw_params_t *hw_params; + snd_pcm_hw_params_alloca( &hw_params ); + result = snd_pcm_hw_params_any( phandle, hw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" ); + snd_pcm_hw_params_dump( hw_params, out ); +#endif + + // Set access ... check user preference. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) { + stream_.userInterleaved = false; + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); + if ( result < 0 ) { + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); + stream_.deviceInterleaved[mode] = true; + } + else + stream_.deviceInterleaved[mode] = false; + } + else { + stream_.userInterleaved = true; + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); + if ( result < 0 ) { + result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); + stream_.deviceInterleaved[mode] = false; + } + else + stream_.deviceInterleaved[mode] = true; + } + + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine how to set the device format. + stream_.userFormat = format; + snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; + + if ( format == RTAUDIO_SINT8 ) + deviceFormat = SND_PCM_FORMAT_S8; + else if ( format == RTAUDIO_SINT16 ) + deviceFormat = SND_PCM_FORMAT_S16; + else if ( format == RTAUDIO_SINT24 ) + deviceFormat = SND_PCM_FORMAT_S24; + else if ( format == RTAUDIO_SINT32 ) + deviceFormat = SND_PCM_FORMAT_S32; + else if ( format == RTAUDIO_FLOAT32 ) + deviceFormat = SND_PCM_FORMAT_FLOAT; + else if ( format == RTAUDIO_FLOAT64 ) + deviceFormat = SND_PCM_FORMAT_FLOAT64; + + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { + stream_.deviceFormat[mode] = format; + goto setFormat; + } + + // The user requested format is not natively supported by the device. + deviceFormat = SND_PCM_FORMAT_FLOAT64; + if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_FLOAT; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S32; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S24; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S16; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + goto setFormat; + } + + deviceFormat = SND_PCM_FORMAT_S8; + if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + goto setFormat; + } + + // If we get here, no supported format was found. + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + return FAILURE; + + setFormat: + result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine whether byte-swaping is necessary. + stream_.doByteSwap[mode] = false; + if ( deviceFormat != SND_PCM_FORMAT_S8 ) { + result = snd_pcm_format_cpu_endian( deviceFormat ); + if ( result == 0 ) + stream_.doByteSwap[mode] = true; + else if (result < 0) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + + // Set the sample rate. + result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine the number of channels for this device. We support a possible + // minimum device channel number > than the value requested by the user. + stream_.nUserChannels[mode] = channels; + unsigned int value; + result = snd_pcm_hw_params_get_channels_max( hw_params, &value ); + unsigned int deviceChannels = value; + if ( result < 0 || deviceChannels < channels + firstChannel ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + result = snd_pcm_hw_params_get_channels_min( hw_params, &value ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + deviceChannels = value; + if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel; + stream_.nDeviceChannels[mode] = deviceChannels; + + // Set the device channels. + result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the buffer (or period) size. + int dir = 0; + snd_pcm_uframes_t periodSize = *bufferSize; + result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + *bufferSize = periodSize; + + // Set the buffer number, which in ALSA is referred to as the "period". + unsigned int periods = 0; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2; + if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers; + if ( periods < 2 ) periods = 4; // a fairly safe default value + result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // If attempting to setup a duplex stream, the bufferSize parameter + // MUST be the same in both directions! + if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + stream_.bufferSize = *bufferSize; + + // Install the hardware configuration + result = snd_pcm_hw_params( phandle, hw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n"); + snd_pcm_hw_params_dump( hw_params, out ); +#endif + + // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. + snd_pcm_sw_params_t *sw_params = NULL; + snd_pcm_sw_params_alloca( &sw_params ); + snd_pcm_sw_params_current( phandle, sw_params ); + snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize ); + snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX ); + snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 ); + + // The following two settings were suggested by Theo Veenker + //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize ); + //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 ); + + // here are two options for a fix + //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX ); + snd_pcm_uframes_t val; + snd_pcm_sw_params_get_boundary( sw_params, &val ); + snd_pcm_sw_params_set_silence_size( phandle, sw_params, val ); + + result = snd_pcm_sw_params( phandle, sw_params ); + if ( result < 0 ) { + snd_pcm_close( phandle ); + errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + return FAILURE; + } + +#if defined(__RTAUDIO_DEBUG__) + fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n"); + snd_pcm_sw_params_dump( sw_params, out ); +#endif + + // Set flags for buffer conversion + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate the ApiHandle if necessary and then save. + AlsaHandle *apiInfo = 0; + if ( stream_.apiHandle == 0 ) { + try { + apiInfo = (AlsaHandle *) new AlsaHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory."; + goto error; + } + + if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + + stream_.apiHandle = (void *) apiInfo; + apiInfo->handles[0] = 0; + apiInfo->handles[1] = 0; + } + else { + apiInfo = (AlsaHandle *) stream_.apiHandle; + } + apiInfo->handles[mode] = phandle; + phandle = 0; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.sampleRate = sampleRate; + stream_.nBuffers = periods; + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup thread if necessary. + if ( stream_.mode == OUTPUT && mode == INPUT ) { + // We had already set up an output stream. + stream_.mode = DUPLEX; + // Link the streams if possible. + apiInfo->synchronized = false; + if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 ) + apiInfo->synchronized = true; + else { + errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; + error( RtAudioError::WARNING ); + } + } + else { + stream_.mode = mode; + + // Setup callback thread. + stream_.callbackInfo.object = (void *) this; + + // Set the thread attributes for joinable and realtime scheduling + // priority (optional). The higher priority will only take affect + // if the program is run as root or suid. Note, under Linux + // processes with CAP_SYS_NICE privilege, a user can change + // scheduling policy and priority (thus need not be root). See + // POSIX "capabilities". + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + stream_.callbackInfo.doRealtime = true; + struct sched_param param; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + param.sched_priority = priority; + + // Set the policy BEFORE the priority. Otherwise it fails. + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + // This is definitely required. Otherwise it fails. + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedparam(&attr, ¶m); + } + else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#endif + + stream_.callbackInfo.isRunning = true; + result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); + pthread_attr_destroy( &attr ); + if ( result ) { + // Failed. Try instead with default attributes. + result = pthread_create( &stream_.callbackInfo.thread, NULL, alsaCallbackHandler, &stream_.callbackInfo ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiAlsa::error creating callback thread!"; + goto error; + } + } + } + + return SUCCESS; + + error: + if ( apiInfo ) { + pthread_cond_destroy( &apiInfo->runnable_cv ); + if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); + if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); + delete apiInfo; + stream_.apiHandle = 0; + } + + if ( phandle) snd_pcm_close( phandle ); + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiAlsa :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + stream_.callbackInfo.isRunning = false; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + pthread_join( stream_.callbackInfo.thread, NULL ); + + if ( stream_.state == STREAM_RUNNING ) { + stream_.state = STREAM_STOPPED; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + snd_pcm_drop( apiInfo->handles[0] ); + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) + snd_pcm_drop( apiInfo->handles[1] ); + } + + if ( apiInfo ) { + pthread_cond_destroy( &apiInfo->runnable_cv ); + if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); + if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); + delete apiInfo; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiAlsa :: startStream() +{ + // This method calls snd_pcm_prepare if the device isn't already in that state. + + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + int result = 0; + snd_pcm_state_t state; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + state = snd_pcm_state( handle[0] ); + if ( state != SND_PCM_STATE_PREPARED ) { + result = snd_pcm_prepare( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open + state = snd_pcm_state( handle[1] ); + if ( state != SND_PCM_STATE_PREPARED ) { + result = snd_pcm_prepare( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + } + + stream_.state = STREAM_RUNNING; + + unlock: + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( apiInfo->synchronized ) + result = snd_pcm_drop( handle[0] ); + else + result = snd_pcm_drain( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + int result = 0; + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = snd_pcm_drop( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + apiInfo->runnable = false; // fixes high CPU usage when stopped + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result >= 0 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiAlsa :: callbackEvent() +{ + AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !apiInfo->runnable ) + pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + int doStopStream = 0; + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + apiInfo->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + apiInfo->xrun[1] = false; + } + doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) goto unlock; + + int result; + char *buffer; + int channels; + snd_pcm_t **handle; + snd_pcm_sframes_t frames; + RtAudioFormat format; + handle = (snd_pcm_t **) apiInfo->handles; + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + channels = stream_.nDeviceChannels[1]; + format = stream_.deviceFormat[1]; + } + else { + buffer = stream_.userBuffer[1]; + channels = stream_.nUserChannels[1]; + format = stream_.userFormat; + } + + // Read samples from device in interleaved/non-interleaved format. + if ( stream_.deviceInterleaved[1] ) + result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize ); + else { + void *bufs[channels]; + size_t offset = stream_.bufferSize * formatBytes( format ); + for ( int i=0; ixrun[1] = true; + result = snd_pcm_prepare( handle[1] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + error( RtAudioError::WARNING ); + goto tryOutput; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( buffer, stream_.bufferSize * channels, format ); + + // Do buffer conversion if necessary. + if ( stream_.doConvertBuffer[1] ) + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + + // Check stream latency + result = snd_pcm_delay( handle[1], &frames ); + if ( result == 0 && frames > 0 ) stream_.latency[1] = frames; + } + + tryOutput: + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + channels = stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + channels = stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[0] ) + byteSwapBuffer(buffer, stream_.bufferSize * channels, format); + + // Write samples to device in interleaved/non-interleaved format. + if ( stream_.deviceInterleaved[0] ) + result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize ); + else { + void *bufs[channels]; + size_t offset = stream_.bufferSize * formatBytes( format ); + for ( int i=0; ixrun[0] = true; + result = snd_pcm_prepare( handle[0] ); + if ( result < 0 ) { + errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + else + errorText_ = "RtApiAlsa::callbackEvent: audio write error, underrun."; + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + } + else { + errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; + errorText_ = errorStream_.str(); + } + error( RtAudioError::WARNING ); + goto unlock; + } + + // Check stream latency + result = snd_pcm_delay( handle[0], &frames ); + if ( result == 0 && frames > 0 ) stream_.latency[0] = frames; + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + + RtApi::tickStreamTime(); + if ( doStopStream == 1 ) this->stopStream(); +} + +static void *alsaCallbackHandler( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiAlsa *object = (RtApiAlsa *) info->object; + bool *isRunning = &info->isRunning; + +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if ( info->doRealtime ) { + std::cerr << "RtAudio alsa: " << + (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << + "running realtime scheduling" << std::endl; + } +#endif + + while ( *isRunning == true ) { + pthread_testcancel(); + object->callbackEvent(); + } + + pthread_exit( NULL ); +} + +//******************** End of __LINUX_ALSA__ *********************// +#endif + +#if defined(__LINUX_PULSE__) + +// Code written by Peter Meerwald, pmeerw@pmeerw.net +// and Tristan Matthews. + +#include +#include +#include + +static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, + 44100, 48000, 96000, 0}; + +struct rtaudio_pa_format_mapping_t { + RtAudioFormat rtaudio_format; + pa_sample_format_t pa_format; +}; + +static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { + {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, + {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, + {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, + {0, PA_SAMPLE_INVALID}}; + +struct PulseAudioHandle { + pa_simple *s_play; + pa_simple *s_rec; + pthread_t thread; + pthread_cond_t runnable_cv; + bool runnable; + PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } +}; + +RtApiPulse::~RtApiPulse() +{ + if ( stream_.state != STREAM_CLOSED ) + closeStream(); +} + +unsigned int RtApiPulse::getDeviceCount( void ) +{ + return 1; +} + +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ ) +{ + RtAudio::DeviceInfo info; + info.probed = true; + info.name = "PulseAudio"; + info.outputChannels = 2; + info.inputChannels = 2; + info.duplexChannels = 2; + info.isDefaultOutput = true; + info.isDefaultInput = true; + + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + info.sampleRates.push_back( *sr ); + + info.preferredSampleRate = 48000; + info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; + + return info; +} + +static void *pulseaudio_callback( void * user ) +{ + CallbackInfo *cbi = static_cast( user ); + RtApiPulse *context = static_cast( cbi->object ); + volatile bool *isRunning = &cbi->isRunning; + +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if (cbi->doRealtime) { + std::cerr << "RtAudio pulse: " << + (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << + "running realtime scheduling" << std::endl; + } +#endif + + while ( *isRunning ) { + pthread_testcancel(); + context->callbackEvent(); + } + + pthread_exit( NULL ); +} + +void RtApiPulse::closeStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + stream_.callbackInfo.isRunning = false; + if ( pah ) { + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + + pthread_join( pah->thread, 0 ); + if ( pah->s_play ) { + pa_simple_flush( pah->s_play, NULL ); + pa_simple_free( pah->s_play ); + } + if ( pah->s_rec ) + pa_simple_free( pah->s_rec ); + + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + if ( stream_.userBuffer[0] ) { + free( stream_.userBuffer[0] ); + stream_.userBuffer[0] = 0; + } + if ( stream_.userBuffer[1] ) { + free( stream_.userBuffer[1] ); + stream_.userBuffer[1] = 0; + } + + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; +} + +void RtApiPulse::callbackEvent( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !pah->runnable ) + pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " + "this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], + stream_.bufferSize, streamTime, status, + stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; + void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; + + if ( stream_.state != STREAM_RUNNING ) + goto unlock; + + int pa_error; + size_t bytes; + if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[OUTPUT] ); + } else + bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { + if ( stream_.doConvertBuffer[INPUT] ) + bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[INPUT] ); + else + bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + if ( stream_.doConvertBuffer[INPUT] ) { + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + RtApi::tickStreamTime(); + + if ( doStopStream == 1 ) + stopStream(); +} + +void RtApiPulse::startStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::startStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiPulse::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + stream_.state = STREAM_RUNNING; + + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::stopStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::abortStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, + unsigned int channels, unsigned int firstChannel, + unsigned int sampleRate, RtAudioFormat format, + unsigned int *bufferSize, RtAudio::StreamOptions *options ) +{ + PulseAudioHandle *pah = 0; + unsigned long bufferBytes = 0; + pa_sample_spec ss; + + if ( device != 0 ) return false; + if ( mode != INPUT && mode != OUTPUT ) return false; + if ( channels != 1 && channels != 2 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels."; + return false; + } + ss.channels = channels; + + if ( firstChannel != 0 ) return false; + + bool sr_found = false; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { + if ( sampleRate == *sr ) { + sr_found = true; + stream_.sampleRate = sampleRate; + ss.rate = sampleRate; + break; + } + } + if ( !sr_found ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; + return false; + } + + bool sf_found = 0; + for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; + sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { + if ( format == sf->rtaudio_format ) { + sf_found = true; + stream_.userFormat = sf->rtaudio_format; + stream_.deviceFormat[mode] = stream_.userFormat; + ss.format = sf->pa_format; + break; + } + } + if ( !sf_found ) { // Use internal data format conversion. + stream_.userFormat = format; + stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; + ss.format = PA_SAMPLE_FLOAT32LE; + } + + // Set other stream parameters. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + stream_.nBuffers = 1; + stream_.doByteSwap[mode] = false; + stream_.nUserChannels[mode] = channels; + stream_.nDeviceChannels[mode] = channels + firstChannel; + stream_.channelOffset[mode] = 0; + std::string streamName = "RtAudio"; + + // Set flags for buffer conversion. + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + + // Allocate necessary internal buffers. + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + stream_.bufferSize = *bufferSize; + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + if ( !stream_.apiHandle ) { + PulseAudioHandle *pah = new PulseAudioHandle; + if ( !pah ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; + goto error; + } + + stream_.apiHandle = pah; + if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; + goto error; + } + } + pah = static_cast( stream_.apiHandle ); + + int error; + if ( options && !options->streamName.empty() ) streamName = options->streamName; + switch ( mode ) { + case INPUT: + pa_buffer_attr buffer_attr; + buffer_attr.fragsize = bufferBytes; + buffer_attr.maxlength = -1; + + pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); + if ( !pah->s_rec ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; + goto error; + } + break; + case OUTPUT: + pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + if ( !pah->s_play ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; + goto error; + } + break; + default: + goto error; + } + + if ( stream_.mode == UNINITIALIZED ) + stream_.mode = mode; + else if ( stream_.mode == mode ) + goto error; + else + stream_.mode = DUPLEX; + + if ( !stream_.callbackInfo.isRunning ) { + stream_.callbackInfo.object = this; + + stream_.state = STREAM_STOPPED; + // Set the thread attributes for joinable and realtime scheduling + // priority (optional). The higher priority will only take affect + // if the program is run as root or suid. Note, under Linux + // processes with CAP_SYS_NICE privilege, a user can change + // scheduling policy and priority (thus need not be root). See + // POSIX "capabilities". + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + stream_.callbackInfo.doRealtime = true; + struct sched_param param; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + param.sched_priority = priority; + + // Set the policy BEFORE the priority. Otherwise it fails. + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + // This is definitely required. Otherwise it fails. + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedparam(&attr, ¶m); + } + else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#endif + + stream_.callbackInfo.isRunning = true; + int result = pthread_create( &pah->thread, &attr, pulseaudio_callback, (void *)&stream_.callbackInfo); + pthread_attr_destroy(&attr); + if(result != 0) { + // Failed. Try instead with default attributes. + result = pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo); + if(result != 0) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; + goto error; + } + } + } + + return SUCCESS; + + error: + if ( pah && stream_.callbackInfo.isRunning ) { + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +//******************** End of __LINUX_PULSE__ *********************// +#endif + +#if defined(__LINUX_OSS__) + +#include +#include +#include +#include +#include +#include +#include + +static void *ossCallbackHandler(void * ptr); + +// A structure to hold various information related to the OSS API +// implementation. +struct OssHandle { + int id[2]; // device ids + bool xrun[2]; + bool triggered; + pthread_cond_t runnable; + + OssHandle() + :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +RtApiOss :: RtApiOss() +{ + // Nothing to do here. +} + +RtApiOss :: ~RtApiOss() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiOss :: getDeviceCount( void ) +{ + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; + error( RtAudioError::WARNING ); + return 0; + } + + oss_sysinfo sysinfo; + if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; + error( RtAudioError::WARNING ); + return 0; + } + + close( mixerfd ); + return sysinfo.numaudios; +} + +RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) +{ + RtAudio::DeviceInfo info; + info.probed = false; + + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; + error( RtAudioError::WARNING ); + return info; + } + + oss_sysinfo sysinfo; + int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); + if ( result == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; + error( RtAudioError::WARNING ); + return info; + } + + unsigned nDevices = sysinfo.numaudios; + if ( nDevices == 0 ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + if ( device >= nDevices ) { + close( mixerfd ); + errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; + error( RtAudioError::INVALID_USE ); + return info; + } + + oss_audioinfo ainfo; + ainfo.dev = device; + result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); + close( mixerfd ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe channels + if ( ainfo.caps & PCM_CAP_OUTPUT ) info.outputChannels = ainfo.max_channels; + if ( ainfo.caps & PCM_CAP_INPUT ) info.inputChannels = ainfo.max_channels; + if ( ainfo.caps & PCM_CAP_DUPLEX ) { + if ( info.outputChannels > 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX ) + info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; + } + + // Probe data formats ... do for input + unsigned long mask = ainfo.iformats; + if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE ) + info.nativeFormats |= RTAUDIO_SINT16; + if ( mask & AFMT_S8 ) + info.nativeFormats |= RTAUDIO_SINT8; + if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE ) + info.nativeFormats |= RTAUDIO_SINT32; +#ifdef AFMT_FLOAT + if ( mask & AFMT_FLOAT ) + info.nativeFormats |= RTAUDIO_FLOAT32; +#endif + if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE ) + info.nativeFormats |= RTAUDIO_SINT24; + + // Check that we have at least one supported format + if ( info.nativeFormats == 0 ) { + errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + return info; + } + + // Probe the supported sample rates. + info.sampleRates.clear(); + if ( ainfo.nrates ) { + for ( unsigned int i=0; i info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + + break; + } + } + } + } + else { + // Check min and max rate values; + for ( unsigned int k=0; k= (int) SAMPLE_RATES[k] ) { + info.sampleRates.push_back( SAMPLE_RATES[k] ); + + if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) + info.preferredSampleRate = SAMPLE_RATES[k]; + } + } + } + + if ( info.sampleRates.size() == 0 ) { + errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + else { + info.probed = true; + info.name = ainfo.name; + } + + return info; +} + + +bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ) +{ + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'."; + return FAILURE; + } + + oss_sysinfo sysinfo; + int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); + if ( result == -1 ) { + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required."; + return FAILURE; + } + + unsigned nDevices = sysinfo.numaudios; + if ( nDevices == 0 ) { + // This should not happen because a check is made before this function is called. + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: no devices found!"; + return FAILURE; + } + + if ( device >= nDevices ) { + // This should not happen because a check is made before this function is called. + close( mixerfd ); + errorText_ = "RtApiOss::probeDeviceOpen: device ID is invalid!"; + return FAILURE; + } + + oss_audioinfo ainfo; + ainfo.dev = device; + result = ioctl( mixerfd, SNDCTL_AUDIOINFO, &ainfo ); + close( mixerfd ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Check if device supports input or output + if ( ( mode == OUTPUT && !( ainfo.caps & PCM_CAP_OUTPUT ) ) || + ( mode == INPUT && !( ainfo.caps & PCM_CAP_INPUT ) ) ) { + if ( mode == OUTPUT ) + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support output."; + else + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support input."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + int flags = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( mode == OUTPUT ) + flags |= O_WRONLY; + else { // mode == INPUT + if (stream_.mode == OUTPUT && stream_.device[0] == device) { + // We just set the same device for playback ... close and reopen for duplex (OSS only). + close( handle->id[0] ); + handle->id[0] = 0; + if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) { + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode."; + errorText_ = errorStream_.str(); + return FAILURE; + } + // Check that the number previously set channels is the same. + if ( stream_.nUserChannels[0] != channels ) { + errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + flags |= O_RDWR; + } + else + flags |= O_RDONLY; + } + + // Set exclusive access if specified. + if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL; + + // Try to open the device. + int fd; + fd = open( ainfo.devnode, flags, 0 ); + if ( fd == -1 ) { + if ( errno == EBUSY ) + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy."; + else + errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // For duplex operation, specifically set this mode (this doesn't seem to work). + /* + if ( flags | O_RDWR ) { + result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL ); + if ( result == -1) { + errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } + */ + + // Check the device channel support. + stream_.nUserChannels[mode] = channels; + if ( ainfo.max_channels < (int)(channels + firstChannel) ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the number of channels. + int deviceChannels = channels + firstChannel; + result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels ); + if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.nDeviceChannels[mode] = deviceChannels; + + // Get the data format mask + int mask; + result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Determine how to set the device format. + stream_.userFormat = format; + int deviceFormat = -1; + stream_.doByteSwap[mode] = false; + if ( format == RTAUDIO_SINT8 ) { + if ( mask & AFMT_S8 ) { + deviceFormat = AFMT_S8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + } + else if ( format == RTAUDIO_SINT16 ) { + if ( mask & AFMT_S16_NE ) { + deviceFormat = AFMT_S16_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else if ( mask & AFMT_S16_OE ) { + deviceFormat = AFMT_S16_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + stream_.doByteSwap[mode] = true; + } + } + else if ( format == RTAUDIO_SINT24 ) { + if ( mask & AFMT_S24_NE ) { + deviceFormat = AFMT_S24_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + } + else if ( mask & AFMT_S24_OE ) { + deviceFormat = AFMT_S24_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + stream_.doByteSwap[mode] = true; + } + } + else if ( format == RTAUDIO_SINT32 ) { + if ( mask & AFMT_S32_NE ) { + deviceFormat = AFMT_S32_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + } + else if ( mask & AFMT_S32_OE ) { + deviceFormat = AFMT_S32_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + stream_.doByteSwap[mode] = true; + } + } + + if ( deviceFormat == -1 ) { + // The user requested format is not natively supported by the device. + if ( mask & AFMT_S16_NE ) { + deviceFormat = AFMT_S16_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + } + else if ( mask & AFMT_S32_NE ) { + deviceFormat = AFMT_S32_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + } + else if ( mask & AFMT_S24_NE ) { + deviceFormat = AFMT_S24_NE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + } + else if ( mask & AFMT_S16_OE ) { + deviceFormat = AFMT_S16_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT16; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S32_OE ) { + deviceFormat = AFMT_S32_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT32; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S24_OE ) { + deviceFormat = AFMT_S24_OE; + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + stream_.doByteSwap[mode] = true; + } + else if ( mask & AFMT_S8) { + deviceFormat = AFMT_S8; + stream_.deviceFormat[mode] = RTAUDIO_SINT8; + } + } + + if ( stream_.deviceFormat[mode] == 0 ) { + // This really shouldn't happen ... + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Set the data format. + int temp = deviceFormat; + result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat ); + if ( result == -1 || deviceFormat != temp ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Attempt to set the buffer size. According to OSS, the minimum + // number of buffers is two. The supposed minimum buffer size is 16 + // bytes, so that will be our lower bound. The argument to this + // call is in the form 0xMMMMSSSS (hex), where the buffer size (in + // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. + // We'll check the actual value used near the end of the setup + // procedure. + int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels; + if ( ossBufferBytes < 16 ) ossBufferBytes = 16; + int buffers = 0; + if ( options ) buffers = options->numberOfBuffers; + if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2; + if ( buffers < 2 ) buffers = 3; + temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) ); + result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.nBuffers = buffers; + + // Save buffer size (in sample frames). + *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels ); + stream_.bufferSize = *bufferSize; + + // Set the sample rate. + int srate = sampleRate; + result = ioctl( fd, SNDCTL_DSP_SPEED, &srate ); + if ( result == -1 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + + // Verify the sample rate setup worked. + if ( abs( srate - (int)sampleRate ) > 100 ) { + close( fd ); + errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + stream_.sampleRate = sampleRate; + + if ( mode == INPUT && stream_.mode == OUTPUT && stream_.device[0] == device) { + // We're doing duplex setup here. + stream_.deviceFormat[0] = stream_.deviceFormat[1]; + stream_.nDeviceChannels[0] = deviceChannels; + } + + // Set interleaving parameters. + stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) + stream_.userInterleaved = false; + + // Set flags for buffer conversion + stream_.doConvertBuffer[mode] = false; + if ( stream_.userFormat != stream_.deviceFormat[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) + stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && + stream_.nUserChannels[mode] > 1 ) + stream_.doConvertBuffer[mode] = true; + + // Allocate the stream handles if necessary and then save. + if ( stream_.apiHandle == 0 ) { + try { + handle = new OssHandle; + } + catch ( std::bad_alloc& ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory."; + goto error; + } + + if ( pthread_cond_init( &handle->runnable, NULL ) ) { + errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable."; + goto error; + } + + stream_.apiHandle = (void *) handle; + } + else { + handle = (OssHandle *) stream_.apiHandle; + } + handle->id[mode] = fd; + + // Allocate necessary internal buffers. + unsigned long bufferBytes; + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + stream_.state = STREAM_STOPPED; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + // Setup thread if necessary. + if ( stream_.mode == OUTPUT && mode == INPUT ) { + // We had already set up an output stream. + stream_.mode = DUPLEX; + if ( stream_.device[0] == device ) handle->id[0] = fd; + } + else { + stream_.mode = mode; + + // Setup callback thread. + stream_.callbackInfo.object = (void *) this; + + // Set the thread attributes for joinable and realtime scheduling + // priority. The higher priority will only take affect if the + // program is run as root or suid. + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { + stream_.callbackInfo.doRealtime = true; + struct sched_param param; + int priority = options->priority; + int min = sched_get_priority_min( SCHED_RR ); + int max = sched_get_priority_max( SCHED_RR ); + if ( priority < min ) priority = min; + else if ( priority > max ) priority = max; + param.sched_priority = priority; + + // Set the policy BEFORE the priority. Otherwise it fails. + pthread_attr_setschedpolicy(&attr, SCHED_RR); + pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); + // This is definitely required. Otherwise it fails. + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + pthread_attr_setschedparam(&attr, ¶m); + } + else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#else + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); +#endif + + stream_.callbackInfo.isRunning = true; + result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); + pthread_attr_destroy( &attr ); + if ( result ) { + // Failed. Try instead with default attributes. + result = pthread_create( &stream_.callbackInfo.thread, NULL, ossCallbackHandler, &stream_.callbackInfo ); + if ( result ) { + stream_.callbackInfo.isRunning = false; + errorText_ = "RtApiOss::error creating callback thread!"; + goto error; + } + } + } + + return SUCCESS; + + error: + if ( handle ) { + pthread_cond_destroy( &handle->runnable ); + if ( handle->id[0] ) close( handle->id[0] ); + if ( handle->id[1] ) close( handle->id[1] ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.state = STREAM_CLOSED; + return FAILURE; +} + +void RtApiOss :: closeStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiOss::closeStream(): no open stream to close!"; + error( RtAudioError::WARNING ); + return; + } + + OssHandle *handle = (OssHandle *) stream_.apiHandle; + stream_.callbackInfo.isRunning = false; + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) + pthread_cond_signal( &handle->runnable ); + MUTEX_UNLOCK( &stream_.mutex ); + pthread_join( stream_.callbackInfo.thread, NULL ); + + if ( stream_.state == STREAM_RUNNING ) { + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) + ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + else + ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + stream_.state = STREAM_STOPPED; + } + + if ( handle ) { + pthread_cond_destroy( &handle->runnable ); + if ( handle->id[0] ) close( handle->id[0] ); + if ( handle->id[1] ) close( handle->id[1] ); + delete handle; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; +} + +void RtApiOss :: startStream() +{ + verifyStream(); + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiOss::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + + stream_.state = STREAM_RUNNING; + + // No need to do anything else here ... OSS automatically starts + // when fed samples. + + MUTEX_UNLOCK( &stream_.mutex ); + + OssHandle *handle = (OssHandle *) stream_.apiHandle; + pthread_cond_signal( &handle->runnable ); +} + +void RtApiOss :: stopStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + int result = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Flush the output with zeros a few times. + char *buffer; + int samples; + RtAudioFormat format; + + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + samples = stream_.bufferSize * stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + samples = stream_.bufferSize * stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + memset( buffer, 0, samples * formatBytes(format) ); + for ( unsigned int i=0; iid[0], buffer, samples * formatBytes(format) ); + if ( result == -1 ) { + errorText_ = "RtApiOss::stopStream: audio write error."; + error( RtAudioError::WARNING ); + } + } + + result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + handle->triggered = false; + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { + result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result != -1 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiOss :: abortStream() +{ + verifyStream(); + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + + int result = 0; + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + handle->triggered = false; + } + + if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { + result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); + if ( result == -1 ) { + errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.device[0] << ")."; + errorText_ = errorStream_.str(); + goto unlock; + } + } + + unlock: + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); + + if ( result != -1 ) return; + error( RtAudioError::SYSTEM_ERROR ); +} + +void RtApiOss :: callbackEvent() +{ + OssHandle *handle = (OssHandle *) stream_.apiHandle; + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + pthread_cond_wait( &handle->runnable, &stream_.mutex ); + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + // Invoke user callback to get fresh output data. + int doStopStream = 0; + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + if ( stream_.mode != INPUT && handle->xrun[0] == true ) { + status |= RTAUDIO_OUTPUT_UNDERFLOW; + handle->xrun[0] = false; + } + if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { + status |= RTAUDIO_INPUT_OVERFLOW; + handle->xrun[1] = false; + } + doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); + if ( doStopStream == 2 ) { + this->abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + // The state might change while waiting on a mutex. + if ( stream_.state == STREAM_STOPPED ) goto unlock; + + int result; + char *buffer; + int samples; + RtAudioFormat format; + + if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + + // Setup parameters and do buffer conversion if necessary. + if ( stream_.doConvertBuffer[0] ) { + buffer = stream_.deviceBuffer; + convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); + samples = stream_.bufferSize * stream_.nDeviceChannels[0]; + format = stream_.deviceFormat[0]; + } + else { + buffer = stream_.userBuffer[0]; + samples = stream_.bufferSize * stream_.nUserChannels[0]; + format = stream_.userFormat; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[0] ) + byteSwapBuffer( buffer, samples, format ); + + if ( stream_.mode == DUPLEX && handle->triggered == false ) { + int trig = 0; + ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); + result = write( handle->id[0], buffer, samples * formatBytes(format) ); + trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; + ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); + handle->triggered = true; + } + else + // Write samples to device. + result = write( handle->id[0], buffer, samples * formatBytes(format) ); + + if ( result == -1 ) { + // We'll assume this is an underrun, though there isn't a + // specific means for determining that. + handle->xrun[0] = true; + errorText_ = "RtApiOss::callbackEvent: audio write error."; + error( RtAudioError::WARNING ); + // Continue on to input section. + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { + + // Setup parameters. + if ( stream_.doConvertBuffer[1] ) { + buffer = stream_.deviceBuffer; + samples = stream_.bufferSize * stream_.nDeviceChannels[1]; + format = stream_.deviceFormat[1]; + } + else { + buffer = stream_.userBuffer[1]; + samples = stream_.bufferSize * stream_.nUserChannels[1]; + format = stream_.userFormat; + } + + // Read samples from device. + result = read( handle->id[1], buffer, samples * formatBytes(format) ); + + if ( result == -1 ) { + // We'll assume this is an overrun, though there isn't a + // specific means for determining that. + handle->xrun[1] = true; + errorText_ = "RtApiOss::callbackEvent: audio read error."; + error( RtAudioError::WARNING ); + goto unlock; + } + + // Do byte swapping if necessary. + if ( stream_.doByteSwap[1] ) + byteSwapBuffer( buffer, samples, format ); + + // Do buffer conversion if necessary. + if ( stream_.doConvertBuffer[1] ) + convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + + RtApi::tickStreamTime(); + if ( doStopStream == 1 ) this->stopStream(); +} + +static void *ossCallbackHandler( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiOss *object = (RtApiOss *) info->object; + bool *isRunning = &info->isRunning; + +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) + if (info->doRealtime) { + std::cerr << "RtAudio oss: " << + (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << + "running realtime scheduling" << std::endl; + } +#endif + + while ( *isRunning == true ) { + pthread_testcancel(); + object->callbackEvent(); + } + + pthread_exit( NULL ); +} + +//******************** End of __LINUX_OSS__ *********************// +#endif + + +// *************************************************** // +// +// Protected common (OS-independent) RtAudio methods. +// +// *************************************************** // + +// This method can be modified to control the behavior of error +// message printing. +void RtApi :: error( RtAudioError::Type type ) +{ + errorStream_.str(""); // clear the ostringstream + + RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; + if ( errorCallback ) { + // abortStream() can generate new error messages. Ignore them. Just keep original one. + + if ( firstErrorOccurred_ ) + return; + + firstErrorOccurred_ = true; + const std::string errorMessage = errorText_; + + if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { + stream_.callbackInfo.isRunning = false; // exit from the thread + abortStream(); + } + + errorCallback( type, errorMessage ); + firstErrorOccurred_ = false; + return; + } + + if ( type == RtAudioError::WARNING && showWarnings_ == true ) + std::cerr << '\n' << errorText_ << "\n\n"; + else if ( type != RtAudioError::WARNING ) + throw( RtAudioError( errorText_, type ) ); +} + +void RtApi :: verifyStream() +{ + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApi:: a stream is not open!"; + error( RtAudioError::INVALID_USE ); + } +} + +void RtApi :: clearStreamInfo() +{ + stream_.mode = UNINITIALIZED; + stream_.state = STREAM_CLOSED; + stream_.sampleRate = 0; + stream_.bufferSize = 0; + stream_.nBuffers = 0; + stream_.userFormat = 0; + stream_.userInterleaved = true; + stream_.streamTime = 0.0; + stream_.apiHandle = 0; + stream_.deviceBuffer = 0; + stream_.callbackInfo.callback = 0; + stream_.callbackInfo.userData = 0; + stream_.callbackInfo.isRunning = false; + stream_.callbackInfo.errorCallback = 0; + for ( int i=0; i<2; i++ ) { + stream_.device[i] = 11111; + stream_.doConvertBuffer[i] = false; + stream_.deviceInterleaved[i] = true; + stream_.doByteSwap[i] = false; + stream_.nUserChannels[i] = 0; + stream_.nDeviceChannels[i] = 0; + stream_.channelOffset[i] = 0; + stream_.deviceFormat[i] = 0; + stream_.latency[i] = 0; + stream_.userBuffer[i] = 0; + stream_.convertInfo[i].channels = 0; + stream_.convertInfo[i].inJump = 0; + stream_.convertInfo[i].outJump = 0; + stream_.convertInfo[i].inFormat = 0; + stream_.convertInfo[i].outFormat = 0; + stream_.convertInfo[i].inOffset.clear(); + stream_.convertInfo[i].outOffset.clear(); + } +} + +unsigned int RtApi :: formatBytes( RtAudioFormat format ) +{ + if ( format == RTAUDIO_SINT16 ) + return 2; + else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) + return 4; + else if ( format == RTAUDIO_FLOAT64 ) + return 8; + else if ( format == RTAUDIO_SINT24 ) + return 3; + else if ( format == RTAUDIO_SINT8 ) + return 1; + + errorText_ = "RtApi::formatBytes: undefined format."; + error( RtAudioError::WARNING ); + + return 0; +} + +void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel ) +{ + if ( mode == INPUT ) { // convert device to user buffer + stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1]; + stream_.convertInfo[mode].outJump = stream_.nUserChannels[1]; + stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1]; + stream_.convertInfo[mode].outFormat = stream_.userFormat; + } + else { // convert user to device buffer + stream_.convertInfo[mode].inJump = stream_.nUserChannels[0]; + stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0]; + stream_.convertInfo[mode].inFormat = stream_.userFormat; + stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0]; + } + + if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump ) + stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump; + else + stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump; + + // Set up the interleave/deinterleave offsets. + if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) { + if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) || + ( mode == INPUT && stream_.userInterleaved ) ) { + for ( int k=0; k 0 ) { + if ( stream_.deviceInterleaved[mode] ) { + if ( mode == OUTPUT ) { + for ( int k=0; k> 8); + //out[info.outOffset[j]] >>= 8; + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i> 8); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT32) { + Int32 *in = (Int32 *)inBuffer; + for (unsigned int i=0; i> 16) & 0x0000ffff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i> 8) & 0x00ff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT24) { + Int24 *in = (Int24 *)inBuffer; + for (unsigned int i=0; i> 16); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_SINT32) { + Int32 *in = (Int32 *)inBuffer; + for (unsigned int i=0; i> 24) & 0x000000ff); + } + in += info.inJump; + out += info.outJump; + } + } + else if (info.inFormat == RTAUDIO_FLOAT32) { + Float32 *in = (Float32 *)inBuffer; + for (unsigned int i=0; i>8) | (x<<8); } +//static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } +//static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } + +void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) +{ + char val; + char *ptr; + + ptr = buffer; + if ( format == RTAUDIO_SINT16 ) { + for ( unsigned int i=0; i= 4 + #define RTAUDIO_DLL_PUBLIC __attribute__( (visibility( "default" )) ) + #else + #define RTAUDIO_DLL_PUBLIC + #endif +#endif + +#include +#include +#include +#include + +/*! \typedef typedef unsigned long RtAudioFormat; + \brief RtAudio data format type. + + Support for signed integers and floats. Audio data fed to/from an + RtAudio stream is assumed to ALWAYS be in host byte order. The + internal routines will automatically take care of any necessary + byte-swapping between the host format and the soundcard. Thus, + endian-ness is not a concern in the following format definitions. + + - \e RTAUDIO_SINT8: 8-bit signed integer. + - \e RTAUDIO_SINT16: 16-bit signed integer. + - \e RTAUDIO_SINT24: 24-bit signed integer. + - \e RTAUDIO_SINT32: 32-bit signed integer. + - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. + - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. +*/ +typedef unsigned long RtAudioFormat; +static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. +static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. +static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. +static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. + +/*! \typedef typedef unsigned long RtAudioStreamFlags; + \brief RtAudio stream option flags. + + The following flags can be OR'ed together to allow a client to + make changes to the default stream behavior: + + - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). + - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. + - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. + - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). + - \e RTAUDIO_JACK_DONT_CONNECT: Do not automatically connect ports (JACK only). + + By default, RtAudio streams pass and receive audio data from the + client in an interleaved format. By passing the + RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio + data will instead be presented in non-interleaved buffers. In + this case, each buffer argument in the RtAudioCallback function + will point to a single array of data, with \c nFrames samples for + each channel concatenated back-to-back. For example, the first + sample of data for the second channel would be located at index \c + nFrames (assuming the \c buffer pointer was recast to the correct + data type for the stream). + + Certain audio APIs offer a number of parameters that influence the + I/O latency of a stream. By default, RtAudio will attempt to set + these parameters internally for robust (glitch-free) performance + (though some APIs, like Windows DirectSound, make this difficult). + By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() + function, internal stream settings will be influenced in an attempt + to minimize stream latency, though possibly at the expense of stream + performance. + + If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to + open the input and/or output stream device(s) for exclusive use. + Note that this is not possible with all supported audio APIs. + + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + to select realtime scheduling (round-robin) for the callback thread. + + If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to + open the "default" PCM device when using the ALSA API. Note that this + will override any specified input or output device id. + + If the RTAUDIO_JACK_DONT_CONNECT flag is set, RtAudio will not attempt + to automatically connect the ports of the client to the audio device. +*/ +typedef unsigned int RtAudioStreamFlags; +static const RtAudioStreamFlags RTAUDIO_NONINTERLEAVED = 0x1; // Use non-interleaved buffers (default = interleaved). +static const RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY = 0x2; // Attempt to set stream parameters for lowest possible latency. +static const RtAudioStreamFlags RTAUDIO_HOG_DEVICE = 0x4; // Attempt grab device and prevent use by others. +static const RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME = 0x8; // Try to select realtime scheduling for callback thread. +static const RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT = 0x10; // Use the "default" PCM device (ALSA only). +static const RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT = 0x20; // Do not automatically connect ports (JACK only). + +/*! \typedef typedef unsigned long RtAudioStreamStatus; + \brief RtAudio stream status (over- or underflow) flags. + + Notification of a stream over- or underflow is indicated by a + non-zero stream \c status argument in the RtAudioCallback function. + The stream status can be one of the following two options, + depending on whether the stream is open for output and/or input: + + - \e RTAUDIO_INPUT_OVERFLOW: Input data was discarded because of an overflow condition at the driver. + - \e RTAUDIO_OUTPUT_UNDERFLOW: The output buffer ran low, likely producing a break in the output sound. +*/ +typedef unsigned int RtAudioStreamStatus; +static const RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. +static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. + +//! RtAudio callback function prototype. +/*! + All RtAudio clients must create a function of type RtAudioCallback + to read and/or write data from/to the audio stream. When the + underlying audio system is ready for new input or output data, this + function will be invoked. + + \param outputBuffer For output (or duplex) streams, the client + should write \c nFrames of audio sample frames into this + buffer. This argument should be recast to the datatype + specified when the stream was opened. For input-only + streams, this argument will be NULL. + + \param inputBuffer For input (or duplex) streams, this buffer will + hold \c nFrames of input audio sample frames. This + argument should be recast to the datatype specified when the + stream was opened. For output-only streams, this argument + will be NULL. + + \param nFrames The number of sample frames of input or output + data in the buffers. The actual buffer size in bytes is + dependent on the data type and number of channels in use. + + \param streamTime The number of seconds that have elapsed since the + stream was started. + + \param status If non-zero, this argument indicates a data overflow + or underflow condition for the stream. The particular + condition can be determined by comparison with the + RtAudioStreamStatus flags. + + \param userData A pointer to optional data provided by the client + when opening the stream (default = NULL). + + \return + To continue normal stream operation, the RtAudioCallback function + should return a value of zero. To stop the stream and drain the + output buffer, the function should return a value of one. To abort + the stream immediately, the client should return a value of two. + */ +typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, + unsigned int nFrames, + double streamTime, + RtAudioStreamStatus status, + void *userData ); + +/************************************************************************/ +/*! \class RtAudioError + \brief Exception handling class for RtAudio. + + The RtAudioError class is quite simple but it does allow errors to be + "caught" by RtAudioError::Type. See the RtAudio documentation to know + which methods can throw an RtAudioError. +*/ +/************************************************************************/ + +class RTAUDIO_DLL_PUBLIC RtAudioError : public std::runtime_error +{ + public: + //! Defined RtAudioError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtAudioError( const std::string& message, + Type type = RtAudioError::UNSPECIFIED ) + : std::runtime_error(message), type_(type) {} + + //! Prints thrown error message to stderr. + virtual void printMessage( void ) const + { std::cerr << '\n' << what() << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType(void) const { return type_; } + + //! Returns the thrown error message string. + virtual const std::string getMessage(void) const + { return std::string(what()); } + + protected: + Type type_; +}; + +//! RtAudio error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + */ +typedef void (*RtAudioErrorCallback)( RtAudioError::Type type, const std::string &errorText ); + +// **************************************************************** // +// +// RtAudio class declaration. +// +// RtAudio is a "controller" used to select an available audio i/o +// interface. It presents a common API for the user to call but all +// functionality is implemented by the class RtApi and its +// subclasses. RtAudio creates an instance of an RtApi subclass +// based on the user's API choice. If no choice is made, RtAudio +// attempts to make a "logical" API selection. +// +// **************************************************************** // + +class RtApi; + +class RTAUDIO_DLL_PUBLIC RtAudio +{ + public: + + //! Audio API specifier arguments. + enum Api { + UNSPECIFIED, /*!< Search for a working compiled API. */ + LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ + LINUX_PULSE, /*!< The Linux PulseAudio API. */ + LINUX_OSS, /*!< The Linux Open Sound System API. */ + UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ + MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ + WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ + WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ + WINDOWS_DS, /*!< The Microsoft DirectSound API. */ + RTAUDIO_DUMMY, /*!< A compilable but non-functional API. */ + NUM_APIS /*!< Number of values in this enum. */ + }; + + //! The public device information structure for returning queried values. + struct DeviceInfo { + bool probed; /*!< true if the device capabilities were successfully probed. */ + std::string name; /*!< Character string device identifier. */ + unsigned int outputChannels; /*!< Maximum output channels supported by device. */ + unsigned int inputChannels; /*!< Maximum input channels supported by device. */ + unsigned int duplexChannels; /*!< Maximum simultaneous input/output channels supported by device. */ + bool isDefaultOutput; /*!< true if this is the default output device. */ + bool isDefaultInput; /*!< true if this is the default input device. */ + std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ + unsigned int preferredSampleRate; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ + RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ + + // Default constructor. + DeviceInfo() + :probed(false), outputChannels(0), inputChannels(0), duplexChannels(0), + isDefaultOutput(false), isDefaultInput(false), preferredSampleRate(0), nativeFormats(0) {} + }; + + //! The structure for specifying input or ouput stream parameters. + struct StreamParameters { + unsigned int deviceId; /*!< Device index (0 to getDeviceCount() - 1). */ + unsigned int nChannels; /*!< Number of channels. */ + unsigned int firstChannel; /*!< First channel index on device (default = 0). */ + + // Default constructor. + StreamParameters() + : deviceId(0), nChannels(0), firstChannel(0) {} + }; + + //! The structure for specifying stream options. + /*! + The following flags can be OR'ed together to allow a client to + make changes to the default stream behavior: + + - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). + - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. + - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. + - \e RTAUDIO_SCHEDULE_REALTIME: Attempt to select realtime scheduling for callback thread. + - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). + + By default, RtAudio streams pass and receive audio data from the + client in an interleaved format. By passing the + RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio + data will instead be presented in non-interleaved buffers. In + this case, each buffer argument in the RtAudioCallback function + will point to a single array of data, with \c nFrames samples for + each channel concatenated back-to-back. For example, the first + sample of data for the second channel would be located at index \c + nFrames (assuming the \c buffer pointer was recast to the correct + data type for the stream). + + Certain audio APIs offer a number of parameters that influence the + I/O latency of a stream. By default, RtAudio will attempt to set + these parameters internally for robust (glitch-free) performance + (though some APIs, like Windows DirectSound, make this difficult). + By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() + function, internal stream settings will be influenced in an attempt + to minimize stream latency, though possibly at the expense of stream + performance. + + If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to + open the input and/or output stream device(s) for exclusive use. + Note that this is not possible with all supported audio APIs. + + If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt + to select realtime scheduling (round-robin) for the callback thread. + The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME + flag is set. It defines the thread's realtime priority. + + If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to + open the "default" PCM device when using the ALSA API. Note that this + will override any specified input or output device id. + + The \c numberOfBuffers parameter can be used to control stream + latency in the Windows DirectSound, Linux OSS, and Linux Alsa APIs + only. A value of two is usually the smallest allowed. Larger + numbers can potentially result in more robust stream performance, + though likely at the cost of stream latency. The value set by the + user is replaced during execution of the RtAudio::openStream() + function by the value actually used by the system. + + The \c streamName parameter can be used to set the client name + when using the Jack API. By default, the client name is set to + RtApiJack. However, if you wish to create multiple instances of + RtAudio with Jack, each instance must have a unique client name. + */ + struct StreamOptions { + RtAudioStreamFlags flags; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ + unsigned int numberOfBuffers; /*!< Number of stream buffers. */ + std::string streamName; /*!< A stream name (currently used only in Jack). */ + int priority; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ + + // Default constructor. + StreamOptions() + : flags(0), numberOfBuffers(0), priority(0) {} + }; + + //! A static function to determine the current RtAudio version. + static std::string getVersion( void ); + + //! A static function to determine the available compiled audio APIs. + /*! + The values returned in the std::vector can be compared against + the enumerated list values. Note that there can be more than one + API compiled for certain operating systems. + */ + static void getCompiledApi( std::vector &apis ); + + //! Return the name of a specified compiled audio API. + /*! + This obtains a short lower-case name used for identification purposes. + This value is guaranteed to remain identical across library versions. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiName( RtAudio::Api api ); + + //! Return the display name of a specified compiled audio API. + /*! + This obtains a long name used for display purposes. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiDisplayName( RtAudio::Api api ); + + //! Return the compiled audio API having the given name. + /*! + A case insensitive comparison will check the specified name + against the list of compiled APIs, and return the one which + matches. On failure, the function returns UNSPECIFIED. + */ + static RtAudio::Api getCompiledApiByName( const std::string &name ); + + //! The class constructor. + /*! + The constructor performs minor initialization tasks. An exception + can be thrown if no API support is compiled. + + If no API argument is specified and multiple API support has been + compiled, the default order of use is JACK, ALSA, OSS (Linux + systems) and ASIO, DS (Windows systems). + */ + RtAudio( RtAudio::Api api=UNSPECIFIED ); + + //! The destructor. + /*! + If a stream is running or open, it will be stopped and closed + automatically. + */ + ~RtAudio(); + + //! Returns the audio API specifier for the current instance of RtAudio. + RtAudio::Api getCurrentApi( void ); + + //! A public function that queries for the number of audio devices available. + /*! + This function performs a system query of available devices each time it + is called, thus supporting devices connected \e after instantiation. If + a system error occurs during processing, a warning will be issued. + */ + unsigned int getDeviceCount( void ); + + //! Return an RtAudio::DeviceInfo structure for a specified device number. + /*! + + Any device integer between 0 and getDeviceCount() - 1 is valid. + If an invalid argument is provided, an RtAudioError (type = INVALID_USE) + will be thrown. If a device is busy or otherwise unavailable, the + structure member "probed" will have a value of "false" and all + other members are undefined. If the specified device is the + current default input or output device, the corresponding + "isDefault" member will have a value of "true". + */ + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + + //! A function that returns the index of the default output device. + /*! + If the underlying audio API does not provide a "default + device", or if no devices are available, the return value will be + 0. Note that this is a valid device identifier and it is the + client's responsibility to verify that a device is available + before attempting to open a stream. + */ + unsigned int getDefaultOutputDevice( void ); + + //! A function that returns the index of the default input device. + /*! + If the underlying audio API does not provide a "default + device", or if no devices are available, the return value will be + 0. Note that this is a valid device identifier and it is the + client's responsibility to verify that a device is available + before attempting to open a stream. + */ + unsigned int getDefaultInputDevice( void ); + + //! A public function for opening a stream with the specified parameters. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if a stream cannot be + opened with the specified parameters or an error occurs during + processing. An RtAudioError (type = INVALID_USE) is thrown if any + invalid device ID or channel number parameters are specified. + + \param outputParameters Specifies output stream parameters to use + when opening a stream, including a device ID, number of channels, + and starting channel number. For input-only streams, this + argument should be NULL. The device ID is an index value between + 0 and getDeviceCount() - 1. + \param inputParameters Specifies input stream parameters to use + when opening a stream, including a device ID, number of channels, + and starting channel number. For output-only streams, this + argument should be NULL. The device ID is an index value between + 0 and getDeviceCount() - 1. + \param format An RtAudioFormat specifying the desired sample data format. + \param sampleRate The desired sample rate (sample frames per second). + \param *bufferFrames A pointer to a value indicating the desired + internal buffer size in sample frames. The actual value + used by the device is returned via the same pointer. A + value of zero can be specified, in which case the lowest + allowable value is determined. + \param callback A client-defined function that will be invoked + when input data is available and/or output data is needed. + \param userData An optional pointer to data that can be accessed + from within the callback function. + \param options An optional pointer to a structure containing various + global stream options, including a list of OR'ed RtAudioStreamFlags + and a suggested number of stream buffers that can be used to + control stream latency. More buffers typically result in more + robust performance, though at a cost of greater latency. If a + value of zero is specified, a system-specific median value is + chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the + lowest allowable value is used. The actual value used is + returned via the structure argument. The parameter is API dependent. + \param errorCallback A client-defined function that will be invoked + when an error has occured. + */ + void openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData = NULL, RtAudio::StreamOptions *options = NULL, RtAudioErrorCallback errorCallback = NULL ); + + //! A function that closes a stream and frees any associated stream memory. + /*! + If a stream is not open, this function issues a warning and + returns (no exception is thrown). + */ + void closeStream( void ); + + //! A function that starts a stream. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + running. + */ + void startStream( void ); + + //! Stop a stream, allowing any samples remaining in the output queue to be played. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + stopped. + */ + void stopStream( void ); + + //! Stop a stream, discarding any samples remaining in the input/output queue. + /*! + An RtAudioError (type = SYSTEM_ERROR) is thrown if an error occurs + during processing. An RtAudioError (type = INVALID_USE) is thrown if a + stream is not open. A warning is issued if the stream is already + stopped. + */ + void abortStream( void ); + + //! Returns true if a stream is open and false if not. + bool isStreamOpen( void ) const; + + //! Returns true if the stream is running and false if it is stopped or not open. + bool isStreamRunning( void ) const; + + //! Returns the number of elapsed seconds since the stream was started. + /*! + If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + double getStreamTime( void ); + + //! Set the stream time to a time in seconds greater than or equal to 0.0. + /*! + If a stream is not open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + void setStreamTime( double time ); + + //! Returns the internal stream latency in sample frames. + /*! + The stream latency refers to delay in audio input and/or output + caused by internal buffering by the audio system and/or hardware. + For duplex streams, the returned value will represent the sum of + the input and output latencies. If a stream is not open, an + RtAudioError (type = INVALID_USE) will be thrown. If the API does not + report latency, the return value will be zero. + */ + long getStreamLatency( void ); + + //! Returns actual sample rate in use by the stream. + /*! + On some systems, the sample rate used may be slightly different + than that specified in the stream parameters. If a stream is not + open, an RtAudioError (type = INVALID_USE) will be thrown. + */ + unsigned int getStreamSampleRate( void ); + + //! Specify whether warning messages should be printed to stderr. + void showWarnings( bool value = true ); + + protected: + + void openRtApi( RtAudio::Api api ); + RtApi *rtapi_; +}; + +// Operating system dependent thread functionality. +#if defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) + + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include + + typedef uintptr_t ThreadHandle; + typedef CRITICAL_SECTION StreamMutex; + +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) + // Using pthread library for various flavors of unix. + #include + + typedef pthread_t ThreadHandle; + typedef pthread_mutex_t StreamMutex; + +#else // Setup for "dummy" behavior + + #define __RTAUDIO_DUMMY__ + typedef int ThreadHandle; + typedef int StreamMutex; + +#endif + +// This global structure type is used to pass callback information +// between the private RtAudio stream structure and global callback +// handling functions. +struct CallbackInfo { + void *object; // Used as a "this" pointer. + ThreadHandle thread; + void *callback; + void *userData; + void *errorCallback; + void *apiInfo; // void pointer for API specific callback information + bool isRunning; + bool doRealtime; + int priority; + + // Default constructor. + CallbackInfo() + :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false), priority(0) {} +}; + +// **************************************************************** // +// +// RtApi class declaration. +// +// Subclasses of RtApi contain all API- and OS-specific code necessary +// to fully implement the RtAudio API. +// +// Note that RtApi is an abstract base class and cannot be +// explicitly instantiated. The class RtAudio will create an +// instance of an RtApi subclass (RtApiOss, RtApiAlsa, +// RtApiJack, RtApiCore, RtApiDs, or RtApiAsio). +// +// **************************************************************** // + +#pragma pack(push, 1) +class S24 { + + protected: + unsigned char c3[3]; + + public: + S24() {} + + S24& operator = ( const int& i ) { + c3[0] = (i & 0x000000ff); + c3[1] = (i & 0x0000ff00) >> 8; + c3[2] = (i & 0x00ff0000) >> 16; + return *this; + } + + S24( const double& d ) { *this = (int) d; } + S24( const float& f ) { *this = (int) f; } + S24( const signed short& s ) { *this = (int) s; } + S24( const char& c ) { *this = (int) c; } + + int asInt() { + int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); + if (i & 0x800000) i |= ~0xffffff; + return i; + } +}; +#pragma pack(pop) + +#if defined( HAVE_GETTIMEOFDAY ) + #include +#endif + +#include + +class RTAUDIO_DLL_PUBLIC RtApi +{ +public: + + RtApi(); + virtual ~RtApi(); + virtual RtAudio::Api getCurrentApi( void ) = 0; + virtual unsigned int getDeviceCount( void ) = 0; + virtual RtAudio::DeviceInfo getDeviceInfo( unsigned int device ) = 0; + virtual unsigned int getDefaultInputDevice( void ); + virtual unsigned int getDefaultOutputDevice( void ); + void openStream( RtAudio::StreamParameters *outputParameters, + RtAudio::StreamParameters *inputParameters, + RtAudioFormat format, unsigned int sampleRate, + unsigned int *bufferFrames, RtAudioCallback callback, + void *userData, RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ); + virtual void closeStream( void ); + virtual void startStream( void ) = 0; + virtual void stopStream( void ) = 0; + virtual void abortStream( void ) = 0; + long getStreamLatency( void ); + unsigned int getStreamSampleRate( void ); + virtual double getStreamTime( void ); + virtual void setStreamTime( double time ); + bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } + bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } + void showWarnings( bool value ) { showWarnings_ = value; } + + +protected: + + static const unsigned int MAX_SAMPLE_RATES; + static const unsigned int SAMPLE_RATES[]; + + enum { FAILURE, SUCCESS }; + + enum StreamState { + STREAM_STOPPED, + STREAM_STOPPING, + STREAM_RUNNING, + STREAM_CLOSED = -50 + }; + + enum StreamMode { + OUTPUT, + INPUT, + DUPLEX, + UNINITIALIZED = -75 + }; + + // A protected structure used for buffer conversion. + struct ConvertInfo { + int channels; + int inJump, outJump; + RtAudioFormat inFormat, outFormat; + std::vector inOffset; + std::vector outOffset; + }; + + // A protected structure for audio streams. + struct RtApiStream { + unsigned int device[2]; // Playback and record, respectively. + void *apiHandle; // void pointer for API specific stream handle information + StreamMode mode; // OUTPUT, INPUT, or DUPLEX. + StreamState state; // STOPPED, RUNNING, or CLOSED + char *userBuffer[2]; // Playback and record, respectively. + char *deviceBuffer; + bool doConvertBuffer[2]; // Playback and record, respectively. + bool userInterleaved; + bool deviceInterleaved[2]; // Playback and record, respectively. + bool doByteSwap[2]; // Playback and record, respectively. + unsigned int sampleRate; + unsigned int bufferSize; + unsigned int nBuffers; + unsigned int nUserChannels[2]; // Playback and record, respectively. + unsigned int nDeviceChannels[2]; // Playback and record channels, respectively. + unsigned int channelOffset[2]; // Playback and record, respectively. + unsigned long latency[2]; // Playback and record, respectively. + RtAudioFormat userFormat; + RtAudioFormat deviceFormat[2]; // Playback and record, respectively. + StreamMutex mutex; + CallbackInfo callbackInfo; + ConvertInfo convertInfo[2]; + double streamTime; // Number of elapsed seconds since the stream started. + +#if defined(HAVE_GETTIMEOFDAY) + struct timeval lastTickTimestamp; +#endif + + RtApiStream() + :apiHandle(0), deviceBuffer(0) { device[0] = 11111; device[1] = 11111; } + }; + + typedef S24 Int24; + typedef signed short Int16; + typedef signed int Int32; + typedef float Float32; + typedef double Float64; + + std::ostringstream errorStream_; + std::string errorText_; + bool showWarnings_; + RtApiStream stream_; + bool firstErrorOccurred_; + + /*! + Protected, api-specific method that attempts to open a device + with the given parameters. This function MUST be implemented by + all subclasses. If an error is encountered during the probe, a + "warning" message is reported and FAILURE is returned. A + successful probe is indicated by a return value of SUCCESS. + */ + virtual bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); + + //! A protected function used to increment the stream time. + void tickStreamTime( void ); + + //! Protected common method to clear an RtApiStream structure. + void clearStreamInfo(); + + /*! + Protected common method that throws an RtAudioError (type = + INVALID_USE) if a stream is not open. + */ + void verifyStream( void ); + + //! Protected common error method to allow global control over error handling. + void error( RtAudioError::Type type ); + + /*! + Protected method used to perform format, channel number, and/or interleaving + conversions between the user and device buffers. + */ + void convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info ); + + //! Protected common method used to perform byte-swapping on buffers. + void byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ); + + //! Protected common method that returns the number of bytes for a given format. + unsigned int formatBytes( RtAudioFormat format ); + + //! Protected common method that sets up the parameters for buffer conversion. + void setConvertInfo( StreamMode mode, unsigned int firstChannel ); +}; + +// **************************************************************** // +// +// Inline RtAudio definitions. +// +// **************************************************************** // + +inline RtAudio::Api RtAudio :: getCurrentApi( void ) { return rtapi_->getCurrentApi(); } +inline unsigned int RtAudio :: getDeviceCount( void ) { return rtapi_->getDeviceCount(); } +inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int device ) { return rtapi_->getDeviceInfo( device ); } +inline unsigned int RtAudio :: getDefaultInputDevice( void ) { return rtapi_->getDefaultInputDevice(); } +inline unsigned int RtAudio :: getDefaultOutputDevice( void ) { return rtapi_->getDefaultOutputDevice(); } +inline void RtAudio :: closeStream( void ) { return rtapi_->closeStream(); } +inline void RtAudio :: startStream( void ) { return rtapi_->startStream(); } +inline void RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } +inline void RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } +inline bool RtAudio :: isStreamOpen( void ) const { return rtapi_->isStreamOpen(); } +inline bool RtAudio :: isStreamRunning( void ) const { return rtapi_->isStreamRunning(); } +inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } +inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } +inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } +inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } +inline void RtAudio :: showWarnings( bool value ) { rtapi_->showWarnings( value ); } + +// RtApi Subclass prototypes. + +#if defined(__MACOSX_CORE__) + +#include + +class RtApiCore: public RtApi +{ +public: + + RtApiCore(); + ~RtApiCore(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::MACOSX_CORE; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( AudioDeviceID deviceId, + const AudioBufferList *inBufferList, + const AudioBufferList *outBufferList ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); + static const char* getErrorCode( OSStatus code ); +}; + +#endif + +#if defined(__UNIX_JACK__) + +class RtApiJack: public RtApi +{ +public: + + RtApiJack(); + ~RtApiJack(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::UNIX_JACK; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( unsigned long nframes ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); + + bool shouldAutoconnect_; +}; + +#endif + +#if defined(__WINDOWS_ASIO__) + +class RtApiAsio: public RtApi +{ +public: + + RtApiAsio(); + ~RtApiAsio(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_ASIO; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + bool callbackEvent( long bufferIndex ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool coInitialized_; + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_DS__) + +class RtApiDs: public RtApi +{ +public: + + RtApiDs(); + ~RtApiDs(); + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_DS; } + unsigned int getDeviceCount( void ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + bool coInitialized_; + bool buffersRolling; + long duplexPrerollBytes; + std::vector dsDevices; + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__WINDOWS_WASAPI__) + +struct IMMDeviceEnumerator; + +class RtApiWasapi : public RtApi +{ +public: + RtApiWasapi(); + virtual ~RtApiWasapi(); + + RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + unsigned int getDefaultOutputDevice( void ); + unsigned int getDefaultInputDevice( void ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + +private: + bool coInitialized_; + IMMDeviceEnumerator* deviceEnumerator_; + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int* bufferSize, + RtAudio::StreamOptions* options ); + + static DWORD WINAPI runWasapiThread( void* wasapiPtr ); + static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); + static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); + void wasapiThread(); +}; + +#endif + +#if defined(__LINUX_ALSA__) + +class RtApiAlsa: public RtApi +{ +public: + + RtApiAlsa(); + ~RtApiAlsa(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_ALSA; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__LINUX_PULSE__) + +class RtApiPulse: public RtApi +{ +public: + ~RtApiPulse(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_PULSE; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + std::vector devices_; + void saveDeviceInfo( void ); + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__LINUX_OSS__) + +class RtApiOss: public RtApi +{ +public: + + RtApiOss(); + ~RtApiOss(); + RtAudio::Api getCurrentApi() { return RtAudio::LINUX_OSS; } + unsigned int getDeviceCount( void ); + RtAudio::DeviceInfo getDeviceInfo( unsigned int device ); + void closeStream( void ); + void startStream( void ); + void stopStream( void ); + void abortStream( void ); + + // This function is intended for internal use only. It must be + // public because it is called by the internal callback handler, + // which is not a member of RtAudio. External use of this function + // will most likely produce highly undesireable results! + void callbackEvent( void ); + + private: + + bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +}; + +#endif + +#if defined(__RTAUDIO_DUMMY__) + +class RtApiDummy: public RtApi +{ +public: + + RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RtAudioError::WARNING ); } + RtAudio::Api getCurrentApi( void ) { return RtAudio::RTAUDIO_DUMMY; } + unsigned int getDeviceCount( void ) { return 0; } + RtAudio::DeviceInfo getDeviceInfo( unsigned int /*device*/ ) { RtAudio::DeviceInfo info; return info; } + void closeStream( void ) {} + void startStream( void ) {} + void stopStream( void ) {} + void abortStream( void ) {} + + private: + + bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) { return false; } +}; + +#endif + +#endif + +// Indentation settings for Vim and Emacs +// +// Local Variables: +// c-basic-offset: 2 +// indent-tabs-mode: nil +// End: +// +// vim: et sts=2 sw=2 diff --git a/src/rtaudio/RtAudio.os b/src/rtaudio/RtAudio.os new file mode 100644 index 0000000000000000000000000000000000000000..82494c53fc326b37589644c4dab3e3e13e4cdc6d GIT binary patch literal 1493776 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

inVbEam*c?`-8-sh4w< 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%0AKv>!wA8U_{t{*`-| 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

!CN-s*2=^|6&9?4Ysj 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
+2tpD$QjQ;$9d;R-# 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%0D0YYjDr=8Qk-1 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

i#SIwLJ_v2_J0_A@R^_dq{H)dTkGPx!|hp;q5s1 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