From ebf19f512d9c9e10cf9e26b0bca8eec80ca83010 Mon Sep 17 00:00:00 2001 From: =?utf8?q?IOhannes=20m=20zm=C3=B6lnig=20=28Debian/GNU=29?= Date: Wed, 6 Mar 2019 22:08:53 +0000 Subject: [PATCH] Import giada_0.15.2+ds1-2.debian.tar.xz [dgit import tarball giada 0.15.2+ds1-2 giada_0.15.2+ds1-2.debian.tar.xz] --- README.source | 38 + changelog | 319 + compat | 1 + control | 47 + copyright | 68 + gbp.conf | 5 + gbp/postclone.sh | 14 + giada.1 | 34 + giada.desktop | 14 + giada.fr.1 | 42 + giada.manpages | 2 + giada.svg | 35 + install | 2 + patches/01-rtaudio5.patch | 20963 ++++++++++++++++++++++++++++ patches/02-rtmidi-pkgconfig.patch | 46 + patches/series | 2 + rules | 62 + source/format | 1 + watch | 3 + 19 files changed, 21698 insertions(+) create mode 100644 README.source create mode 100644 changelog create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 gbp.conf create mode 100755 gbp/postclone.sh create mode 100644 giada.1 create mode 100644 giada.desktop create mode 100644 giada.fr.1 create mode 100644 giada.manpages create mode 100644 giada.svg create mode 100644 install create mode 100644 patches/01-rtaudio5.patch create mode 100644 patches/02-rtmidi-pkgconfig.patch create mode 100644 patches/series create mode 100755 rules create mode 100644 source/format create mode 100644 watch diff --git a/README.source b/README.source new file mode 100644 index 0000000..ddefb37 --- /dev/null +++ b/README.source @@ -0,0 +1,38 @@ +repacking upstream sources +-------------------------- + +The originall tarball has been repacked to remove files already in Debian. + +Upstream also bundles the 'JUCE' framework which is available as a Debian +package. + +Upstream also bundles a slightly modified version of RtAudio. +Since upstream relies on their modifications, we are using the bundled version +(rather than the system provided package!) + +Files stripped away are enumerated in the Files-Excluded stanza in +debian/copyright. + + +gbp clone +--------- + +Starting with gbp>0.8.1, here's an simple way to automatically fine-tune the +repository in the following ways: +- make git ignore any .pc/ directory (created by quilt) +- enable the "--follow-tags" when running 'git-push', so it's harder + to forget to push packaging tags along with the branches. + +To enable this, run gbp-clone with the '--postclone debian/gbp/postclone.sh' +option. +To enable this for ALL repositories cloned via 'gbp' (in the future), do +something like the following: + + $ mkdir -p ~/bin + $ cp debian/gbp/postclone.sh ~/bin/gbphook-postclone + $ cat >> ~/.gbp.conf < Mon, 1 Aug 2016 12:15:50 +0200 diff --git a/changelog b/changelog new file mode 100644 index 0000000..be79b74 --- /dev/null +++ b/changelog @@ -0,0 +1,319 @@ +giada (0.15.2+ds1-2) unstable; urgency=medium + + * B-D and link against libcurl (Closes: #923898) + + -- IOhannes m zmölnig (Debian/GNU) Wed, 06 Mar 2019 23:08:53 +0100 + +giada (0.15.2+ds1-1) unstable; urgency=medium + + * New upstream version 0.15.2+ds1 + * Refreshed patches + * Bumped standards version to 4.2.1 + + -- IOhannes m zmölnig (Debian/GNU) Sat, 08 Sep 2018 18:22:39 +0200 + +giada (0.15.1+ds1-1) unstable; urgency=medium + + * New upstream version 0.15.1+ds1 + + [ Olivier Humbert ] + * debian/control : + - Build-Depends: alphabetical order + one package per line + - long description (lines were too short) + * debian/copyright : + - adding myself + * debian/giada.desktop : + - removes [es] name (no need since it's not translated) + - adds Comment for EN and ES (then they get displayed when mouseovering the + menu item) + - adds French Generic Name and Comment + * debian/giada.1 : + - suppresses a duplicate (.TH) + * debian/giada.fr.1 : + - adds a French manpage (translated from the English one) + * debian/giada.manpages : + - install the French manpage + + [ IOhannes m zmölnig ] + * Refreshed patches + * Switched to +ds suffix (more widespread than +repack) + * debian/control : + - Set Rules-Requires-Root to no + - Bumped standards version to 4.1.5 + + -- IOhannes m zmölnig (Debian/GNU) Wed, 04 Jul 2018 22:23:58 +0200 + +giada (0.15.0+repack1-2) unstable; urgency=medium + + * Dropped versioned depends on alsa/sndfile/samplerate + * Added libatomic to riscv64 builds + * Install updstream changelog + + * Upload to unstable. + + -- IOhannes m zmölnig (Debian/GNU) Thu, 24 May 2018 10:20:47 +0200 + +giada (0.15.0+repack1-2~exp2) experimental; urgency=medium + + * Pass "-latomic" via the LIBS + + -- IOhannes m zmölnig (Debian/GNU) Wed, 23 May 2018 19:56:50 +0200 + +giada (0.15.0+repack1-2~exp1) experimental; urgency=medium + + * Link against libatomic (on platforms that require it) + + -- IOhannes m zmölnig (Debian/GNU) Wed, 23 May 2018 16:36:00 +0200 + +giada (0.15.0+repack1-1) unstable; urgency=medium + + * New upstream version 0.15.0+repack1 + + * Enabled VST support + * Build with JUCE + * Repacked sources + * Exclude all travis-ci related stuff + * Don't exclude VST files (dropped upstream) + * Updated d/watch to new repacksuffix "+repack" + * Bumped standards version to 4.1.4 + + -- IOhannes m zmölnig (Debian/GNU) Tue, 22 May 2018 21:10:08 +0200 + +giada (0.14.6~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.14.6~dfsg1 + + [ Jaromír MikeÅ¡ ] + * Patch forwarded. + + [ Ondřej Nový ] + * d/control: Set Vcs-* to salsa.debian.org + + [ IOhannes m zmölnig ] + * Refreshed patches + * Run unit-tests in fake-X environment + * Updated maintainer email + + -- IOhannes m zmölnig (Debian/GNU) Wed, 04 Apr 2018 21:39:36 +0200 + +giada (0.14.5~dfsg1-2) unstable; urgency=medium + + * Add patch to fix build on some archs. + + -- Jaromír MikeÅ¡ Wed, 17 Jan 2018 18:45:49 +0100 + +giada (0.14.5~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.14.5~dfsg1 + * Patches removed/updated. + * Set dh/compat 11. + * Bump Standards. + + -- Jaromír MikeÅ¡ Tue, 16 Jan 2018 23:19:27 +0100 + +giada (0.14.4~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.14.4~dfsg1 + * Patch refreshed. + * Vcs - use git instead of cgit. + * Remove trailing-whitespaces. + + -- Jaromír MikeÅ¡ Sat, 25 Nov 2017 16:35:14 +0100 + +giada (0.14.3~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.14.3~dfsg1 + + [ Jaromír MikeÅ¡ ] + * Introduce postclone.sh script to ignore .pc/ dir. + * Patches refreshed. + + [ IOhannes m zmölnig ] + * Updated patches + * Updated patch headers for use with gbp-pq + * Refreshed patches + * Updated bundled RtAudio to RtAudio5 + * Added patch to fix compilation with g++-7 (Closes: #853418) + * Added missing files to catch-patch + * Dropped unneeded B-D on autotools-dev + * Dropped override_dh_autoconf + * Calculated BUILD_DATE based on SOURCE_DATE_EPOCH + * Switched to https:// where possible + * Fixed typo in d/README.source + * Bumped standards version to 4.1.1 + + -- IOhannes m zmölnig (Debian/GNU) Mon, 30 Oct 2017 10:08:45 +0100 + +giada (0.14.1~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.14.1~dfsg1 + * Patch refreshed. + + -- Jaromír MikeÅ¡ Mon, 24 Jul 2017 01:10:36 +0200 + +giada (0.14.0~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.14.0~dfsg1 + * Patch forwarded. + * Bump Standards. + * Add catch as B-D. + * Add patch to test with system-wide catch. + * Update copyright file. + * Remove not-needed override file. + + -- Jaromír MikeÅ¡ Mon, 26 Jun 2017 11:43:49 +0200 + +giada (0.13.2~dfsg1-1) unstable; urgency=medium + + * Remove more files on repack. + * New upstream version 0.13.2~dfsg1 + * Patches deleted/refreshed. + * Clean up rules file. + * Remove B-D dh_autoreconf. + * Update copyright file. + * Sign tags. + * Install only svg icon. + + -- Jaromír MikeÅ¡ Sun, 15 Jan 2017 18:51:32 +0100 + +giada (0.13.1~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.13.1~dfsg1 + * Patch refreshed. + * Set dh/compat 10. + * Add patch to fix FTBFS. + + -- Jaromír MikeÅ¡ Mon, 21 Nov 2016 09:32:55 +0100 + +giada (0.13.0~dfsg1-1) unstable; urgency=medium + + * New upstream version 0.13.0~dfsg1 + * Refresh patch. + * Drop patches applied upstream. + + -- Jaromír MikeÅ¡ Fri, 02 Sep 2016 01:35:02 +0200 + +giada (0.12.2~dfsg1-3) unstable; urgency=medium + + * Add patch to build with gcc6 (Closes: ##831183). + * Numbering patches. + * Tune .gitignore file. + + -- Jaromír MikeÅ¡ Mon, 25 Jul 2016 01:09:36 +0200 + +giada (0.12.2~dfsg1-2) unstable; urgency=medium + + * Add patch to build with new rtmidi lib. (Closes: #828955) + Thanks to James Cowgill + + -- Jaromír MikeÅ¡ Mon, 04 Jul 2016 16:03:13 +0200 + +giada (0.12.2~dfsg1-1) unstable; urgency=medium + + * Imported Upstream version 0.12.2~dfsg1 + * Patch refreshed. + * Update copyright file. + * Fix hardening. + * Add overrides file. + + -- Jaromír MikeÅ¡ Wed, 22 Jun 2016 14:41:40 +0200 + +giada (0.12.1~dfsg1-1) unstable; urgency=medium + + * Imported Upstream version 0.12.1~dfsg1 + * Dropped embedded copy of non-free ASIO SDK in rtaudio-mod + that had accidentally crept in with 0.11.0 (Closes: #823318) + * Dropped patches applied upstream. + * Dropped references to JUCE + * Bumped to standards version 3.9.8 + + -- IOhannes m zmölnig (Debian/GNU) Tue, 17 May 2016 15:54:31 +0200 + +giada (0.11.2~dfsg-1) unstable; urgency=medium + + * Imported Upstream version 0.11.2~dfsg + + * File-Exclude bundled libraries + * Explain repacking in detail + * Simplified get-orig-source rule + * Added repack-suffix to debian/watch + * Refreshed patches + * Skip tests that require missing files + * Clean up in rtaudio-mod directory + * Use https:// in Vcs-Git stanza + * Bumped standards to 3.9.7 + + -- IOhannes m zmölnig (Debian/GNU) Tue, 16 Feb 2016 15:39:16 +0100 + +giada (0.11.0~dfsg1-1) unstable; urgency=medium + + * Imported Upstream version 0.11.0~dfsg1 + * Patch applied upstream. + * Add libjansson-dev as build dep. + + -- Jaromír MikeÅ¡ Wed, 02 Dec 2015 09:45:41 +0100 + +giada (0.10.2~dfsg1-2) unstable; urgency=medium + + * unit-tests + * Made tests failing more verbosely + * Set ${HOME} to /tmp + * debian/README.source + * Documented git-tuneclone.sh + * Fixed typos + + -- IOhannes m zmölnig (Debian/GNU) Mon, 23 Nov 2015 00:15:05 +0100 + +giada (0.10.2~dfsg1-1) unstable; urgency=medium + + [ Jaromír MikeÅ¡ ] + * Imported Upstream version 0.10.2~dfsg1 + + [ IOhannes m zmölnig ] + * Enable C++11 to prevent FTBFS in tests + * Made build reproducible + * Patch to allow setting of DATE in about-dialog + * Set BUILD_DATE to date from debian/changelog + * Removed giada.menu + (in response to the tech-ctte decision on #741573) + * Updated debian/copyright + * Updated Vcs-Browser stanza + * Added debian/git-tuneclone.sh script + + -- IOhannes m zmölnig (Debian/GNU) Fri, 06 Nov 2015 22:28:02 +0100 + +giada (0.10.1~dfsg1-1) unstable; urgency=medium + + * Imported Upstream version 0.10.1~dfsg1 + * Remove patch - apllied upstream. + * Refresh patch. + * Upstream build changed - tune rules file accordingly. + * Update copyright file. + + -- Jaromír MikeÅ¡ Thu, 03 Sep 2015 03:43:59 +0200 + +giada (0.10.0~dfsg1-1) unstable; urgency=medium + + * Imported Upstream version 0.10.0~dfsg1 + * Patches refreshed. + * Added spelling patch. + + -- Jaromír MikeÅ¡ Tue, 07 Jul 2015 14:40:16 +0200 + +giada (0.9.6~dfsg1-3) unstable; urgency=medium + + * Fixed FTBFS due to parenthesization (Closes: #791544) + + -- IOhannes m zmölnig (Debian/GNU) Tue, 07 Jul 2015 11:20:10 +0200 + +giada (0.9.6~dfsg1-2) unstable; urgency=medium + + * Attempt to fix build on arm64 and ppc64el. + + -- Jaromír MikeÅ¡ Thu, 18 Jun 2015 09:58:19 +0200 + +giada (0.9.6~dfsg1-1) unstable; urgency=medium + + * Initial release. (Closes: Bug#786610) + + -- Jaromír MikeÅ¡ Wed, 08 Apr 2015 14:59:44 +0200 diff --git a/compat b/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +11 diff --git a/control b/control new file mode 100644 index 0000000..51865ed --- /dev/null +++ b/control @@ -0,0 +1,47 @@ +Source: giada +Section: sound +Priority: optional +Maintainer: Debian Multimedia Maintainers +Uploaders: + Jaromír MikeÅ¡ , + IOhannes m zmölnig (Debian/GNU) , +Build-Depends: + debhelper (>= 11), + catch, + juce-modules-source, + libasound2, + libfltk1.3-dev, + libcurl4-gnutls-dev | libcurl-dev, + libjack-dev, + libjansson-dev, + libpulse-dev, + librtmidi-dev (>= 2.1.0~ds0), + libsamplerate0-dev, + libsndfile1-dev, + libxext-dev, + libxft2-dev, + libxpm-dev, + xauth, + xvfb, +Standards-Version: 4.2.1 +Rules-Requires-Root: no +Vcs-Git: https://salsa.debian.org/multimedia-team/giada.git +Vcs-Browser: https://salsa.debian.org/multimedia-team/giada +Homepage: https://www.giadamusic.com + +Package: giada +Architecture: any +Built-Using: ${juce:BuiltUsing}, +Depends: + ${shlibs:Depends}, + ${misc:Depends}, +Recommends: + jackd, +Description: Hardcore Loop Machine + free, minimal, hardcore audio tool for DJs, live performers and electronic + musicians. Pick up your channel, fill it with samples or MIDI events and start + the show by using this tiny piece of software as a loop machine, drum machine, + sequencer, live sampler or yet as a plugin/effect host. + . + Giada aims to be a compact and portable virtual device for production use and + live sets. diff --git a/copyright b/copyright new file mode 100644 index 0000000..742c90b --- /dev/null +++ b/copyright @@ -0,0 +1,68 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Giada +Upstream-Contact: Giovanni A. Zuliani | Monocasual +Source: https://www.giadamusic.com +Files-Excluded: + src/deps/juce/* + src/deps/rtaudio-mod/include/* + .git* + .travis* + +Files: * +Copyright: + 2010-2015 Giovanni A. Zuliani | Monocasual +License: GPL-3+ + +Files: debian/* +Copyright: + 2018 Olivier Humbert + 2015-2017 Jaromír MikeÅ¡ + 2015-2016 IOhannes m zmölnig +License: GPL-3+ + +Files: src/deps/rtaudio-mod/RtAudio.h + src/deps/rtaudio-mod/RtAudio.cpp +Copyright: + 2001-2014 Gary P. Scavone +License: MIT + +License: GPL-3+ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +Comment: You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL-3'. + +License: MIT + 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. diff --git a/gbp.conf b/gbp.conf new file mode 100644 index 0000000..4a6c371 --- /dev/null +++ b/gbp.conf @@ -0,0 +1,5 @@ +[DEFAULT] +pristine-tar = True +sign-tags = True + +debian-branch = master diff --git a/gbp/postclone.sh b/gbp/postclone.sh new file mode 100755 index 0000000..5790ec6 --- /dev/null +++ b/gbp/postclone.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +## script to initialize a cloned repository +## with per (local) repository settings. + +# - ignore quilt's .pc/ directory +# - enable the "--follow-tags" mode for pushing + +echo "tuning git-repository for ${NAME}" +git config push.followTags true && echo "enabled push.followTags" + +GITEXCLUDE=".git/info/exclude" +egrep "^/?\.pc/?$" "${GITEXCLUDE}" >/dev/null 2>&1 \ + || (echo "/.pc/" >> "${GITEXCLUDE}" && echo "ignoring /.pc/") diff --git a/giada.1 b/giada.1 new file mode 100644 index 0000000..42f3a58 --- /dev/null +++ b/giada.1 @@ -0,0 +1,34 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH GIADA 1 "May 23, 2015" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.IX Title "GIADA 1" +.SH "NAME" +giada \- Hardcore Loop Machine. +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +giada +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +Free, minimal, hardcore audio tool for DJs, live performers and electronic musicians. +Pick up your channel, fill it with samples or MIDI events and start the show by using +this tiny piece of software as a loop machine, drum machine, sequencer, +live sampler or yet as a plugin/effect host. +.LP +Giada aims to be a compact and portable virtual device for production use and live sets. +.SH AUTHOR +This manual page was written by Jaromír MikeÅ¡ , +for the Debian project (but may be used by others). diff --git a/giada.desktop b/giada.desktop new file mode 100644 index 0000000..3fb8361 --- /dev/null +++ b/giada.desktop @@ -0,0 +1,14 @@ +[Desktop Entry] +Name=Giada +GenericName=Drum machine and loop sequencer +GenericName[es]=Caja de ritmos y secuenciador de loops +GenericName[fr]=Boîte à rythme et séquenceur de boucle +Comment=Drum machine and loop sequencer +Comment[es]=Caja de ritmos y secuenciador de loops +Comment[fr]=Boîte à rythme et séquenceur de boucle +Icon=giada +Type=Application +Exec=giada +Terminal=false +Categories=AudioVideo;Audio;X-Digital_Processing;X-Jack;X-MIDI;Midi; +Keywords=midi;jackd;alsa;pulse;audio;sound;loop; diff --git a/giada.fr.1 b/giada.fr.1 new file mode 100644 index 0000000..381ce4b --- /dev/null +++ b/giada.fr.1 @@ -0,0 +1,42 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH GIADA 1 "May 23, 2015" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.IX Title "GIADA 1" +.SH "NOM" +giada \- Machine de boucle pure et dure +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +giada +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +Outil audio pur et dur, minimal, libre, pour les DJs, les performeurs live et +les musiciens électroniques. +Choisissez votre canal, remplissez-le avec des échantillons ou des événements +MIDI, et démarrez le spectacle en utilisant ce petit morceau de logiciel en tant +que boucleur, boîte à rythme, séquenceur, échantillonneur live, ou encore comme +un hôte de greffon/effet. +.LP +Giada se veut être un périphérique virtuel compact et portable pour une +utilisation en production et un ensemble live. +.SH AUTEUR +.PP +Cette page de manuel (en anglais) a été écrite par Jaromír MikeÅ¡ +, pour le projet Debian (mais peut être utilisée par +d'autres). +.PP +Elle a été traduite en français par Olivier Humbert pour +le projet LibraZiK (et peut également être utilisée par d'autres). diff --git a/giada.manpages b/giada.manpages new file mode 100644 index 0000000..c014cc7 --- /dev/null +++ b/giada.manpages @@ -0,0 +1,2 @@ +debian/giada.1 +debian/giada.fr.1 diff --git a/giada.svg b/giada.svg new file mode 100644 index 0000000..af72104 --- /dev/null +++ b/giada.svg @@ -0,0 +1,35 @@ + + + + + diff --git a/install b/install new file mode 100644 index 0000000..811fc5d --- /dev/null +++ b/install @@ -0,0 +1,2 @@ +debian/giada.desktop usr/share/applications +debian/giada.svg usr/share/icons/hicolor/scalable/apps diff --git a/patches/01-rtaudio5.patch b/patches/01-rtaudio5.patch new file mode 100644 index 0000000..bedf726 --- /dev/null +++ b/patches/01-rtaudio5.patch @@ -0,0 +1,20963 @@ +From: =?utf-8?q?IOhannes_m_zm=C3=B6lnig?= +Date: Wed, 25 Oct 2017 14:21:33 +0200 +Subject: updated bundled and hacked RtAudio to RtAudio5 + +--- + src/core/kernelAudio.cpp | 2 +- + src/deps/rtaudio-mod/RtAudio.cpp | 20580 +++++++++++++++++++------------------ + src/deps/rtaudio-mod/RtAudio.h | 113 +- + 3 files changed, 10401 insertions(+), 10294 deletions(-) + +--- giada.orig/src/core/kernelAudio.cpp ++++ giada/src/core/kernelAudio.cpp +@@ -59,7 +59,7 @@ + + jack_client_t* jackGetHandle() + { +- return static_cast(rtSystem->rtapi_->__HACK__getJackClient()); ++ return static_cast(rtSystem->GIADA_HACK__getJackClient()); + } + + #endif +--- giada.orig/src/deps/rtaudio-mod/RtAudio.cpp ++++ giada/src/deps/rtaudio-mod/RtAudio.cpp +@@ -1,10237 +1,10343 @@ +-/************************************************************************/ +-/*! \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 WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ +- +- RtAudio: realtime audio i/o C++ classes +- Copyright (c) 2001-2016 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 4.1.2 +- +-#include "RtAudio.h" +-#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 ) throw() +-{ +- return RTAUDIO_VERSION; +-} +- +-void RtAudio :: getCompiledApi( std::vector &apis ) throw() +-{ +- apis.clear(); +- +- // The order here will control the order of RtAudio's API search in +- // the constructor. +-#if defined(__UNIX_JACK__) +- apis.push_back( UNIX_JACK ); +-#endif +-#if defined(__LINUX_ALSA__) +- apis.push_back( LINUX_ALSA ); +-#endif +-#if defined(__LINUX_PULSE__) +- apis.push_back( LINUX_PULSE ); +-#endif +-#if defined(__LINUX_OSS__) +- apis.push_back( LINUX_OSS ); +-#endif +-#if defined(__WINDOWS_ASIO__) +- apis.push_back( WINDOWS_ASIO ); +-#endif +-#if defined(__WINDOWS_WASAPI__) +- apis.push_back( WINDOWS_WASAPI ); +-#endif +-#if defined(__WINDOWS_DS__) +- apis.push_back( WINDOWS_DS ); +-#endif +-#if defined(__MACOSX_CORE__) +- apis.push_back( MACOSX_CORE ); +-#endif +-#if defined(__RTAUDIO_DUMMY__) +- apis.push_back( RTAUDIO_DUMMY ); +-#endif +-} +- +-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() throw() +-{ +- 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; +-} +- +-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; +- } +- +- 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; i +-#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; } +-}; +- +-/* --- Monocasual hack ------------------------------------------------------ */ +-#ifdef __linux__ +-void *RtApi :: __HACK__getJackClient() { +- JackHandle *handle = (JackHandle *) stream_.apiHandle; +- return (void*) handle->client; +-} +-#endif +-/* -------------------------------------------------------------------------- */ +- +-static void jackSilentError( const char * ) {} +- +-RtApiJack :: RtApiJack() +-{ +- // 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, NULL, 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, NULL, 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(), NULL, 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(), NULL, 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, NULL, 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; +- } +- +- // Count the available ports containing the client name as device +- // channels. Jack "input ports" equal RtAudio output channels. +- unsigned int nChannels = 0; +- unsigned long flag = JackPortIsInput; +- if ( mode == INPUT ) flag = JackPortIsOutput; +- ports = jack_get_ports( client, deviceName.c_str(), NULL, 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(), NULL, 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 *) &handle ); +- 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 ); +- +- 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; +- } +- +- 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 ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { +- result = 1; +- ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), NULL, 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 ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { +- result = 1; +- ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), NULL, 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. eg. Creatives 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; +- } +- +- 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 +- +-//============================================================================= +- +-#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_; +- } +- +- // "in" index can end on the "out" index but cannot begin at it +- 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_; +- } +- +- // "out" index can begin at and end on 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 convertBufferWasapi function is used to perform this conversion +-// between HwIn->UserIn and UserOut->HwOut during the stream callback loop. +-// This sample rate converter favors speed over quality, and works best with conversions between +-// one rate and its multiple. +-void convertBufferWasapi( char* outBuffer, +- const char* inBuffer, +- const unsigned int& channelCount, +- const unsigned int& inSampleRate, +- const unsigned int& outSampleRate, +- const unsigned int& inSampleCount, +- unsigned int& outSampleCount, +- const RtAudioFormat& format ) +-{ +- // calculate the new outSampleCount and relative sampleStep +- float sampleRatio = ( float ) outSampleRate / inSampleRate; +- float sampleStep = 1.0f / sampleRatio; +- float inSampleFraction = 0.0f; +- +- outSampleCount = ( unsigned int ) roundf( inSampleCount * sampleRatio ); +- +- // frame-by-frame, copy each relative input sample into it's corresponding output sample +- for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) +- { +- unsigned int inSample = ( unsigned int ) inSampleFraction; +- +- switch ( format ) +- { +- case RTAUDIO_SINT8: +- memcpy( &( ( char* ) outBuffer )[ outSample * channelCount ], &( ( char* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( char ) ); +- break; +- case RTAUDIO_SINT16: +- memcpy( &( ( short* ) outBuffer )[ outSample * channelCount ], &( ( short* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( short ) ); +- break; +- case RTAUDIO_SINT24: +- memcpy( &( ( S24* ) outBuffer )[ outSample * channelCount ], &( ( S24* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( S24 ) ); +- break; +- case RTAUDIO_SINT32: +- memcpy( &( ( int* ) outBuffer )[ outSample * channelCount ], &( ( int* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( int ) ); +- break; +- case RTAUDIO_FLOAT32: +- memcpy( &( ( float* ) outBuffer )[ outSample * channelCount ], &( ( float* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( float ) ); +- break; +- case RTAUDIO_FLOAT64: +- memcpy( &( ( double* ) outBuffer )[ outSample * channelCount ], &( ( double* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( double ) ); +- break; +- } +- +- // jump to next in sample +- inSampleFraction += sampleStep; +- } +-} +- +-//----------------------------------------------------------------------------- +- +-// 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 ( FAILED( hr ) ) { +- errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; +- error( RtAudioError::DRIVER_ERROR ); +- } +-} +- +-//----------------------------------------------------------------------------- +- +-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; +- +- // 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; +- } +- +- // 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 ); +- +- // stop capture client if applicable +- if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { +- HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); +- if ( FAILED( hr ) ) { +- errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; +- error( RtAudioError::DRIVER_ERROR ); +- return; +- } +- } +- +- // stop render client if applicable +- if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { +- HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); +- if ( FAILED( hr ) ) { +- errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; +- error( RtAudioError::DRIVER_ERROR ); +- return; +- } +- } +- +- // 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 ); +- } +- +- // stop capture client if applicable +- if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { +- HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); +- if ( FAILED( hr ) ) { +- errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; +- error( RtAudioError::DRIVER_ERROR ); +- return; +- } +- } +- +- // stop render client if applicable +- if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { +- HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); +- if ( FAILED( hr ) ) { +- errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; +- error( RtAudioError::DRIVER_ERROR ); +- return; +- } +- } +- +- // 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; +- } +- +- // determine whether index falls within capture or render 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 device audio client."; +- goto Exit; +- } +- +- hr = captureAudioClient->GetMixFormat( &deviceFormat ); +- if ( FAILED( hr ) ) { +- errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; +- goto Exit; +- } +- +- stream_.nDeviceChannels[mode] = deviceFormat->nChannels; +- captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); +- } +- else { +- if ( mode != OUTPUT ) { +- errorType = RtAudioError::INVALID_USE; +- errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device."; +- goto Exit; +- } +- +- // retrieve renderAudioClient from devicePtr +- IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; +- +- 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 device audio client."; +- goto Exit; +- } +- +- hr = renderAudioClient->GetMixFormat( &deviceFormat ); +- if ( FAILED( hr ) ) { +- errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve 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 != stream_.nDeviceChannels ) +- 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; +- +- // 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 callbackPushed = false; +- 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; +- +- errorText_.clear(); +- 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 ) 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; +- } +- +- captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); +- +- // initialize capture stream according to desire buffer size +- float desiredBufferSize = stream_.bufferSize * captureSrRatio; +- REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); +- +- if ( !captureClient ) { +- hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, +- AUDCLNT_STREAMFLAGS_EVENTCALLBACK, +- desiredBufferPeriod, +- desiredBufferPeriod, +- 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; +- } +- +- // 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 )->captureClient = captureClient; +- ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; +- } +- +- 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 ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; +- inBufferSize *= stream_.nDeviceChannels[INPUT]; +- +- // set captureBuffer size +- captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); +- +- // 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; +- } +- } +- +- // 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; +- } +- +- renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); +- +- // initialize render stream according to desire buffer size +- float desiredBufferSize = stream_.bufferSize * renderSrRatio; +- REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); +- +- if ( !renderClient ) { +- hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, +- AUDCLNT_STREAMFLAGS_EVENTCALLBACK, +- desiredBufferPeriod, +- desiredBufferPeriod, +- 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; +- } +- +- 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 ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; +- outBufferSize *= stream_.nDeviceChannels[OUTPUT]; +- +- // set renderBuffer size +- renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); +- +- // 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; +- } +- } +- +- if ( stream_.mode == INPUT ) { +- convBuffSize = ( size_t ) ( 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 ) ( 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 ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), +- ( size_t ) ( 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] ) ); +- } +- +- convBuffer = ( char* ) malloc( convBuffSize ); +- stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); +- 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 ) { +- // Pull callback buffer from inputBuffer +- callbackPulled = captureBuffer.pullBuffer( convBuffer, +- ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], +- stream_.deviceFormat[INPUT] ); +- +- if ( callbackPulled ) { +- // Convert callback buffer to user sample rate +- convertBufferWasapi( stream_.deviceBuffer, +- convBuffer, +- stream_.nDeviceChannels[INPUT], +- captureFormat->nSamplesPerSec, +- stream_.sampleRate, +- ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), +- convBufferSize, +- stream_.deviceFormat[INPUT] ); +- +- 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 ); +- +- // 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 ( stream_.doConvertBuffer[OUTPUT] ) { +- // Convert callback buffer to stream format +- convertBuffer( stream_.deviceBuffer, +- stream_.userBuffer[OUTPUT], +- stream_.convertInfo[OUTPUT] ); +- +- } +- +- // Convert callback buffer to stream sample rate +- convertBufferWasapi( convBuffer, +- stream_.deviceBuffer, +- stream_.nDeviceChannels[OUTPUT], +- stream_.sampleRate, +- renderFormat->nSamplesPerSec, +- stream_.bufferSize, +- convBufferSize, +- stream_.deviceFormat[OUTPUT] ); +- +- // 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( 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 ) { +- callbackPulled = false; +- // tick stream time +- RtApi::tickStreamTime(); +- } +- +- } +- +-Exit: +- // clean up +- CoTaskMemFree( captureFormat ); +- CoTaskMemFree( renderFormat ); +- +- free ( convBuffer ); +- +- CoUninitialize(); +- +- // update stream state +- stream_.state = STREAM_STOPPED; +- +- if ( errorText_.empty() ) +- return; +- else +- 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 +- +-#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 ( coInitialized_ ) CoUninitialize(); // balanced call. +- if ( stream_.state != STREAM_CLOSED ) closeStream(); +-} +- +-// 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; +- } +- +- 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; +- +- // 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 ) { +- 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: +- 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; +- +- // 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 ) { +- 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: +- 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" ); +- goto foundDevice; +- } +- nDevices++; +- } +- +- 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 (eg: NetBSD 1.6.x with GNU Pthread) +- if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { +- // We previously attempted to increase the audio callback priority +- // to SCHED_RR here via the attributes. However, while no errors +- // were reported in doing so, it did not work. So, now this is +- // done in the alsaCallbackHandler function. +- stream_.callbackInfo.doRealtime = true; +- 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; +- stream_.callbackInfo.priority = priority; +- } +-#endif +- +- stream_.callbackInfo.isRunning = true; +- result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); +- pthread_attr_destroy( &attr ); +- 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 ); +- +- 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 (eg: NetBSD 1.6.x with GNU Pthread) +- if ( info->doRealtime ) { +- pthread_t tID = pthread_self(); // ID of this thread +- sched_param prio = { info->priority }; // scheduling priority of thread +- pthread_setschedparam( tID, SCHED_RR, &prio ); +- } +-#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; +- +- 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 ); +- +- 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_.callbackInfo.isRunning = true; +- if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { +- errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; +- goto error; +- } +- } +- +- stream_.state = STREAM_STOPPED; +- return true; +- +- 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; +- } +- +- 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; +- if ( mask & AFMT_FLOAT ) +- info.nativeFormats |= RTAUDIO_FLOAT32; +- 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 - 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 (eg: NetBSD 1.6.x with GNU Pthread) +- if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { +- 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; +- pthread_attr_setschedparam( &attr, ¶m ); +- pthread_attr_setschedpolicy( &attr, SCHED_RR ); +- } +- 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 ) { +- 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; +- } +- +- 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 ); +- +- 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; +- +- 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 ++#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; ++} ++ ++void RtAudio :: getCompiledApi( std::vector &apis ) ++{ ++ apis.clear(); ++ ++ // The order here will control the order of RtAudio's API search in ++ // the constructor. ++#if defined(__UNIX_JACK__) ++ apis.push_back( UNIX_JACK ); ++#endif ++#if defined(__LINUX_ALSA__) ++ apis.push_back( LINUX_ALSA ); ++#endif ++#if defined(__LINUX_PULSE__) ++ apis.push_back( LINUX_PULSE ); ++#endif ++#if defined(__LINUX_OSS__) ++ apis.push_back( LINUX_OSS ); ++#endif ++#if defined(__WINDOWS_ASIO__) ++ apis.push_back( WINDOWS_ASIO ); ++#endif ++#if defined(__WINDOWS_WASAPI__) ++ apis.push_back( WINDOWS_WASAPI ); ++#endif ++#if defined(__WINDOWS_DS__) ++ apis.push_back( WINDOWS_DS ); ++#endif ++#if defined(__MACOSX_CORE__) ++ apis.push_back( MACOSX_CORE ); ++#endif ++#if defined(__RTAUDIO_DUMMY__) ++ apis.push_back( RTAUDIO_DUMMY ); ++#endif ++} ++ ++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; ++ } ++ ++ 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; i ++#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, NULL, 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, NULL, 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(), NULL, 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(), NULL, 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, NULL, 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; ++ } ++ ++ // Count the available ports containing the client name as device ++ // channels. Jack "input ports" equal RtAudio output channels. ++ unsigned int nChannels = 0; ++ unsigned long flag = JackPortIsInput; ++ if ( mode == INPUT ) flag = JackPortIsOutput; ++ ports = jack_get_ports( client, deviceName.c_str(), NULL, 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(), NULL, 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 *) &handle ); ++ 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; ++ } ++ ++ 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(), NULL, 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(), NULL, 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. eg. Creatives 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; ++ } ++ ++ 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 ++ ++//============================================================================= ++ ++#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_; ++ } ++ ++ // "in" index can end on the "out" index but cannot begin at it ++ 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_; ++ } ++ ++ // "out" index can begin at and end on 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 convertBufferWasapi function is used to perform this conversion ++// between HwIn->UserIn and UserOut->HwOut during the stream callback loop. ++// This sample rate converter works best with conversions between one rate and its multiple. ++void convertBufferWasapi( char* outBuffer, ++ const char* inBuffer, ++ const unsigned int& channelCount, ++ const unsigned int& inSampleRate, ++ const unsigned int& outSampleRate, ++ const unsigned int& inSampleCount, ++ unsigned int& outSampleCount, ++ const RtAudioFormat& format ) ++{ ++ // calculate the new outSampleCount and relative sampleStep ++ float sampleRatio = ( float ) outSampleRate / inSampleRate; ++ float sampleRatioInv = ( float ) 1 / sampleRatio; ++ float sampleStep = 1.0f / sampleRatio; ++ float inSampleFraction = 0.0f; ++ ++ outSampleCount = ( unsigned int ) std::roundf( inSampleCount * sampleRatio ); ++ ++ // if inSampleRate is a multiple of outSampleRate (or vice versa) there's no need to interpolate ++ if ( floor( sampleRatio ) == sampleRatio || floor( sampleRatioInv ) == sampleRatioInv ) ++ { ++ // frame-by-frame, copy each relative input sample into it's corresponding output sample ++ for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) ++ { ++ unsigned int inSample = ( unsigned int ) inSampleFraction; ++ ++ switch ( format ) ++ { ++ case RTAUDIO_SINT8: ++ memcpy( &( ( char* ) outBuffer )[ outSample * channelCount ], &( ( char* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( char ) ); ++ break; ++ case RTAUDIO_SINT16: ++ memcpy( &( ( short* ) outBuffer )[ outSample * channelCount ], &( ( short* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( short ) ); ++ break; ++ case RTAUDIO_SINT24: ++ memcpy( &( ( S24* ) outBuffer )[ outSample * channelCount ], &( ( S24* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( S24 ) ); ++ break; ++ case RTAUDIO_SINT32: ++ memcpy( &( ( int* ) outBuffer )[ outSample * channelCount ], &( ( int* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( int ) ); ++ break; ++ case RTAUDIO_FLOAT32: ++ memcpy( &( ( float* ) outBuffer )[ outSample * channelCount ], &( ( float* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( float ) ); ++ break; ++ case RTAUDIO_FLOAT64: ++ memcpy( &( ( double* ) outBuffer )[ outSample * channelCount ], &( ( double* ) inBuffer )[ inSample * channelCount ], channelCount * sizeof( double ) ); ++ break; ++ } ++ ++ // jump to next in sample ++ inSampleFraction += sampleStep; ++ } ++ } ++ else // else interpolate ++ { ++ // frame-by-frame, copy each relative input sample into it's corresponding output sample ++ for ( unsigned int outSample = 0; outSample < outSampleCount; outSample++ ) ++ { ++ unsigned int inSample = ( unsigned int ) inSampleFraction; ++ float inSampleDec = inSampleFraction - inSample; ++ unsigned int frameInSample = inSample * channelCount; ++ unsigned int frameOutSample = outSample * channelCount; ++ ++ switch ( format ) ++ { ++ case RTAUDIO_SINT8: ++ { ++ for ( unsigned int channel = 0; channel < channelCount; channel++ ) ++ { ++ char fromSample = ( ( char* ) inBuffer )[ frameInSample + channel ]; ++ char toSample = ( ( char* ) inBuffer )[ frameInSample + channelCount + channel ]; ++ char sampleDiff = ( char ) ( ( toSample - fromSample ) * inSampleDec ); ++ ( ( char* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; ++ } ++ break; ++ } ++ case RTAUDIO_SINT16: ++ { ++ for ( unsigned int channel = 0; channel < channelCount; channel++ ) ++ { ++ short fromSample = ( ( short* ) inBuffer )[ frameInSample + channel ]; ++ short toSample = ( ( short* ) inBuffer )[ frameInSample + channelCount + channel ]; ++ short sampleDiff = ( short ) ( ( toSample - fromSample ) * inSampleDec ); ++ ( ( short* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; ++ } ++ break; ++ } ++ case RTAUDIO_SINT24: ++ { ++ for ( unsigned int channel = 0; channel < channelCount; channel++ ) ++ { ++ int fromSample = ( ( S24* ) inBuffer )[ frameInSample + channel ].asInt(); ++ int toSample = ( ( S24* ) inBuffer )[ frameInSample + channelCount + channel ].asInt(); ++ int sampleDiff = ( int ) ( ( toSample - fromSample ) * inSampleDec ); ++ ( ( S24* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; ++ } ++ break; ++ } ++ case RTAUDIO_SINT32: ++ { ++ for ( unsigned int channel = 0; channel < channelCount; channel++ ) ++ { ++ int fromSample = ( ( int* ) inBuffer )[ frameInSample + channel ]; ++ int toSample = ( ( int* ) inBuffer )[ frameInSample + channelCount + channel ]; ++ int sampleDiff = ( int ) ( ( toSample - fromSample ) * inSampleDec ); ++ ( ( int* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; ++ } ++ break; ++ } ++ case RTAUDIO_FLOAT32: ++ { ++ for ( unsigned int channel = 0; channel < channelCount; channel++ ) ++ { ++ float fromSample = ( ( float* ) inBuffer )[ frameInSample + channel ]; ++ float toSample = ( ( float* ) inBuffer )[ frameInSample + channelCount + channel ]; ++ float sampleDiff = ( toSample - fromSample ) * inSampleDec; ++ ( ( float* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; ++ } ++ break; ++ } ++ case RTAUDIO_FLOAT64: ++ { ++ for ( unsigned int channel = 0; channel < channelCount; channel++ ) ++ { ++ double fromSample = ( ( double* ) inBuffer )[ frameInSample + channel ]; ++ double toSample = ( ( double* ) inBuffer )[ frameInSample + channelCount + channel ]; ++ double sampleDiff = ( toSample - fromSample ) * inSampleDec; ++ ( ( double* ) outBuffer )[ frameOutSample + channel ] = fromSample + sampleDiff; ++ } ++ break; ++ } ++ } ++ ++ // jump to next in sample ++ inSampleFraction += sampleStep; ++ } ++ } ++} ++ ++//----------------------------------------------------------------------------- ++ ++// 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 ( FAILED( hr ) ) { ++ errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; ++ error( RtAudioError::DRIVER_ERROR ); ++ } ++} ++ ++//----------------------------------------------------------------------------- ++ ++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; ++ ++ // 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; ++ } ++ ++ // 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 ); ++ ++ // stop capture client if applicable ++ if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { ++ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); ++ if ( FAILED( hr ) ) { ++ errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; ++ error( RtAudioError::DRIVER_ERROR ); ++ return; ++ } ++ } ++ ++ // stop render client if applicable ++ if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { ++ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); ++ if ( FAILED( hr ) ) { ++ errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; ++ error( RtAudioError::DRIVER_ERROR ); ++ return; ++ } ++ } ++ ++ // 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 ); ++ } ++ ++ // stop capture client if applicable ++ if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { ++ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); ++ if ( FAILED( hr ) ) { ++ errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; ++ error( RtAudioError::DRIVER_ERROR ); ++ return; ++ } ++ } ++ ++ // stop render client if applicable ++ if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { ++ HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); ++ if ( FAILED( hr ) ) { ++ errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; ++ error( RtAudioError::DRIVER_ERROR ); ++ return; ++ } ++ } ++ ++ // 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; ++ } ++ ++ // determine whether index falls within capture or render 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 device audio client."; ++ goto Exit; ++ } ++ ++ hr = captureAudioClient->GetMixFormat( &deviceFormat ); ++ if ( FAILED( hr ) ) { ++ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; ++ goto Exit; ++ } ++ ++ stream_.nDeviceChannels[mode] = deviceFormat->nChannels; ++ captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); ++ } ++ else { ++ if ( mode != OUTPUT ) { ++ errorType = RtAudioError::INVALID_USE; ++ errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device."; ++ goto Exit; ++ } ++ ++ // retrieve renderAudioClient from devicePtr ++ IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; ++ ++ 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 device audio client."; ++ goto Exit; ++ } ++ ++ hr = renderAudioClient->GetMixFormat( &deviceFormat ); ++ if ( FAILED( hr ) ) { ++ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve 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 != stream_.nDeviceChannels ) ++ 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; ++ ++ // 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 callbackPushed = false; ++ 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; ++ ++ errorText_.clear(); ++ 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 ) 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; ++ } ++ ++ captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); ++ ++ // initialize capture stream according to desire buffer size ++ float desiredBufferSize = stream_.bufferSize * captureSrRatio; ++ REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); ++ ++ if ( !captureClient ) { ++ hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, ++ AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ++ desiredBufferPeriod, ++ desiredBufferPeriod, ++ 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; ++ } ++ ++ // 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 )->captureClient = captureClient; ++ ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; ++ } ++ ++ 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 ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; ++ inBufferSize *= stream_.nDeviceChannels[INPUT]; ++ ++ // set captureBuffer size ++ captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); ++ ++ // 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; ++ } ++ } ++ ++ // 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; ++ } ++ ++ renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); ++ ++ // initialize render stream according to desire buffer size ++ float desiredBufferSize = stream_.bufferSize * renderSrRatio; ++ REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); ++ ++ if ( !renderClient ) { ++ hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, ++ AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ++ desiredBufferPeriod, ++ desiredBufferPeriod, ++ 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; ++ } ++ ++ 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 ) ( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; ++ outBufferSize *= stream_.nDeviceChannels[OUTPUT]; ++ ++ // set renderBuffer size ++ renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); ++ ++ // 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; ++ } ++ } ++ ++ if ( stream_.mode == INPUT ) { ++ convBuffSize = ( size_t ) ( 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 ) ( 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 ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), ++ ( size_t ) ( 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] ) ); ++ } ++ ++ convBuffer = ( char* ) malloc( convBuffSize ); ++ stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); ++ 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 ) { ++ // Pull callback buffer from inputBuffer ++ callbackPulled = captureBuffer.pullBuffer( convBuffer, ++ ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], ++ stream_.deviceFormat[INPUT] ); ++ ++ if ( callbackPulled ) { ++ // Convert callback buffer to user sample rate ++ convertBufferWasapi( stream_.deviceBuffer, ++ convBuffer, ++ stream_.nDeviceChannels[INPUT], ++ captureFormat->nSamplesPerSec, ++ stream_.sampleRate, ++ ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), ++ convBufferSize, ++ stream_.deviceFormat[INPUT] ); ++ ++ 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 ); ++ ++ // 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 ( stream_.doConvertBuffer[OUTPUT] ) { ++ // Convert callback buffer to stream format ++ convertBuffer( stream_.deviceBuffer, ++ stream_.userBuffer[OUTPUT], ++ stream_.convertInfo[OUTPUT] ); ++ ++ } ++ ++ // Convert callback buffer to stream sample rate ++ convertBufferWasapi( convBuffer, ++ stream_.deviceBuffer, ++ stream_.nDeviceChannels[OUTPUT], ++ stream_.sampleRate, ++ renderFormat->nSamplesPerSec, ++ stream_.bufferSize, ++ convBufferSize, ++ stream_.deviceFormat[OUTPUT] ); ++ ++ // 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( 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 ) { ++ callbackPulled = false; ++ // tick stream time ++ RtApi::tickStreamTime(); ++ } ++ ++ } ++ ++Exit: ++ // clean up ++ CoTaskMemFree( captureFormat ); ++ CoTaskMemFree( renderFormat ); ++ ++ free ( convBuffer ); ++ ++ CoUninitialize(); ++ ++ // update stream state ++ stream_.state = STREAM_STOPPED; ++ ++ if ( errorText_.empty() ) ++ return; ++ else ++ 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 ++ ++#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; ++ } ++ ++ 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; ++ ++ // 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 ) { ++ 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: ++ 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; ++ ++ // 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 ) { ++ 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: ++ 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" ); ++ goto foundDevice; ++ } ++ nDevices++; ++ } ++ ++ 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 (eg: NetBSD 1.6.x with GNU Pthread) ++ if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { ++ // We previously attempted to increase the audio callback priority ++ // to SCHED_RR here via the attributes. However, while no errors ++ // were reported in doing so, it did not work. So, now this is ++ // done in the alsaCallbackHandler function. ++ stream_.callbackInfo.doRealtime = true; ++ 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; ++ stream_.callbackInfo.priority = priority; ++ } ++#endif ++ ++ stream_.callbackInfo.isRunning = true; ++ result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); ++ pthread_attr_destroy( &attr ); ++ 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 ); ++ ++ 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 (eg: NetBSD 1.6.x with GNU Pthread) ++ if ( info->doRealtime ) { ++ pthread_t tID = pthread_self(); // ID of this thread ++ sched_param prio = { info->priority }; // scheduling priority of thread ++ pthread_setschedparam( tID, SCHED_RR, &prio ); ++ } ++#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; ++ ++ 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 ); ++ ++ 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_.callbackInfo.isRunning = true; ++ if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { ++ errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; ++ goto error; ++ } ++ } ++ ++ stream_.state = STREAM_STOPPED; ++ return true; ++ ++ 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; ++ } ++ ++ 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 (eg: NetBSD 1.6.x with GNU Pthread) ++ if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { ++ 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; ++ pthread_attr_setschedparam( &attr, ¶m ); ++ pthread_attr_setschedpolicy( &attr, SCHED_RR ); ++ } ++ 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 ) { ++ 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; ++ } ++ ++ 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 ); ++ ++ 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; ++ ++ 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(rtapi_); ++ if (jackapi && jackapi->stream_.apiHandle) { ++ JackHandle *handle = (JackHandle *) jackapi->stream_.apiHandle; ++ return (void*) handle->client; ++ } ++#endif ++ return 0; ++} ++ ++ ++ ++ // Indentation settings for Vim and Emacs ++ // ++ // Local Variables: ++ // c-basic-offset: 2 ++ // indent-tabs-mode: nil ++ // End: ++ // ++ // vim: et sts=2 sw=2 ++ +--- giada.orig/src/deps/rtaudio-mod/RtAudio.h ++++ giada/src/deps/rtaudio-mod/RtAudio.h +@@ -10,7 +10,7 @@ + RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ + + RtAudio: realtime audio i/o C++ classes +- Copyright (c) 2001-2016 Gary P. Scavone ++ Copyright (c) 2001-2017 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files +@@ -45,11 +45,11 @@ + #ifndef __RTAUDIO_H + #define __RTAUDIO_H + +-#define RTAUDIO_VERSION "4.1.2" ++#define RTAUDIO_VERSION "5.0.0" + + #include + #include +-#include ++#include + #include + + /*! \typedef typedef unsigned long RtAudioFormat; +@@ -86,6 +86,7 @@ + - \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 +@@ -111,12 +112,15 @@ + 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 ++ 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). +@@ -124,6 +128,7 @@ + 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. +@@ -195,7 +200,7 @@ + */ + /************************************************************************/ + +-class RtAudioError : public std::exception ++class RtAudioError : public std::runtime_error + { + public: + //! Defined RtAudioError types. +@@ -214,25 +219,22 @@ + }; + + //! The constructor. +- RtAudioError( const std::string& message, Type type = RtAudioError::UNSPECIFIED ) throw() : message_(message), type_(type) {} +- +- //! The destructor. +- virtual ~RtAudioError( void ) throw() {} ++ 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 throw() { std::cerr << '\n' << message_ << "\n\n"; } ++ virtual void printMessage( void ) const ++ { std::cerr << '\n' << what() << "\n\n"; } + + //! Returns the thrown error message type. +- virtual const Type& getType(void) const throw() { return type_; } ++ virtual const Type& getType(void) const { return type_; } + + //! Returns the thrown error message string. +- virtual const std::string& getMessage(void) const throw() { return message_; } +- +- //! Returns the thrown error message as a c-style string. +- virtual const char* what( void ) const throw() { return message_.c_str(); } ++ virtual const std::string getMessage(void) const ++ { return std::string(what()); } + + protected: +- std::string message_; + Type type_; + }; + +@@ -341,7 +343,7 @@ + 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 ++ 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. +@@ -375,7 +377,7 @@ + }; + + //! A static function to determine the current RtAudio version. +- static std::string getVersion( void ) throw(); ++ static std::string getVersion( void ); + + //! A static function to determine the available compiled audio APIs. + /*! +@@ -383,7 +385,7 @@ + the enumerated list values. Note that there can be more than one + API compiled for certain operating systems. + */ +- static void getCompiledApi( std::vector &apis ) throw(); ++ static void getCompiledApi( std::vector &apis ); + + //! The class constructor. + /*! +@@ -401,18 +403,18 @@ + If a stream is running or open, it will be stopped and closed + automatically. + */ +- ~RtAudio() throw(); ++ ~RtAudio(); + + //! Returns the audio API specifier for the current instance of RtAudio. +- RtAudio::Api getCurrentApi( void ) throw(); ++ 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. ++ a system error occurs during processing, a warning will be issued. + */ +- unsigned int getDeviceCount( void ) throw(); ++ unsigned int getDeviceCount( void ); + + //! Return an RtAudio::DeviceInfo structure for a specified device number. + /*! +@@ -435,7 +437,7 @@ + client's responsibility to verify that a device is available + before attempting to open a stream. + */ +- unsigned int getDefaultOutputDevice( void ) throw(); ++ unsigned int getDefaultOutputDevice( void ); + + //! A function that returns the index of the default input device. + /*! +@@ -445,7 +447,7 @@ + client's responsibility to verify that a device is available + before attempting to open a stream. + */ +- unsigned int getDefaultInputDevice( void ) throw(); ++ unsigned int getDefaultInputDevice( void ); + + //! A public function for opening a stream with the specified parameters. + /*! +@@ -477,7 +479,7 @@ + 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 ++ 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 +@@ -498,7 +500,7 @@ + If a stream is not open, this function issues a warning and + returns (no exception is thrown). + */ +- void closeStream( void ) throw(); ++ void closeStream( void ); + + //! A function that starts a stream. + /*! +@@ -528,10 +530,10 @@ + void abortStream( void ); + + //! Returns true if a stream is open and false if not. +- bool isStreamOpen( void ) const throw(); ++ bool isStreamOpen( void ) const; + + //! Returns true if the stream is running and false if it is stopped or not open. +- bool isStreamRunning( void ) const throw(); ++ bool isStreamRunning( void ) const; + + //! Returns the number of elapsed seconds since the stream was started. + /*! +@@ -565,14 +567,15 @@ + unsigned int getStreamSampleRate( void ); + + //! Specify whether warning messages should be printed to stderr. +- void showWarnings( bool value = true ) throw(); ++ void showWarnings( bool value = true ); + +- /* --- Monocasual hack ---------------------------------------------------- */ +- //protected: +- /* ------------------------------------------------------------------------ */ ++ protected: + + void openRtApi( RtAudio::Api api ); + RtApi *rtapi_; ++ ++ public: ++ void *GIADA_HACK__getJackClient(); /* Monocasual HACK */ + }; + + // Operating system dependent thread functionality. +@@ -618,7 +621,7 @@ + + // Default constructor. + CallbackInfo() +- :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false) {} ++ :object(0), callback(0), userData(0), errorCallback(0), apiInfo(0), isRunning(false), doRealtime(false), priority(0) {} + }; + + // **************************************************************** // +@@ -675,12 +678,6 @@ + { + public: + +- /* --- Monocasual hack ---------------------------------------------------- */ +- #ifdef __linux__ +- void *__HACK__getJackClient(); +- #endif +- /* ------------------------------------------------------------------------ */ +- + RtApi(); + virtual ~RtApi(); + virtual RtAudio::Api getCurrentApi( void ) = 0; +@@ -790,7 +787,7 @@ + "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, ++ 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 ); +@@ -824,6 +821,8 @@ + + //! Protected common method that sets up the parameters for buffer conversion. + void setConvertInfo( StreamMode mode, unsigned int firstChannel ); ++ ++ friend class RtAudio; /* GIADA Hack */ + }; + + // **************************************************************** // +@@ -832,22 +831,22 @@ + // + // **************************************************************** // + +-inline RtAudio::Api RtAudio :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } +-inline unsigned int RtAudio :: getDeviceCount( void ) throw() { return rtapi_->getDeviceCount(); } ++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 ) throw() { return rtapi_->getDefaultInputDevice(); } +-inline unsigned int RtAudio :: getDefaultOutputDevice( void ) throw() { return rtapi_->getDefaultOutputDevice(); } +-inline void RtAudio :: closeStream( void ) throw() { return rtapi_->closeStream(); } ++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 throw() { return rtapi_->isStreamOpen(); } +-inline bool RtAudio :: isStreamRunning( void ) const throw() { return rtapi_->isStreamRunning(); } ++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 ) throw() { rtapi_->showWarnings( value ); } ++inline void RtAudio :: showWarnings( bool value ) { rtapi_->showWarnings( value ); } + + // RtApi Subclass prototypes. + +@@ -882,7 +881,7 @@ + + private: + +- bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, ++ bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +@@ -916,10 +915,12 @@ + + private: + +- bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, ++ 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 +@@ -952,7 +953,7 @@ + std::vector devices_; + void saveDeviceInfo( void ); + bool coInitialized_; +- bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, ++ bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +@@ -991,7 +992,7 @@ + bool buffersRolling; + long duplexPrerollBytes; + std::vector dsDevices; +- bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, ++ bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +@@ -1062,7 +1063,7 @@ + + std::vector devices_; + void saveDeviceInfo( void ); +- bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, ++ bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +@@ -1126,7 +1127,7 @@ + + private: + +- bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, ++ bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, + unsigned int firstChannel, unsigned int sampleRate, + RtAudioFormat format, unsigned int *bufferSize, + RtAudio::StreamOptions *options ); +@@ -1151,7 +1152,7 @@ + + private: + +- bool probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, ++ 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; } diff --git a/patches/02-rtmidi-pkgconfig.patch b/patches/02-rtmidi-pkgconfig.patch new file mode 100644 index 0000000..4390f31 --- /dev/null +++ b/patches/02-rtmidi-pkgconfig.patch @@ -0,0 +1,46 @@ +From: James Cowgill +Date: Wed, 25 Oct 2017 14:25:50 +0200 +Subject: build with new rtmidi lib. + +--- giada.orig/Makefile.am ++++ giada/Makefile.am +@@ -360,10 +360,10 @@ + if LINUX + + # Add preprocessor flags to enable ALSA, Pulse and JACK in RtAudio. +-cppFlags += -D__LINUX_ALSA__ -D__LINUX_PULSE__ -D__UNIX_JACK__ ++cppFlags += $(RTMIDI_CFLAGS) + + ldAdd += -lsndfile -lfltk -lXext -lX11 -lXft -lXpm -lm -ljack -lasound \ +- -lpthread -ldl -lpulse-simple -lpulse -lsamplerate -lrtmidi -ljansson \ ++ -lpthread -ldl -lpulse-simple -lpulse -lsamplerate $(RTMIDI_LIBS) -ljansson \ + -lfreetype + + endif +--- giada.orig/configure.ac ++++ giada/configure.ac +@@ -129,23 +129,7 @@ + ) + AC_LANG_POP + +-if test "x$os" = "xosx"; then +- AC_LANG_PUSH([C++]) +- AC_CHECK_HEADER( +- [RtMidi.h], +- [], +- [AC_MSG_ERROR([library 'rtMidi' not found!])] +- ) +- AC_LANG_POP +-else +- AC_LANG_PUSH([C++]) +- AC_CHECK_HEADER( +- [rtmidi/RtMidi.h], +- [], +- [AC_MSG_ERROR([library 'rtMidi' not found!])] +- ) +- AC_LANG_POP +-fi ++PKG_CHECK_MODULES([RTMIDI], [rtmidi]) + + + AC_LANG_PUSH([C++]) diff --git a/patches/series b/patches/series new file mode 100644 index 0000000..a5856f3 --- /dev/null +++ b/patches/series @@ -0,0 +1,2 @@ +01-rtaudio5.patch +02-rtmidi-pkgconfig.patch diff --git a/rules b/rules new file mode 100755 index 0000000..565fce6 --- /dev/null +++ b/rules @@ -0,0 +1,62 @@ +#!/usr/bin/make -f + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all +export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + +DATE_FMT = %Y-%m-%d +ifdef SOURCE_DATE_EPOCH + BUILD_DATE ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "+$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "+$(DATE_FMT)" 2>/dev/null || date -u "+$(DATE_FMT)") +else + BUILD_DATE ?= $(shell date "+$(DATE_FMT)") +endif + +JUCE_VERSION := $(shell dpkg-query --show --showformat='$${source:Version}' juce-modules-source) + +CPPFLAGS+=-DBUILD_DATE='"$(BUILD_DATE)"' +CXXFLAGS+=-std=c++11 -Wno-error +LIBS=$(shell pkg-config --libs libjpeg libpng libcurl) + +# JUCE (used by giada) uses some c++11 features requiring atomic_store_8 and +# atomic_load_8, so we need to link with libatomic on +# armel, powerpc, powerpcspe, m68k, mips, mipsel, sh4, ... +# see also: +# - https://gcc.gnu.org/wiki/Atomic +# - https://gcc.gnu.org/wiki/Atomic/GCCMM/LIbrary +# - the 'clasp' packaging +noatomicarch = $(shell dpkg-architecture -qDEB_HOST_ARCH | egrep -x "(armel|powerpc|powerpcspe|m68k|mips|mipsel|sh4|riscv64)") +# link with libatomic on architectures without built-in atomic +ifeq ($(if $(noatomicarch),atomic), atomic) + LIBS += -latomic +endif + +%: + dh $@ + +override_dh_auto_configure: + dh_auto_configure -- \ + --enable-vst \ + --enable-system-catch \ + --target=linux \ + LIBS="$(LIBS)" + +override_dh_auto_build: + cp -rav /usr/share/juce src/deps/ + dh_auto_build +override_dh_auto_test: + @echo home: $(HOME) + HOME=/tmp xvfb-run -a dh_auto_test -a || (grep . test-suite.log giada_test.log; false) + +override_dh_auto_clean: + dh_auto_clean + test -e src/deps/rtaudio-mode/Makefile && make -C src/deps/rtaudio-mod/ distclean || true + +override_dh_installchangelogs: + dh_installchangelogs ChangeLog + +override_dh_gencontrol: + dh_gencontrol -- \ + -Vjuce:BuiltUsing="juce ( = $(JUCE_VERSION) )" + +## all the file-exclusion magic is handled by debian/copyright +get-orig-source: + uscan --download diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 0000000..5f36ed8 --- /dev/null +++ b/watch @@ -0,0 +1,3 @@ +version=3 +opts=repacksuffix=+ds1,filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/giada-$1\.tar\.gz/,uversionmangle=s/-rc/~rc/,dversionmangle=s/\+ds.*// \ + https://github.com/monocasual/giada/tags .*/v?(\d\S*)\.tar\.gz -- 2.30.2