Stop instantiating RtAudio all over the place
authorCarl Hetherington <cth@carlh.net>
Thu, 25 Jul 2024 12:46:44 +0000 (14:46 +0200)
committerCarl Hetherington <cth@carlh.net>
Fri, 26 Jul 2024 09:39:59 +0000 (11:39 +0200)
and instead just have a singleton.  On Windows I saw a situation where
the first instantiation would use ASIO and the second WASAPI, causing
all kinds of confusion.

src/wx/audio_backend.cc [new file with mode: 0644]
src/wx/audio_backend.h [new file with mode: 0644]
src/wx/config_dialog.cc
src/wx/film_viewer.cc
src/wx/film_viewer.h
src/wx/wscript
src/wx/wx_util.h

diff --git a/src/wx/audio_backend.cc b/src/wx/audio_backend.cc
new file mode 100644 (file)
index 0000000..b77d2cd
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+    Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic 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 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "audio_backend.h"
+
+
+AudioBackend* AudioBackend::_instance = nullptr;
+
+
+#ifdef DCPOMATIC_LINUX
+auto constexpr api = RtAudio::LINUX_PULSE;
+#endif
+#ifdef DCPOMATIC_WINDOWS
+auto constexpr api = RtAudio::UNSPECIFIED;
+#endif
+#ifdef DCPOMATIC_OSX
+auto constexpr api = RtAudio::MACOSX_CORE;
+#endif
+
+
+AudioBackend::AudioBackend()
+#if (RTAUDIO_VERSION_MAJOR >= 6)
+       : _rtaudio(api, boost::bind(&AudioBackend::rtaudio_error_callback, this, _2))
+#else
+       : _rtaudio(api)
+#endif
+{
+
+}
+
+
+#if (RTAUDIO_VERSION_MAJOR >= 6)
+void
+AudioBackend::rtaudio_error_callback(string const& error)
+{
+       boost::mutex::scoped_lock lm(_last_rtaudio_error_mutex);
+       _last_rtaudio_error = error;
+}
+
+string
+AudioBackend::last_rtaudio_error() const
+{
+       boost::mutex::scoped_lock lm(_last_rtaudio_error_mutex);
+       return _last_rtaudio_error;
+}
+#endif
+
+
+AudioBackend*
+AudioBackend::instance()
+{
+       if (!_instance) {
+               _instance = new AudioBackend();
+       }
+
+       return _instance;
+}
+
+
diff --git a/src/wx/audio_backend.h b/src/wx/audio_backend.h
new file mode 100644 (file)
index 0000000..cf1bbad
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic 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 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <RtAudio.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+
+class AudioBackend
+{
+public:
+       AudioBackend();
+
+       AudioBackend(AudioBackend const&) = delete;
+       AudioBackend& operator=(AudioBackend const&) = delete;
+
+       RtAudio& rtaudio() {
+               return _rtaudio;
+       }
+
+#if (RTAUDIO_VERSION_MAJOR >= 6)
+       std::string last_rtaudio_error() const;
+#endif
+
+       static AudioBackend* instance();
+
+private:
+       static AudioBackend* _instance;
+
+       RtAudio _rtaudio;
+
+#if (RTAUDIO_VERSION_MAJOR >= 6)
+       void rtaudio_error_callback(std::string const& error);
+       mutable boost::mutex _last_rtaudio_error_mutex;
+       std::string _last_rtaudio_error;
+#endif
+};
+
index b4adc855e4af103398fd11a8c5a27cbc33c6a8c6..bbe2278ac84ba9fa83b7f8cd3081bc4639e5c0d7 100644 (file)
@@ -19,6 +19,7 @@
 */
 
 
+#include "audio_backend.h"
 #include "audio_mapping_view.h"
 #include "check_box.h"
 #include "config_dialog.h"
@@ -880,7 +881,8 @@ SoundPage::setup ()
        font.SetPointSize (font.GetPointSize() - 1);
        _sound_output_details->SetFont (font);
 
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
+       auto& audio = AudioBackend::instance()->rtaudio();
+
 #if (RTAUDIO_VERSION_MAJOR >= 6)
        for (auto device_id: audio.getDeviceIds()) {
                auto dev = audio.getDeviceInfo(device_id);
@@ -928,7 +930,8 @@ SoundPage::sound_changed ()
 void
 SoundPage::sound_output_changed ()
 {
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
+       auto& audio = AudioBackend::instance()->rtaudio();
+
        auto const so = get_sound_output();
        string default_device;
 #if (RTAUDIO_VERSION_MAJOR >= 6)
@@ -957,11 +960,12 @@ SoundPage::config_changed ()
        auto const current_so = get_sound_output ();
        optional<string> configured_so;
 
+       auto& audio = AudioBackend::instance()->rtaudio();
+
        if (config->sound_output()) {
                configured_so = config->sound_output().get();
        } else {
                /* No configured output means we should use the default */
-               RtAudio audio (DCPOMATIC_RTAUDIO_API);
 #if (RTAUDIO_VERSION_MAJOR >= 6)
                configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
 #else
@@ -985,8 +989,6 @@ SoundPage::config_changed ()
                }
        }
 
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
-
        map<int, wxString> apis;
        apis[RtAudio::MACOSX_CORE]    = _("CoreAudio");
        apis[RtAudio::WINDOWS_ASIO]   = _("ASIO");
index b6f8096e85dda7dcfb07a44a6e19fafb9781fc82..f83e83c68b1feef06ab229290f32662fcaf48bb8 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 
+#include "audio_backend.h"
 #include "closed_captions_dialog.h"
 #include "film_viewer.h"
 #include "gl_video_view.h"
@@ -86,12 +87,7 @@ rtaudio_callback (void* out, void *, unsigned int frames, double, RtAudioStreamS
 
 
 FilmViewer::FilmViewer (wxWindow* p)
-#if (RTAUDIO_VERSION_MAJOR >= 6)
-       : _audio(DCPOMATIC_RTAUDIO_API, boost::bind(&FilmViewer::rtaudio_error_callback, this, _2))
-#else
-       : _audio (DCPOMATIC_RTAUDIO_API)
-#endif
-       , _closed_captions_dialog (new ClosedCaptionsDialog(p, this))
+       : _closed_captions_dialog (new ClosedCaptionsDialog(p, this))
 {
 #if wxCHECK_VERSION(3, 1, 0)
        switch (Config::instance()->video_view_type()) {
@@ -242,6 +238,8 @@ FilmViewer::create_butler()
 
        DCPOMATIC_ASSERT(_player);
 
+       auto& audio = AudioBackend::instance()->rtaudio();
+
        _butler = std::make_shared<Butler>(
                _film,
                *_player,
@@ -252,7 +250,7 @@ FilmViewer::create_butler()
                j2k_gl_optimised ? Image::Alignment::COMPACT : Image::Alignment::PADDED,
                true,
                j2k_gl_optimised,
-               (Config::instance()->sound() && _audio.isStreamOpen()) ? Butler::Audio::ENABLED : Butler::Audio::DISABLED
+               (Config::instance()->sound() && audio.isStreamOpen()) ? Butler::Audio::ENABLED : Butler::Audio::DISABLED
                );
 
        _closed_captions_dialog->set_butler (_butler);
@@ -344,8 +342,10 @@ void
 FilmViewer::suspend ()
 {
        ++_suspended;
-       if (_audio.isStreamRunning()) {
-               _audio.abortStream();
+
+       auto& audio = AudioBackend::instance()->rtaudio();
+       if (audio.isStreamRunning()) {
+               audio.abortStream();
        }
 }
 
@@ -353,19 +353,21 @@ FilmViewer::suspend ()
 void
 FilmViewer::start_audio_stream_if_open ()
 {
-       if (_audio.isStreamOpen()) {
-               _audio.setStreamTime (_video_view->position().seconds());
+       auto& audio = AudioBackend::instance()->rtaudio();
+
+       if (audio.isStreamOpen()) {
+               audio.setStreamTime(_video_view->position().seconds());
 #if (RTAUDIO_VERSION_MAJOR >= 6)
-               if (_audio.startStream() != RTAUDIO_NO_ERROR) {
+               if (audio.startStream() != RTAUDIO_NO_ERROR) {
                        _audio_channels = 0;
                        error_dialog(
                                _video_view->get(),
-                               _("There was a problem starting audio playback.  Please try another audio output device in Preferences."), std_to_wx(last_rtaudio_error())
+                               _("There was a problem starting audio playback.  Please try another audio output device in Preferences."), std_to_wx(audio.last_rtaudio_error())
                                );
                }
 #else
                try {
-                       _audio.startStream ();
+                       audio.startStream ();
                } catch (RtAudioError& e) {
                        _audio_channels = 0;
                        error_dialog (
@@ -428,9 +430,11 @@ FilmViewer::start ()
 bool
 FilmViewer::stop ()
 {
-       if (_audio.isStreamRunning()) {
+       auto& audio = AudioBackend::instance()->rtaudio();
+
+       if (audio.isStreamRunning()) {
                /* stop stream and discard any remaining queued samples */
-               _audio.abortStream ();
+               audio.abortStream();
        }
 
        if (!_playing) {
@@ -618,16 +622,18 @@ FilmViewer::config_changed (Config::Property p)
                return;
        }
 
-       if (_audio.isStreamOpen ()) {
-               _audio.closeStream ();
+       auto& audio = AudioBackend::instance()->rtaudio();
+
+       if (audio.isStreamOpen()) {
+               audio.closeStream();
        }
 
-       if (Config::instance()->sound() && _audio.getDeviceCount() > 0) {
+       if (Config::instance()->sound() && audio.getDeviceCount() > 0) {
                optional<unsigned int> chosen_device_id;
 #if (RTAUDIO_VERSION_MAJOR >= 6)
                if (Config::instance()->sound_output()) {
-                       for (auto device_id: _audio.getDeviceIds()) {
-                               if (_audio.getDeviceInfo(device_id).name == Config::instance()->sound_output().get()) {
+                       for (auto device_id: audio.getDeviceIds()) {
+                               if (audio.getDeviceInfo(device_id).name == Config::instance()->sound_output().get()) {
                                        chosen_device_id = device_id;
                                        break;
                                }
@@ -635,26 +641,26 @@ FilmViewer::config_changed (Config::Property p)
                }
 
                if (!chosen_device_id) {
-                       chosen_device_id = _audio.getDefaultOutputDevice();
+                       chosen_device_id = audio.getDefaultOutputDevice();
                }
-               _audio_channels = _audio.getDeviceInfo(*chosen_device_id).outputChannels;
+               _audio_channels = audio.getDeviceInfo(*chosen_device_id).outputChannels;
                RtAudio::StreamParameters sp;
                sp.deviceId = *chosen_device_id;
                sp.nChannels = _audio_channels;
                sp.firstChannel = 0;
-               if (_audio.openStream(&sp, 0, RTAUDIO_FLOAT32, 48000, &_audio_block_size, &rtaudio_callback, this) != RTAUDIO_NO_ERROR) {
+               if (audio.openStream(&sp, 0, RTAUDIO_FLOAT32, 48000, &_audio_block_size, &rtaudio_callback, this) != RTAUDIO_NO_ERROR) {
                        _audio_channels = 0;
                        error_dialog(
                                _video_view->get(),
-                               _("Could not set up audio output.  There will be no audio during the preview."), std_to_wx(last_rtaudio_error())
+                               _("Could not set up audio output.  There will be no audio during the preview."), std_to_wx(audio.last_rtaudio_error())
                                );
                }
 #else
                unsigned int st = 0;
                if (Config::instance()->sound_output()) {
-                       while (st < _audio.getDeviceCount()) {
+                       while (st < audio.getDeviceCount()) {
                                try {
-                                       if (_audio.getDeviceInfo(st).name == Config::instance()->sound_output().get()) {
+                                       if (audio.getDeviceInfo(st).name == Config::instance()->sound_output().get()) {
                                                break;
                                        }
                                } catch (RtAudioError&) {
@@ -662,28 +668,28 @@ FilmViewer::config_changed (Config::Property p)
                                }
                                ++st;
                        }
-                       if (st == _audio.getDeviceCount()) {
+                       if (st == audio.getDeviceCount()) {
                                try {
-                                       st = _audio.getDefaultOutputDevice();
+                                       st = audio.getDefaultOutputDevice();
                                } catch (RtAudioError&) {
                                        /* Something went wrong with that device so we don't want to use it anyway */
                                }
                        }
                } else {
                        try {
-                               st = _audio.getDefaultOutputDevice();
+                               st = audio.getDefaultOutputDevice();
                        } catch (RtAudioError&) {
                                /* Something went wrong with that device so we don't want to use it anyway */
                        }
                }
 
                try {
-                       _audio_channels = _audio.getDeviceInfo(st).outputChannels;
+                       _audio_channels = audio.getDeviceInfo(st).outputChannels;
                        RtAudio::StreamParameters sp;
                        sp.deviceId = st;
                        sp.nChannels = _audio_channels;
                        sp.firstChannel = 0;
-                       _audio.openStream (&sp, 0, RTAUDIO_FLOAT32, 48000, &_audio_block_size, &rtaudio_callback, this);
+                       audio.openStream(&sp, 0, RTAUDIO_FLOAT32, 48000, &_audio_block_size, &rtaudio_callback, this);
                } catch (RtAudioError& e) {
                        _audio_channels = 0;
                        error_dialog (
@@ -704,8 +710,10 @@ FilmViewer::config_changed (Config::Property p)
 DCPTime
 FilmViewer::uncorrected_time () const
 {
-       if (_audio.isStreamRunning()) {
-               return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime());
+       auto& audio = AudioBackend::instance()->rtaudio();
+
+       if (audio.isStreamRunning()) {
+               return DCPTime::from_seconds(audio.getStreamTime());
        }
 
        return _video_view->position();
@@ -715,11 +723,13 @@ FilmViewer::uncorrected_time () const
 optional<DCPTime>
 FilmViewer::audio_time () const
 {
-       if (!_audio.isStreamRunning()) {
+       auto& audio = AudioBackend::instance()->rtaudio();
+
+       if (!audio.isStreamRunning()) {
                return {};
        }
 
-       return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime ()) -
+       return DCPTime::from_seconds(audio.getStreamTime()) -
                DCPTime::from_frames (average_latency(), _film->audio_frame_rate());
 }
 
@@ -743,13 +753,15 @@ FilmViewer::audio_callback (void* out_p, unsigned int frames)
                /* The audio we just got was (very) late; drop it and get some more. */
        }
 
-        boost::mutex::scoped_lock lm (_latency_history_mutex, boost::try_to_lock);
-        if (lm) {
-                _latency_history.push_back (_audio.getStreamLatency ());
-                if (_latency_history.size() > static_cast<size_t> (_latency_history_count)) {
-                        _latency_history.pop_front ();
-                }
-        }
+       auto& audio = AudioBackend::instance()->rtaudio();
+
+       boost::mutex::scoped_lock lm (_latency_history_mutex, boost::try_to_lock);
+       if (lm) {
+               _latency_history.push_back(audio.getStreamLatency());
+               if (_latency_history.size() > static_cast<size_t> (_latency_history_count)) {
+                       _latency_history.pop_front ();
+               }
+       }
 
        return 0;
 }
@@ -898,21 +910,3 @@ FilmViewer::unset_crop_guess ()
        _video_view->update ();
 }
 
-
-#if (RTAUDIO_VERSION_MAJOR >= 6)
-void
-FilmViewer::rtaudio_error_callback(string const& error)
-{
-       boost::mutex::scoped_lock lm(_last_rtaudio_error_mutex);
-       _last_rtaudio_error = error;
-}
-
-
-string
-FilmViewer::last_rtaudio_error() const
-{
-       boost::mutex::scoped_lock lm(_last_rtaudio_error_mutex);
-       return _last_rtaudio_error;
-}
-#endif
-
index 5824f8baabeba7ca8e233a43a75d4db5ac1b1aad..a0aac62dd920da07ec597e29175e59efefffd4c4 100644 (file)
@@ -171,13 +171,6 @@ private:
        void ui_finished ();
        void start_audio_stream_if_open ();
 
-#if (RTAUDIO_VERSION_MAJOR >= 6)
-       void rtaudio_error_callback(std::string const& error);
-       mutable boost::mutex _last_rtaudio_error_mutex;
-       std::string _last_rtaudio_error;
-       std::string last_rtaudio_error() const;
-#endif
-
        dcpomatic::DCPTime uncorrected_time () const;
        Frame average_latency () const;
 
@@ -190,7 +183,6 @@ private:
        bool _coalesce_player_changes = false;
        std::vector<int> _pending_player_changes;
 
-       RtAudio _audio;
        int _audio_channels = 0;
        unsigned int _audio_block_size = 1024;
        bool _playing = false;
index a6eefa69f6ab452846f9febbc8caa6d8810d1161..f52f71afc7a52e12d4cee09bca44888c16bb1949 100644 (file)
@@ -26,6 +26,7 @@ import i18n
 
 sources = """
           about_dialog.cc
+          audio_backend.cc
           audio_dialog.cc
           audio_gain_dialog.cc
           audio_mapping_view.cc
index 66b01640c5b1175583fee8898c5f7bc11335a88a..c85af1d4f1e932245309c6ae4789291d40ba30d2 100644 (file)
@@ -86,16 +86,6 @@ class PasswordEntry;
 #define DCPOMATIC_BUTTON_STACK_GAP 0
 #endif
 
-#ifdef DCPOMATIC_LINUX
-#define DCPOMATIC_RTAUDIO_API RtAudio::LINUX_PULSE
-#endif
-#ifdef DCPOMATIC_WINDOWS
-#define DCPOMATIC_RTAUDIO_API RtAudio::UNSPECIFIED
-#endif
-#ifdef DCPOMATIC_OSX
-#define DCPOMATIC_RTAUDIO_API RtAudio::MACOSX_CORE
-#endif
-
 
 /** i18n macro to support strings like Context|String
  *  so that `String' can be translated to different things