From c6938ce49e88383ed109ffb0b4663b9091668b43 Mon Sep 17 00:00:00 2001 From: zeldakatze Date: Wed, 10 Apr 2024 23:11:58 +0200 Subject: fix build for haiku diff --git a/CMakeLists.txt b/CMakeLists.txt index 1551d0a..f476113 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,10 @@ if (MINGW OR WIN32) link_libraries(ws2_32) endif () +if (HAIKU) + link_libraries(network) +endif () + if (WIN32) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) add_compile_definitions(NOMINMAX) @@ -70,7 +74,8 @@ if (NOT (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")) target_precompile_headers(abaddon PRIVATE src/abaddon.hpp src/util.hpp) endif () -if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1")) +if (NOT (CMAKE_SYSTEM_NAME STREQUAL "Haiku") AND +((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1"))) target_link_libraries(abaddon stdc++fs) endif () diff --git a/cmake/Findatkmm.cmake b/cmake/Findatkmm.cmake index 68c3bc6..623d0e1 100644 --- a/cmake/Findatkmm.cmake +++ b/cmake/Findatkmm.cmake @@ -25,7 +25,8 @@ find_path(ATKMM_CONFIG_INCLUDE_DIR /usr/lib /usr/local/lib /opt/local/lib - PATH_SUFFIXES ${ATKMM_LIBRARY_NAME}/include) + PATH_SUFFIXES ${ATKMM_LIBRARY_NAME}/include + ${ATKMM_LIBRARY_NAME}) find_library(ATKMM_LIBRARY NAMES ${ATKMM_LIBRARY_NAME} diff --git a/cmake/Findgdkmm.cmake b/cmake/Findgdkmm.cmake index 5316bb7..23077ca 100644 --- a/cmake/Findgdkmm.cmake +++ b/cmake/Findgdkmm.cmake @@ -1,4 +1,5 @@ set(gdkmm_LIBRARY_NAME gdkmm-3.0) +set(gtkmm_LIBRARY_NAME gtkmm-3.0) find_package(PkgConfig) if (PKG_CONFIG_FOUND) @@ -23,7 +24,8 @@ find_path(gdkmm_CONFIG_INCLUDE_DIR /usr/lib /usr/local/lib /opt/local/lib - PATH_SUFFIXES ${gdkmm_LIBRARY_NAME}/include) + PATH_SUFFIXES ${gdkmm_LIBRARY_NAME}/include + ${gtkmm_LIBRARY_NAME}) find_library(gdkmm_LIBRARY NAMES ${gdkmm_LIBRARY_NAME} diff --git a/cmake/Findglibmm.cmake b/cmake/Findglibmm.cmake index 27220e6..11bd996 100644 --- a/cmake/Findglibmm.cmake +++ b/cmake/Findglibmm.cmake @@ -22,7 +22,8 @@ find_path(GLIBMM_INCLUDE_DIR find_path(GLIBMM_CONFIG_INCLUDE_DIR NAMES glibmmconfig.h HINTS ${GLIBMM_LIBRARY_HINTS} - PATH_SUFFIXES ${GLIBMM_LIBRARY_NAME}/include) + PATH_SUFFIXES ${GLIBMM_LIBRARY_NAME}/include + ${GLIBMM_LIBRARY_NAME}) find_library(GLIBMM_LIBRARY NAMES ${GLIBMM_LIBRARY_NAME} diff --git a/cmake/Findgtkmm.cmake b/cmake/Findgtkmm.cmake index addbede..908a08c 100644 --- a/cmake/Findgtkmm.cmake +++ b/cmake/Findgtkmm.cmake @@ -32,12 +32,15 @@ find_path(GTKMM_INCLUDE_DIR find_path(GTKMM_CONFIG_INCLUDE_DIR NAMES gtkmmconfig.h HINTS ${GTKMM_LIBRARY_HINTS} + PATH_SUFFIXES ${GTKMM_LIBRARY_NAME} PATH_SUFFIXES ${GTKMM_LIBRARY_NAME}/include) find_library(GTKMM_LIB NAMES ${GTKMM_LIBRARY_NAME} gtkmm HINTS ${GTKMM_LIBRARY_HINTS} + HINTS ${GTKMM_INCLUDE_DIR} + PATH ${GTKMM_INCLUDE_DIR} PATH_SUFFIXES ${GTKMM_LIBRARY_NAME} ${GTKMM_LIBRARY_NAME}/include) diff --git a/cmake/Findsigc++.cmake b/cmake/Findsigc++.cmake index e0f56c0..d81eb15 100644 --- a/cmake/Findsigc++.cmake +++ b/cmake/Findsigc++.cmake @@ -19,7 +19,8 @@ find_path(SIGC++_INCLUDE_DIR find_path(SIGC++_CONFIG_INCLUDE_DIR NAMES sigc++config.h HINTS ${SIGC++_LIBRARY_HINTS} - PATH_SUFFIXES ${SIGC++_LIBRARY_NAME}/include) + PATH_SUFFIXES ${SIGC++_LIBRARY_NAME}/include + ${SIGC++_LIBRARY_NAME}) find_library(SIGC++_LIBRARY NAMES ${SIGC++_LIBRARY_FILE} -- 2.48.1 From a3bb805cba1bcdf7c3db1258538a11223257718f Mon Sep 17 00:00:00 2001 From: zeldakatze Date: Thu, 11 Apr 2024 00:16:45 +0200 Subject: abaddon: add haiku platform code diff --git a/src/platform.cpp b/src/platform.cpp index 726655b..de75e8f 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -224,6 +224,56 @@ std::string Platform::FindStateCacheFolder() { return home_path; } +#elif __HAIKU__ +#include +#include + +std::string Platform::FindResourceFolder() { + static std::string found_path; + static bool found = false; + if (found) return found_path; + + dev_t volume = dev_for_path("/boot"); + char buffer[B_PATH_NAME_LENGTH+B_FILE_NAME_LENGTH]; + find_directory(B_SYSTEM_DATA_DIRECTORY, volume, false, buffer, sizeof(buffer)); + strcat(buffer, "/abaddon/"); + found_path = std::string(buffer); + return found_path; +} + +std::string Platform::FindConfigFile() { + const auto cfg = std::getenv("ABADDON_CONFIG"); + if (cfg != nullptr) return cfg; + + static std::string found_path; + static bool found = false; + if (found) return found_path; + + dev_t volume = dev_for_path("/boot"); + char buffer[B_PATH_NAME_LENGTH+B_FILE_NAME_LENGTH]; + char cmdbuffer[B_PATH_NAME_LENGTH+B_FILE_NAME_LENGTH] = "mkdir -p "; + find_directory(B_USER_SETTINGS_DIRECTORY, volume, false, buffer, sizeof(buffer)); + strcat(buffer, "/abaddon/"); + strcat(cmdbuffer, buffer); + system(cmdbuffer); + strcat(buffer, "abaddon.ini"); + found_path = std::string(buffer); + return found_path; +} + +std::string Platform::FindStateCacheFolder() { + static std::string found_path; + static bool found = false; + if (found) return found_path; + + dev_t volume = dev_for_path("/boot"); + char buffer[B_PATH_NAME_LENGTH+B_FILE_NAME_LENGTH]; + find_directory(B_SYSTEM_CACHE_DIRECTORY, volume, false, buffer, sizeof(buffer)); + strcat(buffer, "/abaddon/"); + found_path = std::string(buffer); + return found_path; +} + #else std::string Platform::FindResourceFolder() { -- 2.48.1 From 014f8b78347807035b49daaae3bda14800d30672 Mon Sep 17 00:00:00 2001 From: zeldakatze Date: Thu, 7 Aug 2025 20:39:54 +0200 Subject: add a portaudio sound interface diff --git a/CMakeLists.txt b/CMakeLists.txt index be6b650..9b1a81d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ option(USE_KEYCHAIN "Store the token in the keychain (default)" ON) option(ENABLE_NOTIFICATION_SOUNDS "Enable notification sounds (default)" ON) option(ENABLE_RNNOISE "Enable RNNoise for voice activity detection (default)" ON) option(ENABLE_QRCODE_LOGIN "Enable QR code login (default)" ON) +option(USE_PORTAUDIO "Use portaudio instead of miniaudio" OFF) find_package(nlohmann_json REQUIRED) find_package(CURL) @@ -162,7 +163,12 @@ if (ENABLE_VOICE) find_package(PkgConfig) - set(USE_MINIAUDIO TRUE) + if (USE_PORTAUDIO) + set(USE_PORTAUDIO TRUE) + else () + set(USE_MINIAUDIO TRUE) + endif () + pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus) target_link_libraries(abaddon PkgConfig::Opus) @@ -206,7 +212,12 @@ if (ENABLE_VOICE) endif () if (${ENABLE_NOTIFICATION_SOUNDS}) - set(USE_MINIAUDIO TRUE) + if (USE_PORTAUDIO) + set(USE_PORTAUDIO TRUE) + else () + set(USE_MINIAUDIO TRUE) + endif () + target_compile_definitions(abaddon PRIVATE ENABLE_NOTIFICATION_SOUNDS) endif () @@ -221,6 +232,12 @@ if (USE_MINIAUDIO) target_compile_definitions(abaddon PRIVATE WITH_MINIAUDIO) endif () +if (USE_PORTAUDIO) +# find_package(portaudio-2.0) + target_compile_definitions(abaddon PRIVATE USE_PORTAUDIO) + target_link_libraries(abaddon portaudio) +endif() + set(ABADDON_COMPILER_DEFS "" CACHE STRING "Additional compiler definitions") foreach (COMPILER_DEF IN LISTS ABADDON_COMPILER_DEFS) target_compile_definitions(abaddon PRIVATE "${COMPILER_DEF}") diff --git a/src/audio/devices.cpp b/src/audio/devices.cpp index dfb7164..09fc156 100644 --- a/src/audio/devices.cpp +++ b/src/audio/devices.cpp @@ -21,6 +21,75 @@ Glib::RefPtr AudioDevices::GetCaptureDeviceModel() { return m_capture; } +#ifdef USE_PORTAUDIO +void AudioDevices::SetDevices() { + const PaDeviceInfo *deviceInfo; + int i; + + /* clear the lists */ + m_playback->clear(); + m_capture->clear(); + + /* iterate over all available devices */ + int numDevices = Pa_GetDeviceCount(); + +#if 0 + for(i=0; i 0 */ + if(deviceInfo->maxInputChannels > 0) { + auto row = *m_playback->append(); + row[m_playback_columns.Name] = deviceInfo->name; + row[m_playback_columns.DeviceID] = i; + + /* if it is the default, mark it as such */ + if(i == Pa_GetHostApiInfo(deviceInfo->hostApi)->defaultInputDevice) { + m_default_capture_iter = row; + SetActiveCaptureDevice(row); + } + } + + /* add it as an input if maxInputChannels > 0 */ + if(deviceInfo->maxOutputChannels > 0) { + auto row = *m_capture->append(); + row[m_capture_columns.Name] = deviceInfo->name; + row[m_capture_columns.DeviceID] = i; + + /* if it is the default, mark it as such */ + if(i == Pa_GetHostApiInfo( deviceInfo->hostApi )->defaultOutputDevice) { + m_default_playback_iter = row; + SetActivePlaybackDevice(row); + } + } + + } +#endif + { + auto row = *m_playback->append(); + row[m_playback_columns.Name] = "Default Output"; + row[m_playback_columns.DeviceID] = Pa_GetDefaultOutputDevice(); + m_default_playback_iter = row; + SetActivePlaybackDevice(row); + } + + { + auto row = *m_capture->append(); + row[m_capture_columns.Name] = "Default Input"; + row[m_capture_columns.DeviceID] = Pa_GetDefaultInputDevice(); + m_default_capture_iter = row; + SetActiveCaptureDevice(row); + } + + if (!m_default_playback_iter) { + spdlog::get("audio")->warn("No default playback device found"); + } + + if (!m_default_capture_iter) { + spdlog::get("audio")->warn("No default capture device found"); + } +} +#else /* USE_PORTAUDIO */ void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count) { m_playback->clear(); @@ -60,7 +129,39 @@ void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_coun spdlog::get("audio")->warn("No default capture device found"); } } +#endif /* USE_PORTAUDIO */ +#ifdef USE_PORTAUDIO +std::optional AudioDevices::GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { + if (iter) { + return static_cast((*iter)[m_playback_columns.DeviceID]); + } + + return std::nullopt; +} + +std::optional AudioDevices::GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { + if (iter) { + return static_cast((*iter)[m_capture_columns.DeviceID]); + } + + return std::nullopt; +} + +std::optional AudioDevices::GetDefaultPlayback() const { + PaDeviceIndex pi = Pa_GetHostApiInfo(Pa_GetHostApiCount()-1)->defaultOutputDevice; + if(pi == paNoDevice) + return std::nullopt; + return pi; +} + +std::optional AudioDevices::GetDefaultCapture() const { + PaDeviceIndex pi = Pa_GetHostApiInfo(Pa_GetHostApiCount()-1)->defaultInputDevice; + if(pi == paNoDevice) + return std::nullopt; + return pi; +} +#else /* USE_PORTAUDIO */ std::optional AudioDevices::GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { if (iter) { return static_cast((*iter)[m_playback_columns.DeviceID]); @@ -92,6 +193,7 @@ std::optional AudioDevices::GetDefaultCapture() const { return std::nullopt; } +#endif /* USE_PORTAUDIO */ void AudioDevices::SetActivePlaybackDevice(const Gtk::TreeModel::iterator &iter) { m_active_playback_iter = iter; diff --git a/src/audio/devices.hpp b/src/audio/devices.hpp index c83bdb4..54d1713 100644 --- a/src/audio/devices.hpp +++ b/src/audio/devices.hpp @@ -4,7 +4,13 @@ // clang-format off #include + +#ifndef USE_PORTAUDIO #include +#else +#include +#endif + #include // clang-format on @@ -16,13 +22,26 @@ public: Glib::RefPtr GetPlaybackDeviceModel(); Glib::RefPtr GetCaptureDeviceModel(); + #ifndef USE_PORTAUDIO void SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count); + #else + /* call Pa directly */ + void SetDevices(); + #endif +#ifdef USE_PORTAUDIO + [[nodiscard]] std::optional GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; + [[nodiscard]] std::optional GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; + + [[nodiscard]] std::optional GetDefaultPlayback() const; + [[nodiscard]] std::optional GetDefaultCapture() const; +#else [[nodiscard]] std::optional GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; [[nodiscard]] std::optional GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; [[nodiscard]] std::optional GetDefaultPlayback() const; [[nodiscard]] std::optional GetDefaultCapture() const; +#endif void SetActivePlaybackDevice(const Gtk::TreeModel::iterator &iter); void SetActiveCaptureDevice(const Gtk::TreeModel::iterator &iter); @@ -36,7 +55,11 @@ private: PlaybackColumns(); Gtk::TreeModelColumn Name; +#ifdef USE_PORTAUDIO + Gtk::TreeModelColumn DeviceID; +#else Gtk::TreeModelColumn DeviceID; +#endif }; PlaybackColumns m_playback_columns; Glib::RefPtr m_playback; @@ -48,7 +71,11 @@ private: CaptureColumns(); Gtk::TreeModelColumn Name; +#ifndef USE_PORTAUDIO Gtk::TreeModelColumn DeviceID; +#else /* USE_PORTAUDIO */ + Gtk::TreeModelColumn DeviceID; +#endif /* USE_PORTAUDIO */ }; CaptureColumns m_capture_columns; Glib::RefPtr m_capture; diff --git a/src/audio/ma_impl.cpp b/src/audio/ma_impl.cpp index 531b24f..767dec1 100644 --- a/src/audio/ma_impl.cpp +++ b/src/audio/ma_impl.cpp @@ -1,7 +1,9 @@ -#ifdef WITH_MINIAUDIO - #define MINIAUDIO_IMPLEMENTATION - #ifdef __APPLE__ - #define MA_NO_RUNTIME_LINKING - #endif - #include +#ifndef USE_PORTAUDIO + #ifdef WITH_MINIAUDIO + #define MINIAUDIO_IMPLEMENTATION + #ifdef __APPLE__ + #define MA_NO_RUNTIME_LINKING + #endif + #include + #endif #endif diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 5716fc5..a657dcc 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -11,7 +11,13 @@ #include #include #include + +#ifndef USE_PORTAUDIO #include +#else +#include +#endif + #include #include #include @@ -80,7 +86,9 @@ public: void SetVADMethod(VADMethod method); VADMethod GetVADMethod() const; +#ifndef USE_PORTAUDIO static std::vector ParseBackendsList(const Glib::ustring &list); +#endif /* USE_PORTAUDIO */ #ifdef WITH_RNNOISE float GetCurrentVADProbability() const; @@ -94,10 +102,18 @@ public: bool GetMixMono() const; private: +#ifndef USE_PORTAUDIO void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames); +#else /* USE_PORTAUDIO */ + void OnCapturedPCM(const int16_t *pcm, uint32_t frames); +#endif /* USE_PORTAUDIO */ void UpdateReceiveVolume(uint32_t ssrc, const int16_t *pcm, int frames); +#ifndef USE_PORTAUDIO void UpdateCaptureVolume(const int16_t *pcm, ma_uint32 frames); +#else + void UpdateCaptureVolume(const int16_t *pcm, uint32_t frames); +#endif /* USE_PORTAUDIO */ std::atomic m_capture_peak_meter = 0; bool DecayVolumeMeters(); @@ -111,13 +127,24 @@ private: void RNNoiseUninitialize(); #endif +#ifndef USE_PORTAUDIO friend void data_callback(ma_device *, void *, const void *, ma_uint32); friend void capture_data_callback(ma_device *, void *, const void *, ma_uint32); +#else /* USE_PORTAUDIO */ + friend int playCallback(const void *pInput, void *pOutput, unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ); + friend int recordCallback(const void *pInput, void *pOutput, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); +#endif /* USE_PORTAUDIO */ std::thread m_thread; bool m_ok; +#ifndef USE_PORTAUDIO // playback ma_device m_playback_device; ma_device_config m_playback_config; @@ -126,8 +153,18 @@ private: ma_device m_capture_device; ma_device_config m_capture_config; ma_device_id m_capture_id; - + ma_context m_context; +#else + // playback + PaStream* pa_playback_device; + PaStreamParameters m_playback_config; + // capture + PaStream* pa_capture_device; + PaStreamParameters m_capture_config; + +#endif + mutable std::mutex m_mutex; mutable std::mutex m_enc_mutex; @@ -166,7 +203,9 @@ private: #endif std::atomic m_rtp_timestamp = 0; +#ifndef USE_PORTAUDIO ma_log m_ma_log; +#endif std::shared_ptr m_log; public: diff --git a/src/audio/manager.cpp b/src/audio/miniaudioManager.cpp similarity index 99% rename from src/audio/manager.cpp rename to src/audio/miniaudioManager.cpp index d32c5e2..3a155fc 100644 --- a/src/audio/manager.cpp +++ b/src/audio/miniaudioManager.cpp @@ -1,3 +1,4 @@ +#ifndef USE_PORTAUDIO #ifdef WITH_VOICE // clang-format off @@ -704,3 +705,4 @@ AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { } #endif +#endif /* USE_PORTAUDIO */ diff --git a/src/audio/portaudioManager.cpp b/src/audio/portaudioManager.cpp new file mode 100644 index 0000000..bcd1254 --- /dev/null +++ b/src/audio/portaudioManager.cpp @@ -0,0 +1,601 @@ +#ifdef WITH_VOICE +#ifdef USE_PORTAUDIO + +#include "manager.hpp" +#include "abaddon.hpp" +#include +#include +#include +#include +#include +#include + +int playCallback(const void *pInput, void *pOutput, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) { + AudioManager *mgr = reinterpret_cast(userData); + if (mgr == nullptr) {printf("NULL\n");return paContinue;}; + std::lock_guard _(mgr->m_mutex); + + auto *pOutputF32 = static_cast(pOutput); + + /* clear the output buffer */ + for(int i = 0; im_sources) { + double volume = 1.0; + if (const auto vol_it = mgr->m_volume_ssrc.find(ssrc); vol_it != mgr->m_volume_ssrc.end()) { + volume = vol_it->second; + } + auto &buf = pair.first; + const size_t n = std::min(static_cast(buf.size()), static_cast(frameCount * 2ULL)); + for (size_t i = 0; i < n; i++) { + pOutputF32[i] += (int16_t) (volume * buf[i]); + //pOutputF32[i] += volume * buf[i] / 32768.F; + } + buf.erase(buf.begin(), buf.begin() + n); + } + + return paContinue; +} + +int recordCallback(const void *pInput, void *pOutput, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) { + auto *mgr = reinterpret_cast(userData); + if (mgr == nullptr) return paContinue; + + mgr->OnCapturedPCM(static_cast(pInput), frameCount); + + /* + * You can simply increment it by 480 in UDPSocket::SendEncrypted but this is wrong + * The timestamp is supposed to be strictly linear eg. if there's discontinuous + * transmission for 1 second then the timestamp should be 48000 greater than the + * last packet. So it's incremented here because this is fired 100x per second + * and is always called in sync with UDPSocket::SendEncrypted + */ + mgr->m_rtp_timestamp += 480; + return paContinue; +} + +AudioManager::AudioManager(const Glib::ustring &backends_string) + : m_log(spdlog::stdout_color_mt("portaudio")) { + m_ok = true; + + PaError pa_err; + int opus_err; + + /* try to initialize PortAudio */ + pa_err = Pa_Initialize(); + if(pa_err != paNoError) { + spdlog::get("audio")->error("ERROR: Pa_Initialize returned 0x%x\n", pa_err); + Pa_Terminate(); + m_ok = false; + return; + } + + /* initialize RNNoise */ +#ifdef WITH_RNNOISE + RNNoiseInitialize(); +#endif + + /* try to initialize opus */ + m_encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_VOIP, &opus_err); + if (opus_err != OPUS_OK) { + spdlog::get("audio")->error("failed to initialize opus encoder: {}", opus_err); + m_ok = false; + return; + } + opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(64000)); + + /* get the backends */ + Enumerate(); + + /* create the configurations for capture and playback */ + m_playback_config.sampleFormat = paInt16; + m_playback_config.channelCount = 2; + m_playback_config.suggestedLatency = Pa_GetDeviceInfo( m_capture_config.device )->defaultLowInputLatency; + m_playback_config.hostApiSpecificStreamInfo = NULL; + m_playback_config.device = Pa_GetDefaultOutputDevice(); + + m_capture_config.sampleFormat = paInt16; + m_capture_config.channelCount = 2; + m_capture_config.suggestedLatency = Pa_GetDeviceInfo( m_capture_config.device )->defaultLowInputLatency; + m_capture_config.hostApiSpecificStreamInfo = NULL; + m_capture_config.device = Pa_GetDefaultInputDevice(); + + /* create the streams */ + pa_err = Pa_OpenStream(&pa_capture_device, &m_capture_config, NULL, 48000, 480, paClipOff, recordCallback, this); + if(pa_err != paNoError) { + m_ok = false; + spdlog::get("audio")->error("ERROR: Pa_OpenStream for capture returned %d\n", pa_err); + return; + } + + pa_err = Pa_OpenStream(&pa_playback_device, NULL, &m_playback_config, 48000, 480, paClipOff, playCallback, this); + if(pa_err != paNoError) { + m_ok = false; + spdlog::get("audio")->error("ERROR: Pa_OpenStream for playback returned %d\n", pa_err); + return; + } + + if(Pa_StartStream(pa_playback_device) != paNoError) { + spdlog::get("audio")->error("ERROR: Could not start playback\n"); + } +/**TODO + if (const auto capture_id = m_devices.GetDefaultCapture(); capture_id.has_value()) { + m_capture_id = *capture_id; + m_capture_config.capture.pDeviceID = &m_capture_id; + }*/ + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &AudioManager::DecayVolumeMeters), 40); +} + +AudioManager::~AudioManager() { + Pa_Terminate(); + RemoveAllSSRCs(); + +#ifdef WITH_RNNOISE + RNNoiseUninitialize(); +#endif +} + +void AudioManager::AddSSRC(uint32_t ssrc) { + std::lock_guard _(m_mutex); + int error; + if (m_sources.find(ssrc) == m_sources.end()) { + auto *decoder = opus_decoder_create(48000, 2, &error); + m_sources.insert(std::make_pair(ssrc, std::make_pair(std::deque {}, decoder))); + } +} + +void AudioManager::RemoveSSRC(uint32_t ssrc) { + std::lock_guard _(m_mutex); + if (auto it = m_sources.find(ssrc); it != m_sources.end()) { + opus_decoder_destroy(it->second.second); + m_sources.erase(it); + } +} + +void AudioManager::RemoveAllSSRCs() { + spdlog::get("audio")->info("removing all ssrc"); + std::lock_guard _(m_mutex); + for (auto &[ssrc, pair] : m_sources) { + opus_decoder_destroy(pair.second); + } + m_sources.clear(); +} + +void AudioManager::SetOpusBuffer(uint8_t *ptr) { + m_opus_buffer = ptr; +} + +void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { + if (!m_should_playback || Pa_IsStreamActive(pa_playback_device) != 1) return; + + std::lock_guard _(m_mutex); + if (m_muted_ssrcs.find(ssrc) != m_muted_ssrcs.end()) return; + + static std::array pcm; + if (auto it = m_sources.find(ssrc); it != m_sources.end()) { + int decoded = opus_decode(it->second.second, data.data(), static_cast(data.size()), pcm.data(), 120 * 48, 0); + if (decoded <= 0) { + } else { + UpdateReceiveVolume(ssrc, pcm.data(), decoded); + auto &buf = it->second.first; + buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2); + } + } +} + +void AudioManager::StartCaptureDevice() { + if (Pa_StartStream(pa_capture_device) != paNoError) { + spdlog::get("audio")->error("Failed to start capture device"); + } +} + +void AudioManager::StopCaptureDevice() { + if (Pa_StopStream(pa_capture_device) != paNoError) { + spdlog::get("audio")->error("Failed to stop capture device"); + } +} + +void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { + PaError pa_err; + spdlog::get("audio")->debug("Setting new playback device"); + + const auto device_id = m_devices.GetPlaybackDeviceIDFromModel(iter); + if (!device_id) { + spdlog::get("audio")->error("Requested ID from iterator is invalid"); + return; + } + + m_devices.SetActivePlaybackDevice(iter); + + // TODO m_playback_config.device = *device_id; + + Pa_AbortStream(pa_playback_device); + pa_err = Pa_OpenStream(&pa_playback_device, NULL, &m_playback_config, 48000, 480, paClipOff, playCallback, this); + if(pa_err != paNoError) { + spdlog::get("audio")->error("Error setting playback device"); + } + + Pa_StartStream(pa_playback_device); + +} + +void AudioManager::SetCaptureDevice(const Gtk::TreeModel::iterator &iter) { + PaError pa_err; + spdlog::get("audio")->debug("Setting new capture device"); + + const auto device_id = m_devices.GetCaptureDeviceIDFromModel(iter); + if (!device_id) { + spdlog::get("audio")->error("Requested ID from iterator is invalid"); + return; + } + + m_devices.SetActiveCaptureDevice(iter); + + //TODO m_playback_config.device = *device_id; + + Pa_AbortStream(pa_playback_device); + pa_err = Pa_OpenStream(&pa_capture_device, &m_capture_config, NULL, 48000, 480, paClipOff, recordCallback, this); + + Pa_StartStream(pa_playback_device); +} + +void AudioManager::SetCapture(bool capture) { + m_should_capture = capture; +} + +void AudioManager::SetPlayback(bool playback) { + m_should_playback = playback; +} + +void AudioManager::SetCaptureGate(double gate) { + m_capture_gate = gate; +} + +void AudioManager::SetCaptureGain(double gain) { + m_capture_gain = gain; +} + +double AudioManager::GetCaptureGate() const noexcept { + return m_capture_gate; +} + +double AudioManager::GetCaptureGain() const noexcept { + return m_capture_gain; +} + +void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { + std::lock_guard _(m_mutex); + if (mute) { + m_muted_ssrcs.insert(ssrc); + } else { + m_muted_ssrcs.erase(ssrc); + } +} + +void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { + std::lock_guard _(m_mutex); + m_volume_ssrc[ssrc] = volume; +} + +double AudioManager::GetVolumeSSRC(uint32_t ssrc) const { + std::lock_guard _(m_mutex); + if (const auto iter = m_volume_ssrc.find(ssrc); iter != m_volume_ssrc.end()) { + return iter->second; + } + return 1.0; +} + +void AudioManager::SetEncodingApplication(int application) { + std::lock_guard _(m_enc_mutex); + int prev_bitrate = 64000; + if (int err = opus_encoder_ctl(m_encoder, OPUS_GET_BITRATE(&prev_bitrate)); err != OPUS_OK) { + spdlog::get("audio")->error("Failed to get old bitrate when reinitializing: {}", err); + } + opus_encoder_destroy(m_encoder); + int err = 0; + m_encoder = opus_encoder_create(48000, 2, application, &err); + if (err != OPUS_OK) { + spdlog::get("audio")->critical("opus_encoder_create failed: {}", err); + return; + } + + if (int err = opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(prev_bitrate)); err != OPUS_OK) { + spdlog::get("audio")->error("Failed to set bitrate when reinitializing: {}", err); + } +} + +int AudioManager::GetEncodingApplication() { + std::lock_guard _(m_enc_mutex); + int temp = OPUS_APPLICATION_VOIP; + if (int err = opus_encoder_ctl(m_encoder, OPUS_GET_APPLICATION(&temp)); err != OPUS_OK) { + spdlog::get("audio")->error("opus_encoder_ctl(OPUS_GET_APPLICATION) failed: {}", err); + } + return temp; +} + +void AudioManager::SetSignalHint(int signal) { + std::lock_guard _(m_enc_mutex); + if (int err = opus_encoder_ctl(m_encoder, OPUS_SET_SIGNAL(signal)); err != OPUS_OK) { + spdlog::get("audio")->error("opus_encoder_ctl(OPUS_SET_SIGNAL) failed: {}", err); + } +} + +int AudioManager::GetSignalHint() { + std::lock_guard _(m_enc_mutex); + int temp = OPUS_AUTO; + if (int err = opus_encoder_ctl(m_encoder, OPUS_GET_SIGNAL(&temp)); err != OPUS_OK) { + spdlog::get("audio")->error("opus_encoder_ctl(OPUS_GET_SIGNAL) failed: {}", err); + } + return temp; +} + +void AudioManager::SetBitrate(int bitrate) { + std::lock_guard _(m_enc_mutex); + if (int err = opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(bitrate)); err != OPUS_OK) { + spdlog::get("audio")->error("opus_encoder_ctl(OPUS_SET_BITRATE) failed: {}", err); + } +} + +int AudioManager::GetBitrate() { + std::lock_guard _(m_enc_mutex); + int temp = 64000; + if (int err = opus_encoder_ctl(m_encoder, OPUS_GET_BITRATE(&temp)); err != OPUS_OK) { + spdlog::get("audio")->error("opus_encoder_ctl(OPUS_GET_BITRATE) failed: {}", err); + } + return temp; +} + +void AudioManager::Enumerate() { + spdlog::get("audio")->debug("Enumerating devices"); + m_devices.SetDevices(); +} + +void AudioManager::OnCapturedPCM(const int16_t *pcm, uint32_t frames) { + if (m_opus_buffer == nullptr || !m_should_capture) return; + + const double gain = m_capture_gain; + + std::vector new_pcm(pcm, pcm + frames * 2); + for (auto &val : new_pcm) { + const int32_t unclamped = static_cast(val * gain); + val = std::clamp(unclamped, INT16_MIN, INT16_MAX); + } + + if (m_mix_mono) { + for (size_t i = 0; i < frames * 2; i += 2) { + const int sample_L = new_pcm[i]; + const int sample_R = new_pcm[i + 1]; + const int16_t mixed = static_cast((sample_L + sample_R) / 2); + new_pcm[i] = mixed; + new_pcm[i + 1] = mixed; + } + } + + UpdateCaptureVolume(new_pcm.data(), frames); + + static std::array denoised_L; + static std::array denoised_R; + + bool m_rnnoise_passed = false; +#ifdef WITH_RNNOISE + if (m_vad_method == VADMethod::RNNoise || m_enable_noise_suppression) { + m_rnnoise_passed = CheckVADRNNoise(new_pcm.data(), denoised_L.data(), denoised_R.data()); + } +#endif + + switch (m_vad_method) { + case VADMethod::Gate: + if (!CheckVADVoiceGate()) return; + break; +#ifdef WITH_RNNOISE + case VADMethod::RNNoise: + if (!m_rnnoise_passed) return; + break; +#endif + } + + m_enc_mutex.lock(); + int payload_len = -1; + + if (m_enable_noise_suppression) { + static std::array denoised_interleaved; + for (size_t i = 0; i < 480; i++) { + denoised_interleaved[i * 2] = static_cast(denoised_L[i]); + } + for (size_t i = 0; i < 480; i++) { + denoised_interleaved[i * 2 + 1] = static_cast(denoised_R[i]); + } + payload_len = opus_encode(m_encoder, denoised_interleaved.data(), 480, static_cast(m_opus_buffer), 1275); + } else { + payload_len = opus_encode(m_encoder, new_pcm.data(), 480, static_cast(m_opus_buffer), 1275); + } + + m_enc_mutex.unlock(); + if (payload_len < 0) { + spdlog::get("audio")->error("encoding error: {}", payload_len); + } else { + m_signal_opus_packet.emit(payload_len); + } +} + +void AudioManager::UpdateReceiveVolume(uint32_t ssrc, const int16_t *pcm, int frames) { + std::lock_guard _(m_vol_mtx); + + auto &meter = m_volumes[ssrc]; + for (int i = 0; i < frames * 2; i += 2) { + const int amp = std::abs(pcm[i]); + meter = std::max(meter, std::abs(amp) / 32768.0); + } +} + +void AudioManager::UpdateCaptureVolume(const int16_t *pcm, uint32_t frames) { + for (uint32_t i = 0; i < frames * 2; i += 2) { + const int amp = std::abs(pcm[i]); + m_capture_peak_meter = std::max(m_capture_peak_meter.load(std::memory_order_relaxed), amp); + } +} + +bool AudioManager::DecayVolumeMeters() { + m_capture_peak_meter -= 600; + if (m_capture_peak_meter < 0) m_capture_peak_meter = 0; + + const auto x = m_vad_prob.load() - 0.05f; + m_vad_prob.store(x < 0.0f ? 0.0f : x); + + std::lock_guard _(m_vol_mtx); + + for (auto &[ssrc, meter] : m_volumes) { + meter -= 0.01; + if (meter < 0.0) meter = 0.0; + } + + return true; +} + +bool AudioManager::CheckVADVoiceGate() { + return m_capture_peak_meter / 32768.0 > m_capture_gate; +} + +#ifdef WITH_RNNOISE +bool AudioManager::CheckVADRNNoise(const int16_t *pcm, float *denoised_left, float *denoised_right) { + // use left channel for vad, only denoise right if noise suppression enabled + std::unique_lock _(m_rnn_mutex); + + static float rnnoise_input[480]; + for (size_t i = 0; i < 480; i++) { + rnnoise_input[i] = static_cast(pcm[i * 2]); + } + m_vad_prob = std::max(m_vad_prob.load(), rnnoise_process_frame(m_rnnoise[0], denoised_left, rnnoise_input)); + + if (m_enable_noise_suppression) { + for (size_t i = 0; i < 480; i++) { + rnnoise_input[i] = static_cast(pcm[i * 2 + 1]); + } + rnnoise_process_frame(m_rnnoise[1], denoised_right, rnnoise_input); + } + + return m_vad_prob > m_prob_threshold; +} + +void AudioManager::RNNoiseInitialize() { + spdlog::get("audio")->debug("Initializing RNNoise"); + RNNoiseUninitialize(); + std::unique_lock _(m_rnn_mutex); + m_rnnoise[0] = rnnoise_create(nullptr); + m_rnnoise[1] = rnnoise_create(nullptr); + const auto expected = rnnoise_get_frame_size(); + if (expected != 480) { + spdlog::get("audio")->warn("RNNoise expects a frame count other than 480"); + } +} + +void AudioManager::RNNoiseUninitialize() { + if (m_rnnoise[0] != nullptr) { + spdlog::get("audio")->debug("Uninitializing RNNoise"); + std::unique_lock _(m_rnn_mutex); + rnnoise_destroy(m_rnnoise[0]); + rnnoise_destroy(m_rnnoise[1]); + m_rnnoise[0] = nullptr; + m_rnnoise[1] = nullptr; + } +} +#endif + +bool AudioManager::OK() const { + return m_ok; +} + +double AudioManager::GetCaptureVolumeLevel() const noexcept { + return m_capture_peak_meter / 32768.0; +} + +double AudioManager::GetSSRCVolumeLevel(uint32_t ssrc) const noexcept { + std::lock_guard _(m_vol_mtx); + if (const auto it = m_volumes.find(ssrc); it != m_volumes.end()) { + return it->second; + } + return 0.0; +} + +AudioDevices &AudioManager::GetDevices() { + return m_devices; +} + +uint32_t AudioManager::GetRTPTimestamp() const noexcept { + return m_rtp_timestamp; +} + +void AudioManager::SetVADMethod(const std::string &method) { + spdlog::get("audio")->debug("Setting VAD method to {}", method); + if (method == "gate") { + SetVADMethod(VADMethod::Gate); + } else if (method == "rnnoise") { +#ifdef WITH_RNNOISE + SetVADMethod(VADMethod::RNNoise); +#else + SetVADMethod(VADMethod::Gate); + spdlog::get("audio")->error("Tried to set RNNoise VAD method with support disabled"); +#endif + } else { + SetVADMethod(VADMethod::Gate); + spdlog::get("audio")->error("Tried to set unknown VAD method {}", method); + } +} + +void AudioManager::SetVADMethod(VADMethod method) { + const auto method_int = static_cast(method); + spdlog::get("audio")->debug("Setting VAD method to enum {}", method_int); + m_vad_method = method; +} + +AudioManager::VADMethod AudioManager::GetVADMethod() const { + return m_vad_method; +} + +#ifdef WITH_RNNOISE +float AudioManager::GetCurrentVADProbability() const { + return m_vad_prob; +} + +double AudioManager::GetRNNProbThreshold() const { + return m_prob_threshold; +} + +void AudioManager::SetRNNProbThreshold(double value) { + m_prob_threshold = value; +} + +void AudioManager::SetSuppressNoise(bool value) { + m_enable_noise_suppression = value; +} + +bool AudioManager::GetSuppressNoise() const { + return m_enable_noise_suppression; +} +#endif + +void AudioManager::SetMixMono(bool value) { + m_mix_mono = value; +} + +bool AudioManager::GetMixMono() const { + return m_mix_mono; +} + +AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { + return m_signal_opus_packet; +} + +#endif /* USE_PORTAUDIO */ +#endif /* WITH_VOICE */ -- 2.48.1 From 34c05a18705deaee0583c14898de89cb2c8d5609 Mon Sep 17 00:00:00 2001 From: zeldakatze Date: Wed, 13 Aug 2025 13:50:28 +0200 Subject: do resampling for portaudio diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index a657dcc..2d1ae71 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -163,6 +163,14 @@ private: PaStream* pa_capture_device; PaStreamParameters m_capture_config; + // portaudio does not do resampling. We have to do that ourselves. + // create target buffers + int16_t *resample_capture_buffer; + int16_t *resample_playback_buffer; + uint32_t resample_capture_source_rate, resample_playback_source_rate; + uint32_t resample_capture_framesPerBuffer; + uint32_t resample_playback_framesPerBuffer; + #endif diff --git a/src/audio/portaudioManager.cpp b/src/audio/portaudioManager.cpp index bcd1254..f21f9ff 100644 --- a/src/audio/portaudioManager.cpp +++ b/src/audio/portaudioManager.cpp @@ -10,19 +10,39 @@ #include #include +// TODO replace with libresample +// a simple nearest-neigbhor resampler +static void resampleBuffer(const int16_t *input, const int inputFrames, + int16_t *output, const int outputFrames) { + float ratio = (float) inputFrames / (float) outputFrames; + for(int oi = 0; oi < outputFrames; oi++) { + int ii = (int) (((float) oi) * ratio); + + // clamp the input index + if(ii >= inputFrames) { + ii = inputFrames - 1; + } + + // copy the samples. * 2 because stereo + output[oi * 2] = input[ii * 2]; + output[oi * 2 + 1] = input[ii * 2 + 1]; + } +} + int playCallback(const void *pInput, void *pOutput, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ) { AudioManager *mgr = reinterpret_cast(userData); - if (mgr == nullptr) {printf("NULL\n");return paContinue;}; + if (mgr == nullptr) {return paContinue;}; std::lock_guard _(mgr->m_mutex); - auto *pOutputF32 = static_cast(pOutput); + int16_t *resample_playback_buffer = mgr->resample_playback_buffer; + auto *pOutputF32 = static_cast(resample_playback_buffer); /* clear the output buffer */ - for(int i = 0; isecond; } auto &buf = pair.first; - const size_t n = std::min(static_cast(buf.size()), static_cast(frameCount * 2ULL)); + const size_t n = std::min(static_cast(buf.size()), static_cast(480 * 2ULL)); for (size_t i = 0; i < n; i++) { pOutputF32[i] += (int16_t) (volume * buf[i]); //pOutputF32[i] += volume * buf[i] / 32768.F; @@ -40,6 +60,9 @@ int playCallback(const void *pInput, void *pOutput, buf.erase(buf.begin(), buf.begin() + n); } + /* scale the resample buffer to the output buffer */ + resampleBuffer(resample_playback_buffer, 480, static_cast(pOutput), frameCount); + return paContinue; } @@ -51,7 +74,11 @@ int recordCallback(const void *pInput, void *pOutput, auto *mgr = reinterpret_cast(userData); if (mgr == nullptr) return paContinue; - mgr->OnCapturedPCM(static_cast(pInput), frameCount); + int16_t *resample_capture_buffer = mgr->resample_capture_buffer; + + resampleBuffer(static_cast(pInput), frameCount, resample_capture_buffer, 480); + + mgr->OnCapturedPCM(static_cast(resample_capture_buffer), 480); /* * You can simply increment it by 480 in UDPSocket::SendEncrypted but this is wrong @@ -103,22 +130,30 @@ AudioManager::AudioManager(const Glib::ustring &backends_string) m_playback_config.suggestedLatency = Pa_GetDeviceInfo( m_capture_config.device )->defaultLowInputLatency; m_playback_config.hostApiSpecificStreamInfo = NULL; m_playback_config.device = Pa_GetDefaultOutputDevice(); + resample_playback_source_rate = Pa_GetDeviceInfo( m_playback_config.device )->defaultSampleRate; + resample_playback_framesPerBuffer = (int) (((float) resample_playback_source_rate / 48000.0) * 480.0); m_capture_config.sampleFormat = paInt16; m_capture_config.channelCount = 2; m_capture_config.suggestedLatency = Pa_GetDeviceInfo( m_capture_config.device )->defaultLowInputLatency; m_capture_config.hostApiSpecificStreamInfo = NULL; m_capture_config.device = Pa_GetDefaultInputDevice(); + resample_capture_source_rate = Pa_GetDeviceInfo( m_capture_config.device )->defaultSampleRate; + resample_capture_framesPerBuffer = (int) (((float) resample_capture_source_rate / 48000.0) * 480.0); + + /* initialize the buffers for resampling */ + resample_capture_buffer = (int16_t*) malloc(480 * 2 * sizeof(uint16_t)); + resample_playback_buffer = (int16_t*) malloc(480 * 2 * sizeof(uint16_t)); /* create the streams */ - pa_err = Pa_OpenStream(&pa_capture_device, &m_capture_config, NULL, 48000, 480, paClipOff, recordCallback, this); + pa_err = Pa_OpenStream(&pa_capture_device, &m_capture_config, NULL, resample_capture_source_rate, resample_capture_framesPerBuffer, paClipOff, recordCallback, this); if(pa_err != paNoError) { m_ok = false; spdlog::get("audio")->error("ERROR: Pa_OpenStream for capture returned %d\n", pa_err); return; } - pa_err = Pa_OpenStream(&pa_playback_device, NULL, &m_playback_config, 48000, 480, paClipOff, playCallback, this); + pa_err = Pa_OpenStream(&pa_playback_device, NULL, &m_playback_config, resample_playback_source_rate, resample_playback_framesPerBuffer, paClipOff, playCallback, this); if(pa_err != paNoError) { m_ok = false; spdlog::get("audio")->error("ERROR: Pa_OpenStream for playback returned %d\n", pa_err); @@ -140,6 +175,9 @@ AudioManager::AudioManager(const Glib::ustring &backends_string) AudioManager::~AudioManager() { Pa_Terminate(); RemoveAllSSRCs(); + + free(resample_capture_buffer); + free(resample_playback_buffer); #ifdef WITH_RNNOISE RNNoiseUninitialize(); -- 2.48.1