From 0fa33915da6255cf7460758197eaea7e43353543 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 31 Aug 2022 01:51:02 -0400 Subject: rudimentary voice implementation --- src/audio/manager.cpp | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/audio/manager.cpp (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp new file mode 100644 index 0000000..af36327 --- /dev/null +++ b/src/audio/manager.cpp @@ -0,0 +1,92 @@ +#ifdef _WIN32 + #include +#endif + +#include "manager.hpp" +#include +#define MINIAUDIO_IMPLEMENTATION +#include +#include +#include + +const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t &outlen) { + if (buf[0] == 0xbe && buf[1] == 0xde && num_bytes > 4) { + uint64_t offset = 4 + 4 * ((buf[2] << 8) | buf[3]); + + outlen = num_bytes - offset; + return buf + offset; + } + outlen = num_bytes; + return buf; +} + +void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount) { + AudioManager *mgr = reinterpret_cast(pDevice->pUserData); + if (mgr == nullptr) return; + std::lock_guard _(mgr->m_dumb_mutex); + + const auto buffered_frames = std::min(static_cast(mgr->m_dumb.size() / 2), frameCount); + auto *pOutputCast = static_cast(pOutput); + for (ma_uint32 i = 0; i < buffered_frames * 2; i++) { + pOutputCast[i] = mgr->m_dumb.front(); + mgr->m_dumb.pop(); + } +} + +AudioManager::AudioManager() { + m_ok = true; + + m_device_config = ma_device_config_init(ma_device_type_playback); + m_device_config.playback.format = ma_format_s16; + m_device_config.playback.channels = 2; + m_device_config.sampleRate = 48000; + m_device_config.dataCallback = data_callback; + m_device_config.pUserData = this; + + if (ma_device_init(nullptr, &m_device_config, &m_device) != MA_SUCCESS) { + puts("open playabck fail"); + m_ok = false; + return; + } + + if (ma_device_start(&m_device) != MA_SUCCESS) { + puts("failed to start playback"); + ma_device_uninit(&m_device); + m_ok = false; + return; + } + + int err; + m_opus_decoder = opus_decoder_create(48000, 2, &err); + + m_active = true; + // m_thread = std::thread(&AudioManager::testthread, this); +} + +AudioManager::~AudioManager() { + m_active = false; + ma_device_uninit(&m_device); +} + +void AudioManager::FeedMeOpus(const std::vector &data) { + size_t payload_size = 0; + const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); + static std::array pcm; + int decoded = opus_decode(m_opus_decoder, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); + if (decoded <= 0) { + printf("failed decode: %d\n", decoded); + } else { + m_dumb_mutex.lock(); + for (size_t i = 0; i < decoded * 2; i++) { + m_dumb.push(pcm[i]); + } + m_dumb_mutex.unlock(); + } +} + +void AudioManager::testthread() { +} + +bool AudioManager::OK() const { + return m_ok; +} -- cgit v1.2.3 From 12a5fcfcd3bb65ea45a4a11017f30f6ae145c312 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 31 Aug 2022 16:58:17 -0400 Subject: fix opus include path --- src/audio/manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index af36327..30896ec 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -6,7 +6,7 @@ #include #define MINIAUDIO_IMPLEMENTATION #include -#include +#include #include const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t &outlen) { -- cgit v1.2.3 From 1f4070e52f94470eea3b86a089e3fe6e7fdca2fc Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 31 Aug 2022 20:42:14 -0400 Subject: basic buffering (i think) --- src/audio/manager.cpp | 24 +++++++++++------------- src/audio/manager.hpp | 8 +++++--- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 30896ec..e686a82 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -9,6 +9,8 @@ #include #include +#define BUFFER_SAMPLES 4800 + const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t &outlen) { if (buf[0] == 0xbe && buf[1] == 0xde && num_bytes > 4) { uint64_t offset = 4 + 4 * ((buf[2] << 8) | buf[3]); @@ -27,10 +29,8 @@ void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uin const auto buffered_frames = std::min(static_cast(mgr->m_dumb.size() / 2), frameCount); auto *pOutputCast = static_cast(pOutput); - for (ma_uint32 i = 0; i < buffered_frames * 2; i++) { - pOutputCast[i] = mgr->m_dumb.front(); - mgr->m_dumb.pop(); - } + std::copy(mgr->m_dumb.begin(), mgr->m_dumb.begin() + buffered_frames * 2, pOutputCast); + mgr->m_dumb.erase(mgr->m_dumb.begin(), mgr->m_dumb.begin() + buffered_frames * 2); } AudioManager::AudioManager() { @@ -60,7 +60,6 @@ AudioManager::AudioManager() { m_opus_decoder = opus_decoder_create(48000, 2, &err); m_active = true; - // m_thread = std::thread(&AudioManager::testthread, this); } AudioManager::~AudioManager() { @@ -71,22 +70,21 @@ AudioManager::~AudioManager() { void AudioManager::FeedMeOpus(const std::vector &data) { size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); - static std::array pcm; + static std::array pcm; int decoded = opus_decode(m_opus_decoder, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); if (decoded <= 0) { printf("failed decode: %d\n", decoded); } else { - m_dumb_mutex.lock(); - for (size_t i = 0; i < decoded * 2; i++) { - m_dumb.push(pcm[i]); + m_buffer.insert(m_buffer.end(), pcm.begin(), pcm.begin() + decoded * 2); + if (m_buffer.size() >= BUFFER_SAMPLES * 2) { + m_dumb_mutex.lock(); + m_dumb.insert(m_dumb.end(), m_buffer.begin(), m_buffer.begin() + BUFFER_SAMPLES * 2); + m_dumb_mutex.unlock(); + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + BUFFER_SAMPLES * 2); } - m_dumb_mutex.unlock(); } } -void AudioManager::testthread() { -} - bool AudioManager::OK() const { return m_ok; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 72d7c2f..27e973b 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include +#include #include #include -#include +#include #include #include @@ -19,7 +21,6 @@ private: friend void data_callback(ma_device *, void *, const void *, ma_uint32); std::atomic m_active; - void testthread(); std::thread m_thread; bool m_ok; @@ -29,7 +30,8 @@ private: ma_device_config m_device_config; std::mutex m_dumb_mutex; - std::queue m_dumb; + std::deque m_dumb; + std::deque m_buffer; OpusDecoder *m_opus_decoder; }; -- cgit v1.2.3 From 9c8d9e54fe96f97bdda2be26bab571e4cbf0c597 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:38:59 -0400 Subject: handle multiple speakers properly --- src/audio/manager.cpp | 49 +++++++++++++++++++++++---------------------- src/audio/manager.hpp | 12 ++++------- src/discord/voiceclient.cpp | 6 +++++- 3 files changed, 34 insertions(+), 33 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index e686a82..9ba8c51 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -9,8 +9,6 @@ #include #include -#define BUFFER_SAMPLES 4800 - const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t &outlen) { if (buf[0] == 0xbe && buf[1] == 0xde && num_bytes > 4) { uint64_t offset = 4 + 4 * ((buf[2] << 8) | buf[3]); @@ -25,19 +23,24 @@ const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount) { AudioManager *mgr = reinterpret_cast(pDevice->pUserData); if (mgr == nullptr) return; - std::lock_guard _(mgr->m_dumb_mutex); + std::lock_guard _(mgr->m_mutex); - const auto buffered_frames = std::min(static_cast(mgr->m_dumb.size() / 2), frameCount); - auto *pOutputCast = static_cast(pOutput); - std::copy(mgr->m_dumb.begin(), mgr->m_dumb.begin() + buffered_frames * 2, pOutputCast); - mgr->m_dumb.erase(mgr->m_dumb.begin(), mgr->m_dumb.begin() + buffered_frames * 2); + auto *pOutputF32 = static_cast(pOutput); + for (auto &[ssrc, pair] : mgr->m_sources) { + auto &buf = pair.first; + const size_t n = std::min(buf.size(), frameCount * 2ULL); + for (size_t i = 0; i < n; i++) { + pOutputF32[i] += buf[i] / 32768.F; + } + buf.erase(buf.begin(), buf.begin() + n); + } } AudioManager::AudioManager() { m_ok = true; m_device_config = ma_device_config_init(ma_device_type_playback); - m_device_config.playback.format = ma_format_s16; + m_device_config.playback.format = ma_format_f32; m_device_config.playback.channels = 2; m_device_config.sampleRate = 48000; m_device_config.dataCallback = data_callback; @@ -55,33 +58,31 @@ AudioManager::AudioManager() { m_ok = false; return; } - - int err; - m_opus_decoder = opus_decoder_create(48000, 2, &err); - - m_active = true; } AudioManager::~AudioManager() { - m_active = false; ma_device_uninit(&m_device); + for (auto &[ssrc, pair] : m_sources) { + opus_decoder_destroy(pair.second); + } } -void AudioManager::FeedMeOpus(const std::vector &data) { +void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); static std::array pcm; - int decoded = opus_decode(m_opus_decoder, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); + if (m_sources.find(ssrc) == m_sources.end()) { + int err; + auto *decoder = opus_decoder_create(48000, 2, &err); + m_sources.insert(std::make_pair(ssrc, std::make_pair(std::deque {}, decoder))); + } + int decoded = opus_decode(m_sources.at(ssrc).second, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); if (decoded <= 0) { - printf("failed decode: %d\n", decoded); } else { - m_buffer.insert(m_buffer.end(), pcm.begin(), pcm.begin() + decoded * 2); - if (m_buffer.size() >= BUFFER_SAMPLES * 2) { - m_dumb_mutex.lock(); - m_dumb.insert(m_dumb.end(), m_buffer.begin(), m_buffer.begin() + BUFFER_SAMPLES * 2); - m_dumb_mutex.unlock(); - m_buffer.erase(m_buffer.begin(), m_buffer.begin() + BUFFER_SAMPLES * 2); - } + m_mutex.lock(); + auto &buf = m_sources.at(ssrc).first; + buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2); + m_mutex.unlock(); } } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 27e973b..55ecdc3 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,25 +14,20 @@ public: AudioManager(); ~AudioManager(); - void FeedMeOpus(const std::vector &data); + void FeedMeOpus(uint32_t ssrc, const std::vector &data); [[nodiscard]] bool OK() const; private: friend void data_callback(ma_device *, void *, const void *, ma_uint32); - std::atomic m_active; std::thread m_thread; bool m_ok; - ma_engine m_engine; ma_device m_device; ma_device_config m_device_config; - std::mutex m_dumb_mutex; - std::deque m_dumb; - std::deque m_buffer; - - OpusDecoder *m_opus_decoder; + std::mutex m_mutex; + std::unordered_map, OpusDecoder *>> m_sources; }; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 4837d7b..7aaa4a5 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -298,12 +298,16 @@ void DiscordVoiceClient::SelectProtocol(std::string_view ip, uint16_t port) { void DiscordVoiceClient::OnUDPData(std::vector data) { uint8_t *payload = data.data() + 12; + uint32_t ssrc = (data[8] << 24) | + (data[9] << 16) | + (data[10] << 8) | + (data[11] << 0); static std::array nonce = {}; std::memcpy(nonce.data(), data.data(), 12); if (crypto_secretbox_open_easy(payload, payload, data.size() - 12, nonce.data(), m_secret_key.data())) { puts("decrypt fail"); } else { - Abaddon::Get().GetAudio().FeedMeOpus({ payload, payload + data.size() - 12 - crypto_box_MACBYTES }); + Abaddon::Get().GetAudio().FeedMeOpus(ssrc, { payload, payload + data.size() - 12 - crypto_box_MACBYTES }); } } -- cgit v1.2.3 From 0a049856782397a7464d03c317ce3788559cdeda Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 2 Sep 2022 01:25:33 -0400 Subject: make compile work if voice support is disabled --- src/abaddon.cpp | 9 +++++++++ src/abaddon.hpp | 9 +++++++++ src/audio/manager.cpp | 4 ++++ src/audio/manager.hpp | 4 ++++ src/components/channels.cpp | 18 ++++++++++++++++++ src/components/channels.hpp | 7 ++++++- src/components/channelscellrenderer.cpp | 12 ++++++++++++ src/components/channelscellrenderer.hpp | 3 +++ src/discord/discord.cpp | 6 ++++++ src/discord/discord.hpp | 12 ++++++++++-- src/discord/objects.cpp | 2 ++ src/discord/objects.hpp | 2 ++ src/discord/voiceclient.cpp | 10 +++++++--- src/discord/voiceclient.hpp | 10 +++++++--- 14 files changed, 99 insertions(+), 9 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index a3a228d..688aff4 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -220,6 +220,7 @@ int Abaddon::StartGTK() { return 1; } +#ifdef WITH_VOICE m_audio = std::make_unique(); if (!m_audio->OK()) { Gtk::MessageDialog dlg(*m_main_window, "The audio engine could not be initialized!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); @@ -227,6 +228,7 @@ int Abaddon::StartGTK() { dlg.run(); return 1; } +#endif // store must be checked before this can be called m_main_window->UpdateComponents(); @@ -247,7 +249,10 @@ int Abaddon::StartGTK() { m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::bind(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened), true)); m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild)); m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings)); + +#ifdef WITH_VOICE m_main_window->GetChannelList()->signal_action_join_voice_channel().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinVoiceChannel)); +#endif m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage)); m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit)); @@ -908,9 +913,11 @@ void Abaddon::ActionViewThreads(Snowflake channel_id) { window->show(); } +#ifdef WITH_VOICE void Abaddon::ActionJoinVoiceChannel(Snowflake channel_id) { m_discord.ConnectToVoice(channel_id); } +#endif std::optional Abaddon::ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder, Gtk::Window *window) { TextInputDialog dlg(prompt, title, placeholder, window != nullptr ? *window : *m_main_window); @@ -951,9 +958,11 @@ EmojiResource &Abaddon::GetEmojis() { return m_emojis; } +#ifdef WITH_VOICE AudioManager &Abaddon::GetAudio() { return *m_audio.get(); } +#endif int main(int argc, char **argv) { if (std::getenv("ABADDON_NO_FC") == nullptr) diff --git a/src/abaddon.hpp b/src/abaddon.hpp index d67f4ab..9a98c9e 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -53,7 +53,10 @@ public: void ActionAddRecipient(Snowflake channel_id); void ActionViewPins(Snowflake channel_id); void ActionViewThreads(Snowflake channel_id); + +#ifdef WITH_VOICE void ActionJoinVoiceChannel(Snowflake channel_id); +#endif std::optional ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder = "", Gtk::Window *window = nullptr); bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr); @@ -62,7 +65,10 @@ public: ImageManager &GetImageManager(); EmojiResource &GetEmojis(); + +#ifdef WITH_VOICE AudioManager &GetAudio(); +#endif std::string GetDiscordToken() const; bool IsDiscordActive() const; @@ -141,7 +147,10 @@ private: ImageManager m_img_mgr; EmojiResource m_emojis; + +#ifdef WITH_VOICE std::unique_ptr m_audio; +#endif mutable std::mutex m_mutex; Glib::RefPtr m_gtk_app; diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 9ba8c51..d7d0bb8 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -1,3 +1,5 @@ +#ifdef WITH_VOICE +// clang-format off #ifdef _WIN32 #include #endif @@ -8,6 +10,7 @@ #include #include #include +// clang-format on const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t &outlen) { if (buf[0] == 0xbe && buf[1] == 0xde && num_bytes > 4) { @@ -89,3 +92,4 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { bool AudioManager::OK() const { return m_ok; } +#endif diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 55ecdc3..d0f3a21 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -1,4 +1,6 @@ #pragma once +#ifdef WITH_VOICE +// clang-format off #include #include #include @@ -8,6 +10,7 @@ #include #include #include +// clang-format on class AudioManager { public: @@ -31,3 +34,4 @@ private: std::mutex m_mutex; std::unordered_map, OpusDecoder *>> m_sources; }; +#endif diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 566ebd1..201e7c9 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -36,7 +36,11 @@ ChannelList::ChannelList() const auto type = row[m_columns.m_type]; // text channels should not be allowed to be collapsed // maybe they should be but it seems a little difficult to handle expansion to permit this +#ifdef WITH_VOICE if (type != RenderType::TextChannel && type != RenderType::VoiceChannel) { +#else + if (type != RenderType::TextChannel) { +#endif if (row[m_columns.m_expanded]) { m_view.collapse_row(path); row[m_columns.m_expanded] = false; @@ -161,6 +165,7 @@ ChannelList::ChannelList() m_menu_channel.append(m_menu_channel_copy_id); m_menu_channel.show_all(); +#ifdef WITH_VOICE m_menu_voice_channel_join.signal_activate().connect([this]() { const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); printf("join voice: %llu\n", static_cast(id)); @@ -169,6 +174,7 @@ ChannelList::ChannelList() m_menu_voice_channel.append(m_menu_voice_channel_join); m_menu_voice_channel.show_all(); +#endif m_menu_dm_copy_id.signal_activate().connect([this] { Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); @@ -588,7 +594,11 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { for (const auto &channel_ : *guild.Channels) { const auto channel = discord.GetChannel(channel_.ID); if (!channel.has_value()) continue; +#ifdef WITH_VOICE if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS || channel->Type == ChannelType::GUILD_VOICE) { +#else + if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS) { +#endif if (channel->ParentID.has_value()) categories[*channel->ParentID].push_back(*channel); else @@ -618,8 +628,10 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { auto channel_row = *m_model->append(guild_row.children()); if (IsTextChannel(channel.Type)) channel_row[m_columns.m_type] = RenderType::TextChannel; +#ifdef WITH_VOICE else channel_row[m_columns.m_type] = RenderType::VoiceChannel; +#endif channel_row[m_columns.m_id] = channel.ID; channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset; @@ -644,8 +656,10 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { auto channel_row = *m_model->append(cat_row.children()); if (IsTextChannel(channel.Type)) channel_row[m_columns.m_type] = RenderType::TextChannel; +#ifdef WITH_VOICE else channel_row[m_columns.m_type] = RenderType::VoiceChannel; +#endif channel_row[m_columns.m_id] = channel.ID; channel_row[m_columns.m_name] = "#" + Glib::Markup::escape_text(*channel.Name); channel_row[m_columns.m_sort] = *channel.Position; @@ -871,10 +885,12 @@ bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) { OnChannelSubmenuPopup(); m_menu_channel.popup_at_pointer(reinterpret_cast(ev)); break; +#ifdef WITH_VOICE case RenderType::VoiceChannel: OnVoiceChannelSubmenuPopup(); m_menu_voice_channel.popup_at_pointer(reinterpret_cast(ev)); break; +#endif case RenderType::DM: { OnDMSubmenuPopup(); const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(static_cast(row[m_columns.m_id])); @@ -966,8 +982,10 @@ void ChannelList::OnChannelSubmenuPopup() { m_menu_channel_toggle_mute.set_label("Mute"); } +#ifdef WITH_VOICE void ChannelList::OnVoiceChannelSubmenuPopup() { } +#endif void ChannelList::OnDMSubmenuPopup() { auto iter = m_model->get_iter(m_path_for_menu); diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 53afbdc..8c34735 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -125,8 +125,10 @@ protected: Gtk::MenuItem m_menu_channel_open_tab; #endif +#ifdef WITH_VOICE Gtk::Menu m_menu_voice_channel; Gtk::MenuItem m_menu_voice_channel_join; +#endif Gtk::Menu m_menu_dm; Gtk::MenuItem m_menu_dm_copy_id; @@ -148,10 +150,13 @@ protected: void OnGuildSubmenuPopup(); void OnCategorySubmenuPopup(); void OnChannelSubmenuPopup(); - void OnVoiceChannelSubmenuPopup(); void OnDMSubmenuPopup(); void OnThreadSubmenuPopup(); +#ifdef WITH_VOICE + void OnVoiceChannelSubmenuPopup(); +#endif + bool m_updating_listing = false; Snowflake m_active_channel; diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index e9c43aa..23ee3f0 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -65,8 +65,10 @@ void CellRendererChannels::get_preferred_width_vfunc(Gtk::Widget &widget, int &m return get_preferred_width_vfunc_channel(widget, minimum_width, natural_width); case RenderType::Thread: return get_preferred_width_vfunc_thread(widget, minimum_width, natural_width); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_width_vfunc_voice_channel(widget, minimum_width, natural_width); +#endif case RenderType::DMHeader: return get_preferred_width_vfunc_dmheader(widget, minimum_width, natural_width); case RenderType::DM: @@ -84,8 +86,10 @@ void CellRendererChannels::get_preferred_width_for_height_vfunc(Gtk::Widget &wid return get_preferred_width_for_height_vfunc_channel(widget, height, minimum_width, natural_width); case RenderType::Thread: return get_preferred_width_for_height_vfunc_thread(widget, height, minimum_width, natural_width); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_width_for_height_vfunc_voice_channel(widget, height, minimum_width, natural_width); +#endif case RenderType::DMHeader: return get_preferred_width_for_height_vfunc_dmheader(widget, height, minimum_width, natural_width); case RenderType::DM: @@ -103,8 +107,10 @@ void CellRendererChannels::get_preferred_height_vfunc(Gtk::Widget &widget, int & return get_preferred_height_vfunc_channel(widget, minimum_height, natural_height); case RenderType::Thread: return get_preferred_height_vfunc_thread(widget, minimum_height, natural_height); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_height_vfunc_voice_channel(widget, minimum_height, natural_height); +#endif case RenderType::DMHeader: return get_preferred_height_vfunc_dmheader(widget, minimum_height, natural_height); case RenderType::DM: @@ -122,8 +128,10 @@ void CellRendererChannels::get_preferred_height_for_width_vfunc(Gtk::Widget &wid return get_preferred_height_for_width_vfunc_channel(widget, width, minimum_height, natural_height); case RenderType::Thread: return get_preferred_height_for_width_vfunc_thread(widget, width, minimum_height, natural_height); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return get_preferred_height_for_width_vfunc_voice_channel(widget, width, minimum_height, natural_height); +#endif case RenderType::DMHeader: return get_preferred_height_for_width_vfunc_dmheader(widget, width, minimum_height, natural_height); case RenderType::DM: @@ -141,8 +149,10 @@ void CellRendererChannels::render_vfunc(const Cairo::RefPtr &cr, return render_vfunc_channel(cr, widget, background_area, cell_area, flags); case RenderType::Thread: return render_vfunc_thread(cr, widget, background_area, cell_area, flags); +#ifdef WITH_VOICE case RenderType::VoiceChannel: return render_vfunc_voice_channel(cr, widget, background_area, cell_area, flags); +#endif case RenderType::DMHeader: return render_vfunc_dmheader(cr, widget, background_area, cell_area, flags); case RenderType::DM: @@ -511,6 +521,7 @@ void CellRendererChannels::render_vfunc_thread(const Cairo::RefPtrGuildID.has_value()) return; @@ -1178,6 +1179,7 @@ void DiscordClient::ConnectToVoice(Snowflake channel_id) { m.PreferredRegion = "newark"; m_websocket.Send(m); } +#endif void DiscordClient::SetReferringChannel(Snowflake id) { if (!id.IsValid()) { @@ -1498,12 +1500,14 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::GUILD_MEMBERS_CHUNK: { HandleGatewayGuildMembersChunk(m); } break; +#ifdef WITH_VOICE case GatewayEvent::VOICE_STATE_UPDATE: { HandleGatewayVoiceStateUpdate(m); } break; case GatewayEvent::VOICE_SERVER_UPDATE: { HandleGatewayVoiceServerUpdate(m); } break; +#endif } } break; default: @@ -2114,6 +2118,7 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { m_store.EndTransaction(); } +#ifdef WITH_VOICE void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { VoiceStateUpdateData data = msg.Data; if (data.UserID == m_user_data.ID) { @@ -2132,6 +2137,7 @@ void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { m_voice.SetUserID(m_user_data.ID); m_voice.Start(); } +#endif void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { ReadySupplementalData data = msg.Data; diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index a6eabd9..907c3e6 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -181,7 +181,9 @@ public: void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot)> &callback); void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot &callback); +#ifdef WITH_VOICE void ConnectToVoice(Snowflake channel_id); +#endif void SetReferringChannel(Snowflake id); @@ -262,11 +264,15 @@ private: void HandleGatewayMessageAck(const GatewayMessage &msg); void HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg); void HandleGatewayGuildMembersChunk(const GatewayMessage &msg); - void HandleGatewayVoiceStateUpdate(const GatewayMessage &msg); - void HandleGatewayVoiceServerUpdate(const GatewayMessage &msg); void HandleGatewayReadySupplemental(const GatewayMessage &msg); void HandleGatewayReconnect(const GatewayMessage &msg); void HandleGatewayInvalidSession(const GatewayMessage &msg); + +#ifdef WITH_VOICE + void HandleGatewayVoiceStateUpdate(const GatewayMessage &msg); + void HandleGatewayVoiceServerUpdate(const GatewayMessage &msg); +#endif + void HeartbeatThread(); void SendIdentify(); void SendResume(); @@ -326,7 +332,9 @@ private: bool m_wants_resume = false; // reconnecting specifically to resume std::string m_session_id; +#ifdef WITH_VOICE DiscordVoiceClient m_voice; +#endif mutable std::mutex m_msg_mutex; Glib::Dispatcher m_msg_dispatch; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 5ca8718..a6a7cc6 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -641,6 +641,7 @@ void from_json(const nlohmann::json &j, GuildMembersChunkData &m) { JS_D("guild_id", m.GuildID); } +#ifdef WITH_VOICE void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m) { j["op"] = GatewayOp::VoiceStateUpdate; j["d"]["guild_id"] = m.GuildID; @@ -661,3 +662,4 @@ void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) { JS_D("guild_id", m.GuildID); JS_D("endpoint", m.Endpoint); } +#endif diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 240b4c5..c9c8aff 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -867,6 +867,7 @@ struct GuildMembersChunkData { friend void from_json(const nlohmann::json &j, GuildMembersChunkData &m); }; +#ifdef WITH_VOICE struct VoiceStateUpdateMessage { Snowflake GuildID; Snowflake ChannelID; @@ -892,3 +893,4 @@ struct VoiceServerUpdateData { friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m); }; +#endif diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 7aaa4a5..d8855fd 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -1,3 +1,5 @@ +#ifdef WITH_VOICE + // clang-format off #include "voiceclient.hpp" #include "json.hpp" #include @@ -10,6 +12,7 @@ #else #define S_ADDR(var) (var).sin_addr.s_addr #endif +// clang-format on UDPSocket::UDPSocket() { m_socket = socket(AF_INET, SOCK_DGRAM, 0); @@ -88,11 +91,11 @@ std::vector UDPSocket::Receive() { void UDPSocket::Stop() { m_running = false; -#ifdef _WIN32 + #ifdef _WIN32 shutdown(m_socket, SD_BOTH); -#else + #else shutdown(m_socket, SHUT_RDWR); -#endif + #endif if (m_thread.joinable()) m_thread.join(); } @@ -385,3 +388,4 @@ void from_json(const nlohmann::json &j, VoiceSessionDescriptionData &m) { JS_D("mode", m.Mode); JS_D("secret_key", m.SecretKey); } +#endif diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 615bbde..4b988d5 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -1,4 +1,6 @@ #pragma once +#ifdef WITH_VOICE +// clang-format off #include "snowflake.hpp" #include "waiter.hpp" #include "websocket.hpp" @@ -6,6 +8,7 @@ #include #include #include +// clang-format on enum class VoiceGatewayCloseCode : uint16_t { UnknownOpcode = 4001, @@ -124,11 +127,11 @@ public: private: void ReadThread(); -#ifdef _WIN32 + #ifdef _WIN32 SOCKET m_socket; -#else + #else int m_socket; -#endif + #endif sockaddr_in m_server; std::atomic m_running = false; @@ -203,3 +206,4 @@ private: Waiter m_heartbeat_waiter; std::thread m_heartbeat_thread; }; +#endif -- cgit v1.2.3 From b7fffb8691c6010864d88b273f346419eb5a5d2b Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 2 Sep 2022 01:47:13 -0400 Subject: fix min call --- src/audio/manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index d7d0bb8..9d380f9 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -1,5 +1,5 @@ #ifdef WITH_VOICE -// clang-format off + // clang-format off #ifdef _WIN32 #include #endif @@ -31,7 +31,7 @@ void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uin auto *pOutputF32 = static_cast(pOutput); for (auto &[ssrc, pair] : mgr->m_sources) { auto &buf = pair.first; - const size_t n = std::min(buf.size(), frameCount * 2ULL); + const size_t n = std::min(static_cast(buf.size()), static_cast(frameCount * 2ULL)); for (size_t i = 0; i < n; i++) { pOutputF32[i] += buf[i] / 32768.F; } -- cgit v1.2.3 From e93b8715f9e42d25c0930becc22561f23a25a709 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 5 Sep 2022 02:21:37 -0400 Subject: basic voice capture + transmission --- src/audio/manager.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++- src/audio/manager.hpp | 20 ++++++++++++++ src/discord/voiceclient.cpp | 38 ++++++++++++++++++++++----- src/discord/voiceclient.hpp | 19 ++++++++++++++ 4 files changed, 132 insertions(+), 8 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 9d380f9..b665c81 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -39,9 +39,25 @@ void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uin } } +void capture_data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount) { + auto *mgr = reinterpret_cast(pDevice->pUserData); + if (mgr == nullptr) return; + + mgr->OnCapturedPCM(static_cast(pInput), frameCount); +} + AudioManager::AudioManager() { m_ok = true; + int err; + m_encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_VOIP, &err); + if (err != OPUS_OK) { + printf("failed to initialize opus encoder: %d\n", err); + m_ok = false; + return; + } + opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(64000)); + m_device_config = ma_device_config_init(ma_device_type_playback); m_device_config.playback.format = ma_format_f32; m_device_config.playback.channels = 2; @@ -50,7 +66,7 @@ AudioManager::AudioManager() { m_device_config.pUserData = this; if (ma_device_init(nullptr, &m_device_config, &m_device) != MA_SUCCESS) { - puts("open playabck fail"); + puts("open playback fail"); m_ok = false; return; } @@ -61,15 +77,45 @@ AudioManager::AudioManager() { m_ok = false; return; } + + m_capture_config = ma_device_config_init(ma_device_type_capture); + m_capture_config.capture.format = ma_format_s16; + m_capture_config.capture.channels = 2; + m_capture_config.sampleRate = 48000; + m_capture_config.periodSizeInFrames = 480; + m_capture_config.dataCallback = capture_data_callback; + m_capture_config.pUserData = this; + + if (ma_device_init(nullptr, &m_capture_config, &m_capture_device) != MA_SUCCESS) { + puts("open capture fail"); + m_ok = false; + return; + } + + if (ma_device_start(&m_capture_device) != MA_SUCCESS) { + puts("failed to start capture"); + ma_device_uninit(&m_capture_device); + m_ok = false; + return; + } + + char device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(&m_capture_device, ma_device_type_capture, device_name, sizeof(device_name), nullptr); + printf("using %s for capture\n", device_name); } AudioManager::~AudioManager() { ma_device_uninit(&m_device); + ma_device_uninit(&m_capture_device); for (auto &[ssrc, pair] : m_sources) { opus_decoder_destroy(pair.second); } } +void AudioManager::SetOpusBuffer(uint8_t *ptr) { + m_opus_buffer = ptr; +} + void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); @@ -89,7 +135,22 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { } } +void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { + if (m_opus_buffer == nullptr) return; + + int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); + if (payload_len < 0) { + printf("encoding error: %d\n", payload_len); + } else { + m_signal_opus_packet.emit(payload_len); + } +} + bool AudioManager::OK() const { return m_ok; } + +AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { + return m_signal_opus_packet; +} #endif diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index d0f3a21..700fcc0 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -10,6 +10,7 @@ #include #include #include +#include // clang-format on class AudioManager { @@ -17,21 +18,40 @@ public: AudioManager(); ~AudioManager(); + void SetOpusBuffer(uint8_t *ptr); void FeedMeOpus(uint32_t ssrc, const std::vector &data); [[nodiscard]] bool OK() const; private: + void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames); + friend void data_callback(ma_device *, void *, const void *, ma_uint32); + friend void capture_data_callback(ma_device *, void *, const void *, ma_uint32); std::thread m_thread; bool m_ok; + // playback ma_device m_device; ma_device_config m_device_config; + // capture + ma_device m_capture_device; + ma_device_config m_capture_config; std::mutex m_mutex; std::unordered_map, OpusDecoder *>> m_sources; + + OpusEncoder *m_encoder; + + uint8_t *m_opus_buffer = nullptr; + +public: + using type_signal_opus_packet = sigc::signal; + type_signal_opus_packet signal_opus_packet(); + +private: + type_signal_opus_packet m_signal_opus_packet; }; #endif diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index d8855fd..3f38eea 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -43,11 +43,11 @@ void UDPSocket::SetSSRC(uint32_t ssrc) { m_ssrc = ssrc; } -void UDPSocket::SendEncrypted(const std::vector &data) { +void UDPSocket::SendEncrypted(const uint8_t *data, size_t len) { m_sequence++; - m_timestamp += (48000 / 100) * 2; + m_timestamp += 480; // this is important - std::vector rtp(12, 0); + std::vector rtp(12 + len + crypto_secretbox_MACBYTES, 0); rtp[0] = 0x80; // ver 2 rtp[1] = 0x78; // payload type 0x78 rtp[2] = (m_sequence >> 8) & 0xFF; @@ -63,14 +63,15 @@ void UDPSocket::SendEncrypted(const std::vector &data) { static std::array nonce = {}; std::memcpy(nonce.data(), rtp.data(), 12); - - std::vector ciphertext(crypto_secretbox_MACBYTES + rtp.size(), 0); - crypto_secretbox_easy(ciphertext.data(), rtp.data(), rtp.size(), nonce.data(), m_secret_key.data()); - rtp.insert(rtp.end(), ciphertext.begin(), ciphertext.end()); + crypto_secretbox_easy(rtp.data() + 12, data, len, nonce.data(), m_secret_key.data()); Send(rtp.data(), rtp.size()); } +void UDPSocket::SendEncrypted(const std::vector &data) { + SendEncrypted(data.data(), data.size()); +} + void UDPSocket::Send(const uint8_t *data, size_t len) { sendto(m_socket, reinterpret_cast(data), static_cast(len), 0, reinterpret_cast(&m_server), sizeof(m_server)); } @@ -172,6 +173,14 @@ DiscordVoiceClient::~DiscordVoiceClient() { void DiscordVoiceClient::Start() { m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); + + // cant put in ctor or deadlock in singleton initialization + auto &aud = Abaddon::Get().GetAudio(); + aud.SetOpusBuffer(m_opus_buffer.data()); + aud.signal_opus_packet().connect([this](int payload_size) { + if (m_connected) + m_udp.SendEncrypted(m_opus_buffer.data(), payload_size); + }); } void DiscordVoiceClient::SetSessionID(std::string_view session_id) { @@ -241,6 +250,13 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa printf("%02X", b); } printf("\n"); + + VoiceSpeakingMessage msg; + msg.Delay = 0; + msg.SSRC = m_ssrc; + msg.Speaking = VoiceSpeakingMessage::Microphone; + m_ws.Send(msg); + m_secret_key = d.SecretKey; m_udp.SetSSRC(m_ssrc); m_udp.SetSecretKey(m_secret_key); @@ -250,6 +266,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.Run(); + m_connected = true; } void DiscordVoiceClient::Identify() { @@ -388,4 +405,11 @@ void from_json(const nlohmann::json &j, VoiceSessionDescriptionData &m) { JS_D("mode", m.Mode); JS_D("secret_key", m.SecretKey); } + +void to_json(nlohmann::json &j, const VoiceSpeakingMessage &m) { + j["op"] = VoiceGatewayOp::Speaking; + j["d"]["speaking"] = m.Speaking; + j["d"]["delay"] = m.Delay; + j["d"]["ssrc"] = m.SSRC; +} #endif diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 4b988d5..67919e5 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -110,6 +110,20 @@ struct VoiceSessionDescriptionData { friend void from_json(const nlohmann::json &j, VoiceSessionDescriptionData &m); }; +struct VoiceSpeakingMessage { + enum { + Microphone = 1 << 0, + Soundshare = 1 << 1, + Priority = 1 << 2, + }; + + int Speaking; + int Delay; + uint32_t SSRC; + + friend void to_json(nlohmann::json &j, const VoiceSpeakingMessage &m); +}; + class UDPSocket { public: UDPSocket(); @@ -119,6 +133,7 @@ public: void Run(); void SetSecretKey(std::array key); void SetSSRC(uint32_t ssrc); + void SendEncrypted(const uint8_t *data, size_t len); void SendEncrypted(const std::vector &data); void Send(const uint8_t *data, size_t len); std::vector Receive(); @@ -205,5 +220,9 @@ private: int m_heartbeat_msec; Waiter m_heartbeat_waiter; std::thread m_heartbeat_thread; + + std::array m_opus_buffer; + + std::atomic m_connected = false; }; #endif -- cgit v1.2.3 From d57d822aa9f85ee023e2a50bd525d530b39a7186 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:44:52 -0400 Subject: manage decoders with ssrc updates --- src/abaddon.cpp | 19 +++++++++++++++ src/abaddon.hpp | 5 ++++ src/audio/manager.cpp | 45 +++++++++++++++++++++++----------- src/audio/manager.hpp | 4 +++ src/discord/discord.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++- src/discord/discord.hpp | 21 ++++++++++++++++ src/discord/objects.cpp | 25 +++++++++++++++---- src/discord/objects.hpp | 34 ++++++++++++++++++++------ src/discord/voiceclient.cpp | 33 +++++++++++++++++++++++-- src/discord/voiceclient.hpp | 38 +++++++++++++++++++++++------ 10 files changed, 246 insertions(+), 37 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index bdedc7c..3245ad4 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -50,6 +50,16 @@ Abaddon::Abaddon() m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate)); m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent)); m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect)); + +#ifdef WITH_VOICE + m_discord.signal_voice_connected().connect(sigc::mem_fun(*this, &Abaddon::OnVoiceConnected)); + m_discord.signal_voice_disconnected().connect(sigc::mem_fun(*this, &Abaddon::OnVoiceDisconnected)); + m_discord.signal_voice_speaking().connect([this](const VoiceSpeakingData &m) { + printf("%llu has ssrc %u\n", (uint64_t)m.UserID, m.SSRC); + m_audio->AddSSRC(m.SSRC); + }); +#endif + m_discord.signal_channel_accessibility_changed().connect([this](Snowflake id, bool accessible) { if (!accessible) m_channels_requested.erase(id); @@ -406,6 +416,15 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { } } +#ifdef WITH_VOICE +void Abaddon::OnVoiceConnected() { +} + +void Abaddon::OnVoiceDisconnected() { + m_audio->RemoveAllSSRCs(); +} +#endif + SettingsManager::Settings &Abaddon::GetSettings() { return m_settings.GetSettings(); } diff --git a/src/abaddon.hpp b/src/abaddon.hpp index ca92370..a15670b 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -89,6 +89,11 @@ public: void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code); void DiscordOnThreadUpdate(const ThreadUpdateData &data); +#ifdef WITH_VOICE + void OnVoiceConnected(); + void OnVoiceDisconnected(); +#endif + SettingsManager::Settings &GetSettings(); Glib::RefPtr GetStyleProvider(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index b665c81..797d899 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -107,8 +107,28 @@ AudioManager::AudioManager() { AudioManager::~AudioManager() { ma_device_uninit(&m_device); ma_device_uninit(&m_capture_device); - for (auto &[ssrc, pair] : m_sources) { - opus_decoder_destroy(pair.second); + RemoveAllSSRCs(); +} + +void AudioManager::AddSSRC(uint32_t ssrc) { + int error; + 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) { + if (auto it = m_sources.find(ssrc); it != m_sources.end()) { + opus_decoder_destroy(it->second.second); + m_sources.erase(it); + } +} + +void AudioManager::RemoveAllSSRCs() { + puts("remove all ssrc"); + for (auto it = m_sources.begin(); it != m_sources.end();) { + opus_decoder_destroy(it->second.second); + m_sources.erase(it); + it++; } } @@ -120,18 +140,15 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); static std::array pcm; - if (m_sources.find(ssrc) == m_sources.end()) { - int err; - auto *decoder = opus_decoder_create(48000, 2, &err); - m_sources.insert(std::make_pair(ssrc, std::make_pair(std::deque {}, decoder))); - } - int decoded = opus_decode(m_sources.at(ssrc).second, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); - if (decoded <= 0) { - } else { - m_mutex.lock(); - auto &buf = m_sources.at(ssrc).first; - buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2); - m_mutex.unlock(); + if (auto it = m_sources.find(ssrc); it != m_sources.end()) { + int decoded = opus_decode(it->second.second, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); + if (decoded <= 0) { + } else { + m_mutex.lock(); + auto &buf = it->second.first; + buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2); + m_mutex.unlock(); + } } } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 700fcc0..3ba6e29 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -18,6 +18,10 @@ public: AudioManager(); ~AudioManager(); + void AddSSRC(uint32_t ssrc); + void RemoveSSRC(uint32_t ssrc); + void RemoveAllSSRCs(); + void SetOpusBuffer(uint8_t *ptr); void FeedMeOpus(uint32_t ssrc, const std::vector &data); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2fff2a1..4202f85 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -23,6 +23,14 @@ DiscordClient::DiscordClient(bool mem_store) m_websocket.signal_open().connect(sigc::mem_fun(*this, &DiscordClient::HandleSocketOpen)); m_websocket.signal_close().connect(sigc::mem_fun(*this, &DiscordClient::HandleSocketClose)); +#ifdef WITH_VOICE + m_voice.signal_connected().connect(sigc::mem_fun(*this, &DiscordClient::OnVoiceConnected)); + m_voice.signal_disconnected().connect(sigc::mem_fun(*this, &DiscordClient::OnVoiceDisconnected)); + m_voice.signal_speaking().connect([this](const VoiceSpeakingData &data) { + m_signal_voice_speaking.emit(data); + }); +#endif + LoadEventMap(); } @@ -2135,11 +2143,16 @@ void DiscordClient::HandleGatewayGuildMembersChunk(const GatewayMessage &msg) { #ifdef WITH_VOICE void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { - VoiceStateUpdateData data = msg.Data; + VoiceState data = msg.Data; if (data.UserID == m_user_data.ID) { printf("voice session id: %s\n", data.SessionID.c_str()); m_voice.SetSessionID(data.SessionID); } + if (data.ChannelID.has_value()) { + SetVoiceState(data.UserID, *data.ChannelID); + } else { + ClearVoiceState(data.UserID); + } } void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { @@ -2170,6 +2183,15 @@ void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { m_user_to_status[p.UserID] = PresenceStatus::DND; m_signal_presence_update.emit(*user, m_user_to_status.at(p.UserID)); } +#ifdef WITH_VOICE + for (const auto &g : data.Guilds) { + for (const auto &s : g.VoiceStates) { + if (s.ChannelID.has_value()) { + SetVoiceState(s.UserID, *s.ChannelID); + } + } + } +#endif } void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) { @@ -2602,6 +2624,29 @@ void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { } } +#ifdef WITH_VOICE +void DiscordClient::SetVoiceState(Snowflake user_id, Snowflake channel_id) { + m_voice_state_user_channel[user_id] = channel_id; + m_voice_state_channel_users[channel_id].insert(user_id); +} + +void DiscordClient::ClearVoiceState(Snowflake user_id) { + if (const auto it = m_voice_state_user_channel.find(user_id); it != m_voice_state_user_channel.end()) { + m_voice_state_channel_users[it->second].erase(user_id); + // invalidated + m_voice_state_user_channel.erase(user_id); + } +} + +void DiscordClient::OnVoiceConnected() { + m_signal_voice_connected.emit(); +} + +void DiscordClient::OnVoiceDisconnected() { + m_signal_voice_disconnected.emit(); +} +#endif + void DiscordClient::LoadEventMap() { m_event_map["READY"] = GatewayEvent::READY; m_event_map["MESSAGE_CREATE"] = GatewayEvent::MESSAGE_CREATE; @@ -2858,3 +2903,15 @@ DiscordClient::type_signal_channel_accessibility_changed DiscordClient::signal_c DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_fail() { return m_signal_message_send_fail; } + +DiscordClient::type_signal_voice_connected DiscordClient::signal_voice_connected() { + return m_signal_voice_connected; +} + +DiscordClient::type_signal_voice_disconnected DiscordClient::signal_voice_disconnected() { + return m_signal_voice_disconnected; +} + +DiscordClient::type_signal_voice_speaking DiscordClient::signal_voice_speaking() { + return m_signal_voice_speaking; +} diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 0b88519..fb0b46d 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -339,6 +339,15 @@ private: DiscordVoiceClient m_voice; Snowflake m_voice_channel_id; + // todo sql i guess + std::unordered_map m_voice_state_user_channel; + std::unordered_map> m_voice_state_channel_users; + + void SetVoiceState(Snowflake user_id, Snowflake channel_id); + void ClearVoiceState(Snowflake user_id); + + void OnVoiceConnected(); + void OnVoiceDisconnected(); #endif mutable std::mutex m_msg_mutex; @@ -413,6 +422,10 @@ public: typedef sigc::signal type_signal_connected; typedef sigc::signal type_signal_message_progress; + using type_signal_voice_connected = sigc::signal; + using type_signal_voice_disconnected = sigc::signal; + using type_signal_voice_speaking = sigc::signal; + type_signal_gateway_ready signal_gateway_ready(); type_signal_message_create signal_message_create(); type_signal_message_delete signal_message_delete(); @@ -467,6 +480,10 @@ public: type_signal_connected signal_connected(); type_signal_message_progress signal_message_progress(); + type_signal_voice_connected signal_voice_connected(); + type_signal_voice_disconnected signal_voice_disconnected(); + type_signal_voice_speaking signal_voice_speaking(); + protected: type_signal_gateway_ready m_signal_gateway_ready; type_signal_message_create m_signal_message_create; @@ -521,4 +538,8 @@ protected: type_signal_disconnected m_signal_disconnected; type_signal_connected m_signal_connected; type_signal_message_progress m_signal_message_progress; + + type_signal_voice_connected m_signal_voice_connected; + type_signal_voice_disconnected m_signal_voice_disconnected; + type_signal_voice_speaking m_signal_voice_speaking; }; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 3cdc6b5..21853a7 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -233,6 +233,11 @@ void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m) { JS_D("friends", m.Friends); } +void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) { + JS_D("id", m.ID); + JS_D("voice_states", m.VoiceStates); +} + void from_json(const nlohmann::json &j, ReadySupplementalData &m) { JS_D("merged_presences", m.MergedPresences); } @@ -658,14 +663,24 @@ void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m) { // j["d"]["preferred_region"] = m.PreferredRegion; } -void from_json(const nlohmann::json &j, VoiceStateUpdateData &m) { - JS_ON("user_id", m.UserID); - JS_ON("session_id", m.SessionID); -} - void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) { JS_D("token", m.Token); JS_D("guild_id", m.GuildID); JS_D("endpoint", m.Endpoint); } #endif + +void from_json(const nlohmann::json &j, VoiceState &m) { + JS_O("guild_id", m.GuildID); + JS_N("channel_id", m.ChannelID); + JS_D("deaf", m.IsDeafened); + JS_D("mute", m.IsMuted); + JS_D("self_deaf", m.IsSelfDeafened); + JS_D("self_mute", m.IsSelfMuted); + JS_D("self_video", m.IsSelfVideo); + JS_O("self_stream", m.IsSelfStream); + JS_D("suppress", m.IsSuppressed); + JS_D("user_id", m.UserID); + JS_N("member", m.Member); + JS_D("session_id", m.SessionID); +} diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 0a947d4..14dd20c 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -354,8 +354,18 @@ struct SupplementalMergedPresencesData { friend void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m); }; +struct VoiceState; +struct SupplementalGuildEntry { + // std::vector EmbeddedActivities; + Snowflake ID; + std::vector VoiceStates; + + friend void from_json(const nlohmann::json &j, SupplementalGuildEntry &m); +}; + struct ReadySupplementalData { SupplementalMergedPresencesData MergedPresences; + std::vector Guilds; friend void from_json(const nlohmann::json &j, ReadySupplementalData &m); }; @@ -879,13 +889,6 @@ struct VoiceStateUpdateMessage { friend void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m); }; -struct VoiceStateUpdateData { - Snowflake UserID; - std::string SessionID; - - friend void from_json(const nlohmann::json &j, VoiceStateUpdateData &m); -}; - struct VoiceServerUpdateData { std::string Token; Snowflake GuildID; @@ -894,3 +897,20 @@ struct VoiceServerUpdateData { friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m); }; #endif + +struct VoiceState { + std::optional ChannelID; + bool IsDeafened; + bool IsMuted; + std::optional GuildID; + std::optional Member; + bool IsSelfDeafened; + bool IsSelfMuted; + bool IsSelfVideo; + bool IsSelfStream = false; + std::string SessionID; + bool IsSuppressed; + Snowflake UserID; + + friend void from_json(const nlohmann::json &j, VoiceState &m); +}; diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 6d45241..b7fe21b 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -1,5 +1,5 @@ #ifdef WITH_VOICE - // clang-format off +// clang-format off #include "voiceclient.hpp" #include "json.hpp" #include @@ -186,6 +186,7 @@ DiscordVoiceClient::~DiscordVoiceClient() { void DiscordVoiceClient::Start() { m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); + m_heartbeat_waiter.revive(); } void DiscordVoiceClient::Stop() { @@ -195,6 +196,7 @@ void DiscordVoiceClient::Stop() { m_heartbeat_waiter.kill(); if (m_heartbeat_thread.joinable()) m_heartbeat_thread.join(); m_connected = false; + m_signal_disconnected.emit(); } } @@ -235,6 +237,9 @@ void DiscordVoiceClient::OnGatewayMessage(const std::string &str) { case VoiceGatewayOp::SessionDescription: { HandleGatewaySessionDescription(msg); } break; + case VoiceGatewayOp::Speaking: { + HandleGatewaySpeaking(msg); + } break; default: break; } } @@ -273,7 +278,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa VoiceSpeakingMessage msg; msg.Delay = 0; msg.SSRC = m_ssrc; - msg.Speaking = VoiceSpeakingMessage::Microphone; + msg.Speaking = VoiceSpeakingType::Microphone; m_ws.Send(msg); m_secret_key = d.SecretKey; @@ -286,6 +291,12 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa m_udp.SendEncrypted({ 0xF8, 0xFF, 0xFE }); m_udp.Run(); m_connected = true; + m_signal_connected.emit(); +} + +void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { + VoiceSpeakingData data = m.Data; + m_signal_speaking.emit(data); } void DiscordVoiceClient::Identify() { @@ -365,6 +376,18 @@ void DiscordVoiceClient::HeartbeatThread() { } } +DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_connected() { + return m_signal_connected; +} + +DiscordVoiceClient::type_signal_disconnected DiscordVoiceClient::signal_disconnected() { + return m_signal_disconnected; +} + +DiscordVoiceClient::type_signal_speaking DiscordVoiceClient::signal_speaking() { + return m_signal_speaking; +} + void from_json(const nlohmann::json &j, VoiceGatewayMessage &m) { JS_D("op", m.Opcode); m.Data = j.at("d"); @@ -431,4 +454,10 @@ void to_json(nlohmann::json &j, const VoiceSpeakingMessage &m) { j["d"]["delay"] = m.Delay; j["d"]["ssrc"] = m.SSRC; } + +void from_json(const nlohmann::json &j, VoiceSpeakingData &m) { + JS_D("user_id", m.UserID); + JS_D("ssrc", m.SSRC); + JS_D("speaking", m.Speaking); +} #endif diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index f81763b..c052f28 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -1,6 +1,6 @@ #pragma once #ifdef WITH_VOICE -// clang-format off + // clang-format off #include "snowflake.hpp" #include "waiter.hpp" #include "websocket.hpp" @@ -8,6 +8,7 @@ #include #include #include +#include // clang-format on enum class VoiceGatewayCloseCode : uint16_t { @@ -110,20 +111,28 @@ struct VoiceSessionDescriptionData { friend void from_json(const nlohmann::json &j, VoiceSessionDescriptionData &m); }; -struct VoiceSpeakingMessage { - enum { - Microphone = 1 << 0, - Soundshare = 1 << 1, - Priority = 1 << 2, - }; +enum class VoiceSpeakingType { + Microphone = 1 << 0, + Soundshare = 1 << 1, + Priority = 1 << 2, +}; - int Speaking; +struct VoiceSpeakingMessage { + VoiceSpeakingType Speaking; int Delay; uint32_t SSRC; friend void to_json(nlohmann::json &j, const VoiceSpeakingMessage &m); }; +struct VoiceSpeakingData { + Snowflake UserID; + uint32_t SSRC; + VoiceSpeakingType Speaking; + + friend void from_json(const nlohmann::json &j, VoiceSpeakingData &m); +}; + class UDPSocket { public: UDPSocket(); @@ -188,6 +197,7 @@ private: void HandleGatewayHello(const VoiceGatewayMessage &m); void HandleGatewayReady(const VoiceGatewayMessage &m); void HandleGatewaySessionDescription(const VoiceGatewayMessage &m); + void HandleGatewaySpeaking(const VoiceGatewayMessage &m); void Identify(); void Discovery(); @@ -228,5 +238,17 @@ private: std::array m_opus_buffer; std::atomic m_connected = false; + + using type_signal_connected = sigc::signal; + using type_signal_disconnected = sigc::signal; + using type_signal_speaking = sigc::signal; + type_signal_connected m_signal_connected; + type_signal_disconnected m_signal_disconnected; + type_signal_speaking m_signal_speaking; + +public: + type_signal_connected signal_connected(); + type_signal_disconnected signal_disconnected(); + type_signal_speaking signal_speaking(); }; #endif -- cgit v1.2.3 From a96d96b3aa883c5ee5892e4ff94e3c539989c66a Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 28 Sep 2022 22:10:36 -0400 Subject: basic mute/deafen --- src/abaddon.cpp | 20 ++++++++++++++++++++ src/audio/manager.cpp | 12 +++++++++++- src/audio/manager.hpp | 7 +++++++ src/discord/discord.cpp | 27 +++++++++++++++++++++++++++ src/discord/discord.hpp | 14 ++++++++++++++ src/windows/voicewindow.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/windows/voicewindow.hpp | 30 ++++++++++++++++++++++++++++++ 7 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/windows/voicewindow.cpp create mode 100644 src/windows/voicewindow.hpp (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 3245ad4..678353c 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -18,6 +18,7 @@ #include "windows/profilewindow.hpp" #include "windows/pinnedwindow.hpp" #include "windows/threadswindow.hpp" +#include "windows/voicewindow.hpp" #include "startup.hpp" #ifdef WITH_LIBHANDY @@ -418,6 +419,25 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { + auto *wnd = new VoiceWindow; + + wnd->signal_mute().connect([this](bool is_mute) { + m_discord.SetVoiceMuted(is_mute); + m_audio->SetCapture(!is_mute); + }); + + wnd->signal_deafen().connect([this](bool is_deaf) { + m_discord.SetVoiceDeafened(is_deaf); + m_audio->SetPlayback(!is_deaf); + }); + + wnd->show(); + wnd->signal_hide().connect([this, wnd]() { + m_discord.DisconnectFromVoice(); + delete wnd; + delete m_user_menu; + SetupUserMenu(); + }); } void Abaddon::OnVoiceDisconnected() { diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 797d899..35a9c76 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -137,6 +137,8 @@ void AudioManager::SetOpusBuffer(uint8_t *ptr) { } void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { + if (!m_should_playback) return; + size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); static std::array pcm; @@ -152,8 +154,16 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { } } +void AudioManager::SetCapture(bool capture) { + m_should_capture = capture; +} + +void AudioManager::SetPlayback(bool playback) { + m_should_playback = playback; +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { - if (m_opus_buffer == nullptr) return; + if (m_opus_buffer == nullptr || !m_should_capture) return; int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 3ba6e29..4986da6 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -1,6 +1,7 @@ #pragma once #ifdef WITH_VOICE // clang-format off + #include #include #include @@ -25,6 +26,9 @@ public: void SetOpusBuffer(uint8_t *ptr); void FeedMeOpus(uint32_t ssrc, const std::vector &data); + void SetCapture(bool capture); + void SetPlayback(bool playback); + [[nodiscard]] bool OK() const; private: @@ -51,6 +55,9 @@ private: uint8_t *m_opus_buffer = nullptr; + std::atomic m_should_capture = true; + std::atomic m_should_playback = true; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 4202f85..f5a1e1b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1202,6 +1202,16 @@ bool DiscordClient::IsConnectedToVoice() const noexcept { Snowflake DiscordClient::GetVoiceChannelID() const noexcept { return m_voice_channel_id; } + +void DiscordClient::SetVoiceMuted(bool is_mute) { + m_mute_requested = is_mute; + SendVoiceStateUpdate(); +} + +void DiscordClient::SetVoiceDeafened(bool is_deaf) { + m_deaf_requested = is_deaf; + SendVoiceStateUpdate(); +} #endif void DiscordClient::SetReferringChannel(Snowflake id) { @@ -2625,6 +2635,21 @@ void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { } #ifdef WITH_VOICE +void DiscordClient::SendVoiceStateUpdate() { + VoiceStateUpdateMessage msg; + msg.ChannelID = m_voice_channel_id; + const auto channel = GetChannel(m_voice_channel_id); + if (channel.has_value() && channel->GuildID.has_value()) { + msg.GuildID = *channel->GuildID; + } + + msg.SelfMute = m_mute_requested; + msg.SelfDeaf = m_deaf_requested; + msg.SelfVideo = false; + + m_websocket.Send(msg); +} + void DiscordClient::SetVoiceState(Snowflake user_id, Snowflake channel_id) { m_voice_state_user_channel[user_id] = channel_id; m_voice_state_channel_users[channel_id].insert(user_id); @@ -2904,6 +2929,7 @@ DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_ return m_signal_message_send_fail; } +#ifdef WITH_VOICE DiscordClient::type_signal_voice_connected DiscordClient::signal_voice_connected() { return m_signal_voice_connected; } @@ -2915,3 +2941,4 @@ DiscordClient::type_signal_voice_disconnected DiscordClient::signal_voice_discon DiscordClient::type_signal_voice_speaking DiscordClient::signal_voice_speaking() { return m_signal_voice_speaking; } +#endif diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fb0b46d..b0fecab 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -186,6 +186,9 @@ public: void DisconnectFromVoice(); [[nodiscard]] bool IsConnectedToVoice() const noexcept; [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; + + void SetVoiceMuted(bool is_mute); + void SetVoiceDeafened(bool is_deaf); #endif void SetReferringChannel(Snowflake id); @@ -338,11 +341,16 @@ private: #ifdef WITH_VOICE DiscordVoiceClient m_voice; + bool m_mute_requested = false; + bool m_deaf_requested = false; + Snowflake m_voice_channel_id; // todo sql i guess std::unordered_map m_voice_state_user_channel; std::unordered_map> m_voice_state_channel_users; + void SendVoiceStateUpdate(); + void SetVoiceState(Snowflake user_id, Snowflake channel_id); void ClearVoiceState(Snowflake user_id); @@ -422,9 +430,11 @@ public: typedef sigc::signal type_signal_connected; typedef sigc::signal type_signal_message_progress; +#ifdef WITH_VOICE using type_signal_voice_connected = sigc::signal; using type_signal_voice_disconnected = sigc::signal; using type_signal_voice_speaking = sigc::signal; +#endif type_signal_gateway_ready signal_gateway_ready(); type_signal_message_create signal_message_create(); @@ -480,9 +490,11 @@ public: type_signal_connected signal_connected(); type_signal_message_progress signal_message_progress(); +#ifdef WITH_VOICE type_signal_voice_connected signal_voice_connected(); type_signal_voice_disconnected signal_voice_disconnected(); type_signal_voice_speaking signal_voice_speaking(); +#endif protected: type_signal_gateway_ready m_signal_gateway_ready; @@ -539,7 +551,9 @@ protected: type_signal_connected m_signal_connected; type_signal_message_progress m_signal_message_progress; +#ifdef WITH_VOICE type_signal_voice_connected m_signal_voice_connected; type_signal_voice_disconnected m_signal_voice_disconnected; type_signal_voice_speaking m_signal_voice_speaking; +#endif }; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp new file mode 100644 index 0000000..b352c86 --- /dev/null +++ b/src/windows/voicewindow.cpp @@ -0,0 +1,36 @@ +#include "voicewindow.hpp" + +VoiceWindow::VoiceWindow() + : m_main(Gtk::ORIENTATION_VERTICAL) + , m_controls(Gtk::ORIENTATION_HORIZONTAL) + , m_mute("Mute") + , m_deafen("Deafen") { + get_style_context()->add_class("app-window"); + + set_default_size(300, 300); + + m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); + m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); + + m_controls.add(m_mute); + m_controls.add(m_deafen); + m_main.add(m_controls); + add(m_main); + show_all_children(); +} + +void VoiceWindow::OnMuteChanged() { + m_signal_mute.emit(m_mute.get_active()); +} + +void VoiceWindow::OnDeafenChanged() { + m_signal_deafen.emit(m_deafen.get_active()); +} + +VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { + return m_signal_mute; +} + +VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { + return m_signal_deafen; +} diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp new file mode 100644 index 0000000..bee0a84 --- /dev/null +++ b/src/windows/voicewindow.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + +class VoiceWindow : public Gtk::Window { +public: + VoiceWindow(); + +private: + void OnMuteChanged(); + void OnDeafenChanged(); + + Gtk::Box m_main; + Gtk::Box m_controls; + + Gtk::CheckButton m_mute; + Gtk::CheckButton m_deafen; + +public: + using type_signal_mute = sigc::signal; + using type_signal_deafen = sigc::signal; + + type_signal_mute signal_mute(); + type_signal_deafen signal_deafen(); + +private: + type_signal_mute m_signal_mute; + type_signal_deafen m_signal_deafen; +}; -- cgit v1.2.3 From dc127d15fb49c108450e691c44ed930a11bb7e59 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 29 Sep 2022 21:46:15 -0400 Subject: display user list, client side mute --- res/css/application-low-priority.css | 2 +- src/abaddon.cpp | 8 ++++- src/audio/manager.cpp | 16 ++++++++- src/audio/manager.hpp | 7 ++++ src/discord/discord.cpp | 8 +++++ src/discord/discord.hpp | 2 ++ src/discord/objects.cpp | 3 +- src/discord/voiceclient.cpp | 9 +++++ src/discord/voiceclient.hpp | 9 ++++- src/windows/voicewindow.cpp | 70 ++++++++++++++++++++++++++++++++++-- src/windows/voicewindow.hpp | 14 +++++++- 11 files changed, 140 insertions(+), 8 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/res/css/application-low-priority.css b/res/css/application-low-priority.css index cf108f4..5078d22 100644 --- a/res/css/application-low-priority.css +++ b/res/css/application-low-priority.css @@ -44,7 +44,7 @@ has to be separate to allow main.css to override certain things background: @secondary_color; } -.app-popup list { +.app-window list, .app-popup list { background: @secondary_color; } diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 678353c..4c879ca 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -419,7 +419,7 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { - auto *wnd = new VoiceWindow; + auto *wnd = new VoiceWindow(m_discord.GetVoiceChannelID()); wnd->signal_mute().connect([this](bool is_mute) { m_discord.SetVoiceMuted(is_mute); @@ -431,6 +431,12 @@ void Abaddon::OnVoiceConnected() { m_audio->SetPlayback(!is_deaf); }); + wnd->signal_mute_user_cs().connect([this](Snowflake id, bool is_mute) { + if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { + m_audio->SetMuteSSRC(*ssrc, is_mute); + } + }); + wnd->show(); wnd->signal_hide().connect([this, wnd]() { m_discord.DisconnectFromVoice(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 35a9c76..e025390 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -1,5 +1,6 @@ #ifdef WITH_VOICE - // clang-format off +// clang-format off + #ifdef _WIN32 #include #endif @@ -138,6 +139,10 @@ void AudioManager::SetOpusBuffer(uint8_t *ptr) { void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { if (!m_should_playback) return; + { + std::lock_guard _(m_muted_ssrc_mutex); + if (m_muted_ssrcs.find(ssrc) != m_muted_ssrcs.end()) return; + } size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); @@ -162,6 +167,15 @@ void AudioManager::SetPlayback(bool playback) { m_should_playback = playback; } +void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { + std::lock_guard _(m_muted_ssrc_mutex); + if (mute) { + m_muted_ssrcs.insert(ssrc); + } else { + m_muted_ssrcs.erase(ssrc); + } +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 4986da6..277f2f3 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,8 @@ public: void SetCapture(bool capture); void SetPlayback(bool playback); + void SetMuteSSRC(uint32_t ssrc, bool mute); + [[nodiscard]] bool OK() const; private: @@ -58,6 +61,10 @@ private: std::atomic m_should_capture = true; std::atomic m_should_playback = true; + std::unordered_set m_muted_ssrcs; + + mutable std::mutex m_muted_ssrc_mutex; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index f5a1e1b..b6bcc07 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1203,6 +1203,14 @@ Snowflake DiscordClient::GetVoiceChannelID() const noexcept { return m_voice_channel_id; } +std::unordered_set DiscordClient::GetUsersInVoiceChannel(Snowflake channel_id) { + return m_voice_state_channel_users[channel_id]; +} + +std::optional DiscordClient::GetSSRCOfUser(Snowflake id) const { + return m_voice.GetSSRCOfUser(id); +} + void DiscordClient::SetVoiceMuted(bool is_mute) { m_mute_requested = is_mute; SendVoiceStateUpdate(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index b0fecab..9c6c748 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -186,6 +186,8 @@ public: void DisconnectFromVoice(); [[nodiscard]] bool IsConnectedToVoice() const noexcept; [[nodiscard]] Snowflake GetVoiceChannelID() const noexcept; + [[nodiscard]] std::unordered_set GetUsersInVoiceChannel(Snowflake channel_id); + [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; void SetVoiceMuted(bool is_mute); void SetVoiceDeafened(bool is_deaf); diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 21853a7..3e87e43 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -240,6 +240,7 @@ void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) { void from_json(const nlohmann::json &j, ReadySupplementalData &m) { JS_D("merged_presences", m.MergedPresences); + JS_D("guilds", m.Guilds); } void to_json(nlohmann::json &j, const IdentifyProperties &m) { @@ -681,6 +682,6 @@ void from_json(const nlohmann::json &j, VoiceState &m) { JS_O("self_stream", m.IsSelfStream); JS_D("suppress", m.IsSuppressed); JS_D("user_id", m.UserID); - JS_N("member", m.Member); + JS_ON("member", m.Member); JS_D("session_id", m.SessionID); } diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index b7fe21b..5f6ab31 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -1,5 +1,6 @@ #ifdef WITH_VOICE // clang-format off + #include "voiceclient.hpp" #include "json.hpp" #include @@ -220,6 +221,13 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { m_user_id = id; } +std::optional DiscordVoiceClient::GetSSRCOfUser(Snowflake id) const { + if (const auto it = m_ssrc_map.find(id); it != m_ssrc_map.end()) { + return it->second; + } + return {}; +} + bool DiscordVoiceClient::IsConnected() const noexcept { return m_connected; } @@ -296,6 +304,7 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { VoiceSpeakingData data = m.Data; + m_ssrc_map[data.UserID] = data.SSRC; m_signal_speaking.emit(data); } diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index c052f28..1dc6e1a 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -1,14 +1,17 @@ #pragma once #ifdef WITH_VOICE - // clang-format off +// clang-format off + #include "snowflake.hpp" #include "waiter.hpp" #include "websocket.hpp" +#include #include #include #include #include #include +#include // clang-format on enum class VoiceGatewayCloseCode : uint16_t { @@ -190,6 +193,8 @@ public: void SetServerID(Snowflake id); void SetUserID(Snowflake id); + [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; + [[nodiscard]] bool IsConnected() const noexcept; private: @@ -218,6 +223,8 @@ private: uint16_t m_port; uint32_t m_ssrc; + std::unordered_map m_ssrc_map; + std::array m_secret_key; Websocket m_ws; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index b352c86..17f8ec2 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -1,24 +1,86 @@ #include "voicewindow.hpp" +#include "components/lazyimage.hpp" +#include "abaddon.hpp" -VoiceWindow::VoiceWindow() +class VoiceWindowUserListEntry : public Gtk::ListBoxRow { +public: + VoiceWindowUserListEntry(Snowflake id) + : m_main(Gtk::ORIENTATION_HORIZONTAL) + , m_avatar(32, 32) + , m_mute("Mute") { + m_name.set_halign(Gtk::ALIGN_START); + m_name.set_hexpand(true); + m_mute.set_halign(Gtk::ALIGN_END); + + m_main.add(m_avatar); + m_main.add(m_name); + m_main.add(m_mute); + add(m_main); + show_all_children(); + + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto user = discord.GetUser(id); + if (user.has_value()) { + m_name.set_text(user->Username); + } else { + m_name.set_text("Unknown user"); + } + + m_mute.signal_toggled().connect([this]() { + m_signal_mute_cs.emit(m_mute.get_active()); + }); + } + +private: + Gtk::Box m_main; + LazyImage m_avatar; + Gtk::Label m_name; + Gtk::CheckButton m_mute; + +public: + using type_signal_mute_cs = sigc::signal; + type_signal_mute_cs signal_mute_cs() { + return m_signal_mute_cs; + } + +private: + type_signal_mute_cs m_signal_mute_cs; +}; + +VoiceWindow::VoiceWindow(Snowflake channel_id) : m_main(Gtk::ORIENTATION_VERTICAL) , m_controls(Gtk::ORIENTATION_HORIZONTAL) , m_mute("Mute") - , m_deafen("Deafen") { + , m_deafen("Deafen") + , m_channel_id(channel_id) { get_style_context()->add_class("app-window"); set_default_size(300, 300); + auto &discord = Abaddon::Get().GetDiscordClient(); + SetUsers(discord.GetUsersInVoiceChannel(m_channel_id)); + m_mute.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnMuteChanged)); m_deafen.signal_toggled().connect(sigc::mem_fun(*this, &VoiceWindow::OnDeafenChanged)); m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); + m_main.add(m_user_list); add(m_main); show_all_children(); } +void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { + for (auto id : user_ids) { + auto *row = Gtk::make_managed(id); + row->signal_mute_cs().connect([this, id](bool is_muted) { + m_signal_mute_user_cs.emit(id, is_muted); + }); + m_user_list.add(*row); + } +} + void VoiceWindow::OnMuteChanged() { m_signal_mute.emit(m_mute.get_active()); } @@ -34,3 +96,7 @@ VoiceWindow::type_signal_mute VoiceWindow::signal_mute() { VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { return m_signal_deafen; } + +VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() { + return m_signal_mute_user_cs; +} diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index bee0a84..a2162b4 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -1,13 +1,18 @@ #pragma once +#include "discord/snowflake.hpp" #include #include +#include #include +#include class VoiceWindow : public Gtk::Window { public: - VoiceWindow(); + VoiceWindow(Snowflake channel_id); private: + void SetUsers(const std::unordered_set &user_ids); + void OnMuteChanged(); void OnDeafenChanged(); @@ -17,14 +22,21 @@ private: Gtk::CheckButton m_mute; Gtk::CheckButton m_deafen; + Gtk::ListBox m_user_list; + + Snowflake m_channel_id; + public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; + using type_signal_mute_user_cs = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); + type_signal_mute_user_cs signal_mute_user_cs(); private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; + type_signal_mute_user_cs m_signal_mute_user_cs; }; -- cgit v1.2.3 From f8ae99ee7bdc9c65938f083d3c98ba86cce94706 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 29 Sep 2022 22:47:10 -0400 Subject: fix crash on disconnect --- src/audio/manager.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index e025390..de04866 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -126,11 +126,10 @@ void AudioManager::RemoveSSRC(uint32_t ssrc) { void AudioManager::RemoveAllSSRCs() { puts("remove all ssrc"); - for (auto it = m_sources.begin(); it != m_sources.end();) { - opus_decoder_destroy(it->second.second); - m_sources.erase(it); - it++; + for (auto &[ssrc, pair] : m_sources) { + opus_decoder_destroy(pair.second); } + m_sources.clear(); } void AudioManager::SetOpusBuffer(uint8_t *ptr) { -- cgit v1.2.3 From 0438b11c910e1bfea2d82d61ed24ec9f47a20598 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 30 Sep 2022 01:09:51 -0400 Subject: dont dispatch udp to main --- src/audio/manager.cpp | 13 +++++++++---- src/discord/voiceclient.cpp | 16 +--------------- src/discord/voiceclient.hpp | 4 ---- 3 files changed, 10 insertions(+), 23 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index de04866..8321c3e 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -112,12 +112,16 @@ AudioManager::~AudioManager() { } void AudioManager::AddSSRC(uint32_t ssrc) { + std::lock_guard _(m_mutex); int error; - auto *decoder = opus_decoder_create(48000, 2, &error); - m_sources.insert(std::make_pair(ssrc, std::make_pair(std::deque {}, decoder))); + 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); @@ -126,6 +130,7 @@ void AudioManager::RemoveSSRC(uint32_t ssrc) { void AudioManager::RemoveAllSSRCs() { puts("remove all ssrc"); + std::lock_guard _(m_mutex); for (auto &[ssrc, pair] : m_sources) { opus_decoder_destroy(pair.second); } @@ -146,14 +151,14 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); static std::array pcm; + + std::lock_guard _(m_mutex); if (auto it = m_sources.find(ssrc); it != m_sources.end()) { int decoded = opus_decode(it->second.second, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); if (decoded <= 0) { } else { - m_mutex.lock(); auto &buf = it->second.first; buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2); - m_mutex.unlock(); } } } diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index 7b4989e..0b87032 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -141,9 +141,7 @@ DiscordVoiceClient::DiscordVoiceClient() { }); m_udp.signal_data().connect([this](const std::vector &data) { - std::lock_guard _(m_udp_dispatch_mutex); - m_udp_message_queue.push(data); - m_udp_dispatcher.emit(); + OnUDPData(data); }); m_dispatcher.connect([this]() { @@ -158,18 +156,6 @@ DiscordVoiceClient::DiscordVoiceClient() { OnGatewayMessage(msg); }); - m_udp_dispatcher.connect([this]() { - m_udp_dispatch_mutex.lock(); - if (m_udp_message_queue.empty()) { - m_udp_dispatch_mutex.unlock(); - return; - } - auto data = std::move(m_udp_message_queue.front()); - m_udp_message_queue.pop(); - m_udp_dispatch_mutex.unlock(); - OnUDPData(data); - }); - Glib::signal_idle().connect_once([this]() { // cant put in ctor or deadlock in singleton initialization auto &aud = Abaddon::Get().GetAudio(); diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 634c333..1d8b952 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -235,10 +235,6 @@ private: std::queue m_message_queue; std::mutex m_dispatch_mutex; - Glib::Dispatcher m_udp_dispatcher; - std::queue> m_udp_message_queue; - std::mutex m_udp_dispatch_mutex; - int m_heartbeat_msec; Waiter m_heartbeat_waiter; std::thread m_heartbeat_thread; -- cgit v1.2.3 From 92c70bda08472d880e23df30661376d526406230 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 5 Oct 2022 18:43:44 -0400 Subject: add per user volume slider --- res/css/application-low-priority.css | 8 ++++++++ src/abaddon.cpp | 6 ++++++ src/audio/manager.cpp | 14 +++++++++++++- src/audio/manager.hpp | 3 +++ src/windows/voicewindow.cpp | 33 +++++++++++++++++++++++++++++---- src/windows/voicewindow.hpp | 3 +++ 6 files changed, 62 insertions(+), 5 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/res/css/application-low-priority.css b/res/css/application-low-priority.css index 5078d22..130033f 100644 --- a/res/css/application-low-priority.css +++ b/res/css/application-low-priority.css @@ -87,3 +87,11 @@ has to be separate to allow main.css to override certain things .app-window colorswatch { box-shadow: 0 1px rgba(0, 0, 0, 0); } + +.app-window scale { + padding-top: 0px; + padding-bottom: 0px; + margin-top: 0px; + margin-bottom: 0px; + color: @text_color; +} diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 6dc5fd1..8a92de7 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -438,6 +438,12 @@ void Abaddon::OnVoiceConnected() { } }); + wnd->signal_user_volume_changed().connect([this](Snowflake id, double volume) { + if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { + m_audio->SetVolumeSSRC(*ssrc, volume); + } + }); + wnd->show(); wnd->signal_hide().connect([this, wnd]() { m_discord.DisconnectFromVoice(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 8321c3e..37f696e 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -28,13 +28,18 @@ void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uin AudioManager *mgr = reinterpret_cast(pDevice->pUserData); if (mgr == nullptr) return; std::lock_guard _(mgr->m_mutex); + std::lock_guard _2(mgr->m_volume_ssrc_mutex); auto *pOutputF32 = static_cast(pOutput); for (auto &[ssrc, pair] : mgr->m_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] += buf[i] / 32768.F; + pOutputF32[i] += volume * buf[i] / 32768.F; } buf.erase(buf.begin(), buf.begin() + n); } @@ -180,6 +185,13 @@ void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { } } +void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { + std::lock_guard _(m_volume_ssrc_mutex); + volume *= 0.01; + constexpr const double E = 2.71828182845904523536; + m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 277f2f3..4d2299e 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -31,6 +31,7 @@ public: void SetPlayback(bool playback); void SetMuteSSRC(uint32_t ssrc, bool mute); + void SetVolumeSSRC(uint32_t ssrc, double volume); [[nodiscard]] bool OK() const; @@ -62,8 +63,10 @@ private: std::atomic m_should_playback = true; std::unordered_set m_muted_ssrcs; + std::unordered_map m_volume_ssrc; mutable std::mutex m_muted_ssrc_mutex; + mutable std::mutex m_volume_ssrc_mutex; public: using type_signal_opus_packet = sigc::signal; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 847ba81..ed9971e 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -10,16 +10,26 @@ class VoiceWindowUserListEntry : public Gtk::ListBoxRow { public: VoiceWindowUserListEntry(Snowflake id) - : m_main(Gtk::ORIENTATION_HORIZONTAL) + : m_main(Gtk::ORIENTATION_VERTICAL) + , m_horz(Gtk::ORIENTATION_HORIZONTAL) , m_avatar(32, 32) , m_mute("Mute") { m_name.set_halign(Gtk::ALIGN_START); m_name.set_hexpand(true); m_mute.set_halign(Gtk::ALIGN_END); - m_main.add(m_avatar); - m_main.add(m_name); - m_main.add(m_mute); + m_volume.set_range(0.0, 200.0); + m_volume.set_value_pos(Gtk::POS_LEFT); + m_volume.set_value(100.0); + m_volume.signal_value_changed().connect([this]() { + m_signal_volume.emit(m_volume.get_value()); + }); + + m_horz.add(m_avatar); + m_horz.add(m_name); + m_horz.add(m_mute); + m_main.add(m_horz); + m_main.add(m_volume); add(m_main); show_all_children(); @@ -38,18 +48,26 @@ public: private: Gtk::Box m_main; + Gtk::Box m_horz; LazyImage m_avatar; Gtk::Label m_name; Gtk::CheckButton m_mute; + Gtk::Scale m_volume; public: using type_signal_mute_cs = sigc::signal; + using type_signal_volume = sigc::signal; type_signal_mute_cs signal_mute_cs() { return m_signal_mute_cs; } + type_signal_volume signal_volume() { + return m_signal_volume; + } + private: type_signal_mute_cs m_signal_mute_cs; + type_signal_volume m_signal_volume; }; VoiceWindow::VoiceWindow(Snowflake channel_id) @@ -82,6 +100,9 @@ void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { row->signal_mute_cs().connect([this, id](bool is_muted) { m_signal_mute_user_cs.emit(id, is_muted); }); + row->signal_volume().connect([this, id](double volume) { + m_signal_user_volume_changed.emit(id, volume); + }); m_user_list.add(*row); } } @@ -105,4 +126,8 @@ VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() { return m_signal_mute_user_cs; } + +VoiceWindow::type_signal_user_volume_changed VoiceWindow::signal_user_volume_changed() { + return m_signal_user_volume_changed; +} #endif diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index 50943f4..da4987e 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -34,14 +34,17 @@ public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; using type_signal_mute_user_cs = sigc::signal; + using type_signal_user_volume_changed = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); type_signal_mute_user_cs signal_mute_user_cs(); + type_signal_user_volume_changed signal_user_volume_changed(); private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; type_signal_mute_user_cs m_signal_mute_user_cs; + type_signal_user_volume_changed m_signal_user_volume_changed; }; #endif -- cgit v1.2.3 From d2c9985c5793c8a6f20b01db87380d8bf241f077 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 5 Oct 2022 21:39:18 -0400 Subject: one mutex is enough --- src/audio/manager.cpp | 14 +++++--------- src/audio/manager.hpp | 3 --- 2 files changed, 5 insertions(+), 12 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 37f696e..cf6b545 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -28,7 +28,6 @@ void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uin AudioManager *mgr = reinterpret_cast(pDevice->pUserData); if (mgr == nullptr) return; std::lock_guard _(mgr->m_mutex); - std::lock_guard _2(mgr->m_volume_ssrc_mutex); auto *pOutputF32 = static_cast(pOutput); for (auto &[ssrc, pair] : mgr->m_sources) { @@ -148,16 +147,13 @@ void AudioManager::SetOpusBuffer(uint8_t *ptr) { void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { if (!m_should_playback) return; - { - std::lock_guard _(m_muted_ssrc_mutex); - if (m_muted_ssrcs.find(ssrc) != m_muted_ssrcs.end()) return; - } + + std::lock_guard _(m_mutex); + if (m_muted_ssrcs.find(ssrc) != m_muted_ssrcs.end()) return; size_t payload_size = 0; const auto *opus_encoded = StripRTPExtensionHeader(data.data(), static_cast(data.size()), payload_size); static std::array pcm; - - std::lock_guard _(m_mutex); if (auto it = m_sources.find(ssrc); it != m_sources.end()) { int decoded = opus_decode(it->second.second, opus_encoded, static_cast(payload_size), pcm.data(), 120 * 48, 0); if (decoded <= 0) { @@ -177,7 +173,7 @@ void AudioManager::SetPlayback(bool playback) { } void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { - std::lock_guard _(m_muted_ssrc_mutex); + std::lock_guard _(m_mutex); if (mute) { m_muted_ssrcs.insert(ssrc); } else { @@ -186,7 +182,7 @@ void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { } void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { - std::lock_guard _(m_volume_ssrc_mutex); + std::lock_guard _(m_mutex); volume *= 0.01; constexpr const double E = 2.71828182845904523536; m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 4d2299e..9d270fa 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -65,9 +65,6 @@ private: std::unordered_set m_muted_ssrcs; std::unordered_map m_volume_ssrc; - mutable std::mutex m_muted_ssrc_mutex; - mutable std::mutex m_volume_ssrc_mutex; - public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); -- cgit v1.2.3 From 621beb13444eb3ca16a756bfb8fdc6b4f79723f5 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 16 Oct 2022 23:12:26 -0400 Subject: basic volume meters --- src/audio/manager.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++ src/audio/manager.hpp | 14 ++++++++++++- src/windows/voicewindow.cpp | 21 +++++++++++++++++++ src/windows/voicewindow.hpp | 8 +++++++- 4 files changed, 90 insertions(+), 2 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index cf6b545..5bcc3bb 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -7,6 +7,7 @@ #include "manager.hpp" #include +#include #define MINIAUDIO_IMPLEMENTATION #include #include @@ -107,6 +108,8 @@ AudioManager::AudioManager() { char device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; ma_device_get_name(&m_capture_device, ma_device_type_capture, device_name, sizeof(device_name), nullptr); printf("using %s for capture\n", device_name); + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &AudioManager::DecayVolumeMeters), 40); } AudioManager::~AudioManager() { @@ -158,6 +161,7 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { int decoded = opus_decode(it->second.second, opus_encoded, static_cast(payload_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); } @@ -191,6 +195,8 @@ void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; + UpdateCaptureVolume(pcm, frames); + int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { printf("encoding error: %d\n", payload_len); @@ -199,10 +205,53 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { } } +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, ma_uint32 frames) { + for (ma_uint32 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 -= 300; + if (m_capture_peak_meter < 0) m_capture_peak_meter = 0; + + 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::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; +} + AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { return m_signal_opus_packet; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 9d270fa..43d8a1e 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -35,9 +35,18 @@ public: [[nodiscard]] bool OK() const; + [[nodiscard]] double GetCaptureVolumeLevel() const noexcept; + [[nodiscard]] double GetSSRCVolumeLevel(uint32_t ssrc) const noexcept; + private: void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames); + void UpdateReceiveVolume(uint32_t ssrc, const int16_t *pcm, int frames); + void UpdateCaptureVolume(const int16_t *pcm, ma_uint32 frames); + std::atomic m_capture_peak_meter = 0; + + bool DecayVolumeMeters(); + friend void data_callback(ma_device *, void *, const void *, ma_uint32); friend void capture_data_callback(ma_device *, void *, const void *, ma_uint32); @@ -52,7 +61,7 @@ private: ma_device m_capture_device; ma_device_config m_capture_config; - std::mutex m_mutex; + mutable std::mutex m_mutex; std::unordered_map, OpusDecoder *>> m_sources; OpusEncoder *m_encoder; @@ -65,6 +74,9 @@ private: std::unordered_set m_muted_ssrcs; std::unordered_map m_volume_ssrc; + mutable std::mutex m_vol_mtx; + std::unordered_map m_volumes; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 0af8176..0e42f32 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -5,6 +5,7 @@ #include "voicewindow.hpp" #include "components/lazyimage.hpp" #include "abaddon.hpp" +#include "audio/manager.hpp" // clang-format on class VoiceWindowUserListEntry : public Gtk::ListBoxRow { @@ -30,6 +31,7 @@ public: m_horz.add(m_mute); m_main.add(m_horz); m_main.add(m_volume); + m_main.add(m_meter); add(m_main); show_all_children(); @@ -47,6 +49,10 @@ public: }); } + void SetVolumeMeter(double frac) { + m_meter.set_fraction(frac); + } + private: Gtk::Box m_main; Gtk::Box m_horz; @@ -54,6 +60,7 @@ private: Gtk::Label m_name; Gtk::CheckButton m_mute; Gtk::Scale m_volume; + Gtk::ProgressBar m_meter; public: using type_signal_mute_cs = sigc::signal; @@ -98,9 +105,12 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); + m_main.add(m_capture_volume); m_main.add(m_scroll); add(m_main); show_all_children(); + + Glib::signal_timeout().connect(sigc::mem_fun(*this, &VoiceWindow::UpdateVoiceMeters), 40); } void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { @@ -130,6 +140,17 @@ void VoiceWindow::OnDeafenChanged() { m_signal_deafen.emit(m_deafen.get_active()); } +bool VoiceWindow::UpdateVoiceMeters() { + m_capture_volume.set_fraction(Abaddon::Get().GetAudio().GetCaptureVolumeLevel()); + for (auto [id, row] : m_rows) { + const auto ssrc = Abaddon::Get().GetDiscordClient().GetSSRCOfUser(id); + if (ssrc.has_value()) { + row->SetVolumeMeter(Abaddon::Get().GetAudio().GetSSRCVolumeLevel(*ssrc)); + } + } + return true; +} + void VoiceWindow::OnUserConnect(Snowflake user_id, Snowflake to_channel_id) { if (m_channel_id == to_channel_id) { if (auto it = m_rows.find(user_id); it == m_rows.end()) { diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index aaf77d7..d28a0cd 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -6,11 +6,13 @@ #include #include #include +#include #include #include #include // clang-format on +class VoiceWindowUserListEntry; class VoiceWindow : public Gtk::Window { public: VoiceWindow(Snowflake channel_id); @@ -26,6 +28,8 @@ private: void OnMuteChanged(); void OnDeafenChanged(); + bool UpdateVoiceMeters(); + Gtk::Box m_main; Gtk::Box m_controls; @@ -35,9 +39,11 @@ private: Gtk::ScrolledWindow m_scroll; Gtk::ListBox m_user_list; + Gtk::ProgressBar m_capture_volume; + Snowflake m_channel_id; - std::unordered_map m_rows; + std::unordered_map m_rows; public: using type_signal_mute = sigc::signal; -- cgit v1.2.3 From 5a3bce7498aea568cb6eb83d04313f8d777e8c94 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 18 Oct 2022 01:47:43 -0400 Subject: basic voice gate --- src/abaddon.cpp | 4 ++++ src/audio/manager.cpp | 6 ++++++ src/audio/manager.hpp | 4 ++++ src/windows/voicewindow.cpp | 12 ++++++++++++ src/windows/voicewindow.hpp | 5 +++++ 5 files changed, 31 insertions(+) (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 701a3f1..45fca80 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -430,6 +430,10 @@ void Abaddon::OnVoiceConnected() { m_audio->SetPlayback(!is_deaf); }); + wnd->signal_gate().connect([this](double gate) { + m_audio->SetCaptureGate(gate); + }); + wnd->signal_mute_user_cs().connect([this](Snowflake id, bool is_mute) { if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { m_audio->SetMuteSSRC(*ssrc, is_mute); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 5bcc3bb..7416ff7 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -176,6 +176,10 @@ void AudioManager::SetPlayback(bool playback) { m_should_playback = playback; } +void AudioManager::SetCaptureGate(double gate) { + m_capture_gate = gate * 0.01; +} + void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { std::lock_guard _(m_mutex); if (mute) { @@ -197,6 +201,8 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { UpdateCaptureVolume(pcm, frames); + if (m_capture_peak_meter / 32768.0 < m_capture_gate) return; + int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { printf("encoding error: %d\n", payload_len); diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 43d8a1e..67241fb 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -30,6 +30,8 @@ public: void SetCapture(bool capture); void SetPlayback(bool playback); + void SetCaptureGate(double gate); + void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); @@ -71,6 +73,8 @@ private: std::atomic m_should_capture = true; std::atomic m_should_playback = true; + std::atomic m_capture_gate = 0.0; + std::unordered_set m_muted_ssrcs; std::unordered_map m_volume_ssrc; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 0e42f32..596f502 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -101,11 +101,19 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_scroll.set_hexpand(true); m_scroll.set_vexpand(true); + m_capture_gate.set_range(0.0, 100.0); + m_capture_gate.set_value_pos(Gtk::POS_LEFT); + m_capture_gate.set_value(0.0); + m_capture_gate.signal_value_changed().connect([this]() { + m_signal_gate.emit(m_capture_gate.get_value()); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); m_main.add(m_capture_volume); + m_main.add(m_capture_gate); m_main.add(m_scroll); add(m_main); show_all_children(); @@ -176,6 +184,10 @@ VoiceWindow::type_signal_deafen VoiceWindow::signal_deafen() { return m_signal_deafen; } +VoiceWindow::type_signal_gate VoiceWindow::signal_gate() { + return m_signal_gate; +} + VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() { return m_signal_mute_user_cs; } diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index d28a0cd..5f5436b 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ private: Gtk::ListBox m_user_list; Gtk::ProgressBar m_capture_volume; + Gtk::Scale m_capture_gate; Snowflake m_channel_id; @@ -48,17 +50,20 @@ private: public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; + using type_signal_gate = sigc::signal; using type_signal_mute_user_cs = sigc::signal; using type_signal_user_volume_changed = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); + type_signal_gate signal_gate(); type_signal_mute_user_cs signal_mute_user_cs(); type_signal_user_volume_changed signal_user_volume_changed(); private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; + type_signal_gate m_signal_gate; type_signal_mute_user_cs m_signal_mute_user_cs; type_signal_user_volume_changed m_signal_user_volume_changed; }; -- cgit v1.2.3 From cf53831b2a368c5413f10565e2ead88d8db8d1b4 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 18 Oct 2022 18:34:02 -0400 Subject: decay capture meter faster --- src/audio/manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 7416ff7..60969cc 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -229,7 +229,7 @@ void AudioManager::UpdateCaptureVolume(const int16_t *pcm, ma_uint32 frames) { } bool AudioManager::DecayVolumeMeters() { - m_capture_peak_meter -= 300; + m_capture_peak_meter -= 600; if (m_capture_peak_meter < 0) m_capture_peak_meter = 0; std::lock_guard _(m_vol_mtx); -- cgit v1.2.3 From e888306272304224ac8edd37b3b2bd24ad8c0765 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 21 Oct 2022 01:23:37 -0400 Subject: add gain slider (how 2 loudmic? 🤓) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/abaddon.cpp | 4 ++++ src/audio/manager.cpp | 16 ++++++++++++++-- src/audio/manager.hpp | 2 ++ src/windows/voicewindow.cpp | 13 +++++++++++++ src/windows/voicewindow.hpp | 4 ++++ 5 files changed, 37 insertions(+), 2 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 45fca80..664c0bd 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -434,6 +434,10 @@ void Abaddon::OnVoiceConnected() { m_audio->SetCaptureGate(gate); }); + wnd->signal_gain().connect([this](double gain) { + m_audio->SetCaptureGain(gain); + }); + wnd->signal_mute_user_cs().connect([this](Snowflake id, bool is_mute) { if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { m_audio->SetMuteSSRC(*ssrc, is_mute); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 60969cc..0461ee8 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -180,6 +180,10 @@ void AudioManager::SetCaptureGate(double gate) { m_capture_gate = gate * 0.01; } +void AudioManager::SetCaptureGain(double gain) { + m_capture_gain = gain; +} + void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { std::lock_guard _(m_mutex); if (mute) { @@ -199,11 +203,19 @@ void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; - UpdateCaptureVolume(pcm, frames); + const double gain = m_capture_gain; + // i have a suspicion i can cast the const away... but i wont + 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); + } + + UpdateCaptureVolume(new_pcm.data(), frames); if (m_capture_peak_meter / 32768.0 < m_capture_gate) return; - int payload_len = opus_encode(m_encoder, pcm, 480, static_cast(m_opus_buffer), 1275); + int payload_len = opus_encode(m_encoder, new_pcm.data(), 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { printf("encoding error: %d\n", payload_len); } else { diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 67241fb..13dbfa4 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -31,6 +31,7 @@ public: void SetPlayback(bool playback); void SetCaptureGate(double gate); + void SetCaptureGain(double gain); void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); @@ -74,6 +75,7 @@ private: std::atomic m_should_playback = true; std::atomic m_capture_gate = 0.0; + std::atomic m_capture_gain = 1.0; std::unordered_set m_muted_ssrcs; std::unordered_map m_volume_ssrc; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 80a388e..9f53638 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -113,12 +113,21 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_capture_volume.SetTick(val / 100.0); }); + m_capture_gain.set_range(0.0, 200.0); + m_capture_gain.set_value_pos(Gtk::POS_LEFT); + m_capture_gain.set_value(100.0); + m_capture_gain.signal_value_changed().connect([this]() { + const double val = m_capture_gain.get_value(); + m_signal_gain.emit(val / 100.0); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); m_main.add(m_controls); m_main.add(m_capture_volume); m_main.add(m_capture_gate); + m_main.add(m_capture_gain); m_main.add(m_scroll); add(m_main); show_all_children(); @@ -193,6 +202,10 @@ VoiceWindow::type_signal_gate VoiceWindow::signal_gate() { return m_signal_gate; } +VoiceWindow::type_signal_gate VoiceWindow::signal_gain() { + return m_signal_gain; +} + VoiceWindow::type_signal_mute_user_cs VoiceWindow::signal_mute_user_cs() { return m_signal_mute_user_cs; } diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index 86cedb8..8eb02f3 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -43,6 +43,7 @@ private: VolumeMeter m_capture_volume; Gtk::Scale m_capture_gate; + Gtk::Scale m_capture_gain; Snowflake m_channel_id; @@ -52,12 +53,14 @@ public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; using type_signal_gate = sigc::signal; + using type_signal_gain = sigc::signal; using type_signal_mute_user_cs = sigc::signal; using type_signal_user_volume_changed = sigc::signal; type_signal_mute signal_mute(); type_signal_deafen signal_deafen(); type_signal_gate signal_gate(); + type_signal_gain signal_gain(); type_signal_mute_user_cs signal_mute_user_cs(); type_signal_user_volume_changed signal_user_volume_changed(); @@ -65,6 +68,7 @@ private: type_signal_mute m_signal_mute; type_signal_deafen m_signal_deafen; type_signal_gate m_signal_gate; + type_signal_gain m_signal_gain; type_signal_mute_user_cs m_signal_mute_user_cs; type_signal_user_volume_changed m_signal_user_volume_changed; }; -- cgit v1.2.3 From cb690b6defde4851889d04a68efa4f06d7e38847 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:10:50 -0400 Subject: only enable microphone when in a voice channel --- src/abaddon.cpp | 3 +++ src/audio/manager.cpp | 19 ++++++++++++------- src/audio/manager.hpp | 3 +++ 3 files changed, 18 insertions(+), 7 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 692526c..b39b854 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -427,6 +427,8 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { + m_audio->StartCaptureDevice(); + auto *wnd = new VoiceWindow(m_discord.GetVoiceChannelID()); m_voice_window = wnd; @@ -473,6 +475,7 @@ void Abaddon::OnVoiceConnected() { } void Abaddon::OnVoiceDisconnected() { + m_audio->StopCaptureDevice(); m_audio->RemoveAllSSRCs(); if (m_voice_window != nullptr) { m_voice_window->close(); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 0461ee8..21c522e 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -98,13 +98,6 @@ AudioManager::AudioManager() { return; } - if (ma_device_start(&m_capture_device) != MA_SUCCESS) { - puts("failed to start capture"); - ma_device_uninit(&m_capture_device); - m_ok = false; - return; - } - char device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; ma_device_get_name(&m_capture_device, ma_device_type_capture, device_name, sizeof(device_name), nullptr); printf("using %s for capture\n", device_name); @@ -168,6 +161,18 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { } } +void AudioManager::StartCaptureDevice() { + if (ma_device_start(&m_capture_device) != MA_SUCCESS) { + puts("failed to start capture"); + } +} + +void AudioManager::StopCaptureDevice() { + if (ma_device_stop(&m_capture_device) != MA_SUCCESS) { + puts("failed to stop capture"); + } +} + void AudioManager::SetCapture(bool capture) { m_should_capture = capture; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 13dbfa4..7d8f479 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -27,6 +27,9 @@ public: void SetOpusBuffer(uint8_t *ptr); void FeedMeOpus(uint32_t ssrc, const std::vector &data); + void StartCaptureDevice(); + void StopCaptureDevice(); + void SetCapture(bool capture); void SetPlayback(bool playback); -- cgit v1.2.3 From f8f9a907c931623b8ef43d7af45b10c49d41afaa Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 5 Nov 2022 02:32:43 -0400 Subject: add basic combobox to choose output device, start using spdlog --- CMakeLists.txt | 3 ++ src/abaddon.cpp | 8 +++++ src/audio/devices.cpp | 33 +++++++++++++++++ src/audio/devices.hpp | 26 ++++++++++++++ src/audio/manager.cpp | 87 ++++++++++++++++++++++++++++++++++++++++----- src/audio/manager.hpp | 13 +++++++ src/discord/voiceclient.cpp | 22 ++++++------ src/windows/voicewindow.cpp | 12 +++++++ src/windows/voicewindow.hpp | 3 ++ 9 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 src/audio/devices.cpp create mode 100644 src/audio/devices.hpp (limited to 'src/audio/manager.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ff2a2b..d4c499e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,9 @@ if (Fontconfig_FOUND) target_link_libraries(abaddon Fontconfig::Fontconfig) endif () +find_package(spdlog REQUIRED) +target_link_libraries(abaddon spdlog::spdlog) + target_link_libraries(abaddon ${SQLite3_LIBRARIES}) target_link_libraries(abaddon ${GTKMM_LIBRARIES}) target_link_libraries(abaddon ${CURL_LIBRARIES}) diff --git a/src/abaddon.cpp b/src/abaddon.cpp index b39b854..a0e73f2 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include #include "platform.hpp" @@ -1098,6 +1101,11 @@ int main(int argc, char **argv) { if (buf[0] != '1') SetEnvironmentVariableA("GTK_CSD", "0"); #endif + + spdlog::cfg::load_env_levels(); + auto log_audio = spdlog::stdout_color_mt("audio"); + auto log_voice = spdlog::stdout_color_mt("voice"); + Gtk::Main::init_gtkmm_internals(); // why??? return Abaddon::Get().StartGTK(); } diff --git a/src/audio/devices.cpp b/src/audio/devices.cpp new file mode 100644 index 0000000..f315a17 --- /dev/null +++ b/src/audio/devices.cpp @@ -0,0 +1,33 @@ +#include "devices.hpp" + +AudioDevices::AudioDevices() + : m_playback(Gtk::ListStore::create(m_playback_columns)) { +} + +Glib::RefPtr AudioDevices::GetPlaybackDeviceModel() { + return m_playback; +} + +void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count) { + m_playback->clear(); + + for (ma_uint32 i = 0; i < playback_count; i++) { + auto &d = pPlayback[i]; + auto row = *m_playback->append(); + row[m_playback_columns.Name] = d.name; + row[m_playback_columns.DeviceID] = d.id; + } +} + +std::optional AudioDevices::GetDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) { + if (iter) { + return static_cast((*iter)[m_playback_columns.DeviceID]); + } else { + return std::nullopt; + } +} + +AudioDevices::PlaybackColumns::PlaybackColumns() { + add(Name); + add(DeviceID); +} diff --git a/src/audio/devices.hpp b/src/audio/devices.hpp new file mode 100644 index 0000000..0a88873 --- /dev/null +++ b/src/audio/devices.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +class AudioDevices { +public: + AudioDevices(); + + Glib::RefPtr GetPlaybackDeviceModel(); + + void SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count); + std::optional GetDeviceIDFromModel(const Gtk::TreeModel::iterator &iter); + +private: + class PlaybackColumns : public Gtk::TreeModel::ColumnRecord { + public: + PlaybackColumns(); + + Gtk::TreeModelColumn Name; + Gtk::TreeModelColumn DeviceID; + }; + PlaybackColumns m_playback_columns; + Glib::RefPtr m_playback; +}; diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 21c522e..c965658 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -8,6 +8,7 @@ #include "manager.hpp" #include #include +#include #define MINIAUDIO_IMPLEMENTATION #include #include @@ -58,12 +59,20 @@ AudioManager::AudioManager() { int err; m_encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_VOIP, &err); if (err != OPUS_OK) { - printf("failed to initialize opus encoder: %d\n", err); + spdlog::get("audio")->error("failed to initialize opus encoder: {}", err); m_ok = false; return; } opus_encoder_ctl(m_encoder, OPUS_SET_BITRATE(64000)); + if (ma_context_init(nullptr, 0, nullptr, &m_context) != MA_SUCCESS) { + spdlog::get("audio")->error("failed to initialize context"); + m_ok = false; + return; + } + + Enumerate(); + m_device_config = ma_device_config_init(ma_device_type_playback); m_device_config.playback.format = ma_format_f32; m_device_config.playback.channels = 2; @@ -72,13 +81,13 @@ AudioManager::AudioManager() { m_device_config.pUserData = this; if (ma_device_init(nullptr, &m_device_config, &m_device) != MA_SUCCESS) { - puts("open playback fail"); + spdlog::get("audio")->error("failed to initialize playback device"); m_ok = false; return; } if (ma_device_start(&m_device) != MA_SUCCESS) { - puts("failed to start playback"); + spdlog::get("audio")->error("failed to start playback"); ma_device_uninit(&m_device); m_ok = false; return; @@ -93,14 +102,14 @@ AudioManager::AudioManager() { m_capture_config.pUserData = this; if (ma_device_init(nullptr, &m_capture_config, &m_capture_device) != MA_SUCCESS) { - puts("open capture fail"); + spdlog::get("audio")->error("failed to initialize capture device"); m_ok = false; return; } char device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; ma_device_get_name(&m_capture_device, ma_device_type_capture, device_name, sizeof(device_name), nullptr); - printf("using %s for capture\n", device_name); + spdlog::get("audio")->info("using {} as capture device", device_name); Glib::signal_timeout().connect(sigc::mem_fun(*this, &AudioManager::DecayVolumeMeters), 40); } @@ -108,6 +117,7 @@ AudioManager::AudioManager() { AudioManager::~AudioManager() { ma_device_uninit(&m_device); ma_device_uninit(&m_capture_device); + ma_context_uninit(&m_context); RemoveAllSSRCs(); } @@ -129,7 +139,7 @@ void AudioManager::RemoveSSRC(uint32_t ssrc) { } void AudioManager::RemoveAllSSRCs() { - puts("remove all ssrc"); + spdlog::get("audio")->info("removing all ssrc"); std::lock_guard _(m_mutex); for (auto &[ssrc, pair] : m_sources) { opus_decoder_destroy(pair.second); @@ -163,13 +173,45 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector &data) { void AudioManager::StartCaptureDevice() { if (ma_device_start(&m_capture_device) != MA_SUCCESS) { - puts("failed to start capture"); + spdlog::get("audio")->error("Failed to start capture device"); } } void AudioManager::StopCaptureDevice() { if (ma_device_stop(&m_capture_device) != MA_SUCCESS) { - puts("failed to stop capture"); + spdlog::get("audio")->error("Failed to stop capture device"); + } +} + +void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { + spdlog::get("audio")->debug("Setting new playback device"); + + const auto device_id = m_devices.GetDeviceIDFromModel(iter); + if (!device_id) { + spdlog::get("audio")->error("Requested ID from iterator is invalid"); + return; + } + + m_playback_id = *device_id; + + ma_device_uninit(&m_device); + + m_device_config = ma_device_config_init(ma_device_type_playback); + m_device_config.playback.format = ma_format_f32; + m_device_config.playback.channels = 2; + m_device_config.playback.pDeviceID = &m_playback_id; + m_device_config.sampleRate = 48000; + m_device_config.dataCallback = data_callback; + m_device_config.pUserData = this; + + if (ma_device_init(&m_context, &m_device_config, &m_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to initialize new device"); + return; + } + + if (ma_device_start(&m_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to start new device"); + return; } } @@ -205,6 +247,29 @@ void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); } +void AudioManager::Enumerate() { + ma_device_info *pPlaybackDeviceInfo; + ma_uint32 playbackDeviceCount; + ma_device_info *pCaptureDeviceInfo; + ma_uint32 captureDeviceCount; + + spdlog::get("audio")->debug("Enumerating devices"); + + if (ma_context_get_devices( + &m_context, + &pPlaybackDeviceInfo, + &playbackDeviceCount, + &pCaptureDeviceInfo, + &captureDeviceCount) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to enumerate devices"); + return; + } + + spdlog::get("audio")->debug("Found {} playback devices and {} capture devices", playbackDeviceCount, captureDeviceCount); + + m_devices.SetDevices(pPlaybackDeviceInfo, playbackDeviceCount, pCaptureDeviceInfo, captureDeviceCount); +} + void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_opus_buffer == nullptr || !m_should_capture) return; @@ -222,7 +287,7 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { int payload_len = opus_encode(m_encoder, new_pcm.data(), 480, static_cast(m_opus_buffer), 1275); if (payload_len < 0) { - printf("encoding error: %d\n", payload_len); + spdlog::get("audio")->error("encoding error: {}", payload_len); } else { m_signal_opus_packet.emit(payload_len); } @@ -275,6 +340,10 @@ double AudioManager::GetSSRCVolumeLevel(uint32_t ssrc) const noexcept { return 0.0; } +AudioDevices &AudioManager::GetDevices() { + return m_devices; +} + AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { return m_signal_opus_packet; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 7d8f479..ff50390 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include "devices.hpp" // clang-format on class AudioManager { @@ -30,6 +32,8 @@ public: void StartCaptureDevice(); void StopCaptureDevice(); + void SetPlaybackDevice(const Gtk::TreeModel::iterator &iter); + void SetCapture(bool capture); void SetPlayback(bool playback); @@ -39,11 +43,15 @@ public: void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); + void Enumerate(); + [[nodiscard]] bool OK() const; [[nodiscard]] double GetCaptureVolumeLevel() const noexcept; [[nodiscard]] double GetSSRCVolumeLevel(uint32_t ssrc) const noexcept; + [[nodiscard]] AudioDevices &GetDevices(); + private: void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames); @@ -63,10 +71,13 @@ private: // playback ma_device m_device; ma_device_config m_device_config; + ma_device_id m_playback_id; // capture ma_device m_capture_device; ma_device_config m_capture_config; + ma_context m_context; + mutable std::mutex m_mutex; std::unordered_map, OpusDecoder *>> m_sources; @@ -86,6 +97,8 @@ private: mutable std::mutex m_vol_mtx; std::unordered_map m_volumes; + AudioDevices m_devices; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index c0679ce..291b975 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -4,6 +4,8 @@ #include "voiceclient.hpp" #include "json.hpp" #include +#include +#include #include "abaddon.hpp" #include "audio/manager.hpp" @@ -127,11 +129,11 @@ DiscordVoiceClient::DiscordVoiceClient() { sodium_init(); m_ws.signal_open().connect([this]() { - puts("vws open"); + spdlog::get("voice")->info("Websocket open"); }); m_ws.signal_close().connect([this](uint16_t code) { - printf("vws close %u\n", code); + spdlog::get("voice")->info("Websocket closed with code {}", code); Stop(); }); @@ -258,9 +260,9 @@ void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { m_port = d.Port; m_ssrc = d.SSRC; if (std::find(d.Modes.begin(), d.Modes.end(), "xsalsa20_poly1305") == d.Modes.end()) { - puts("xsalsa20_poly1305 not in encryption modes"); + spdlog::get("voice")->error("xsalsa20_poly1305 not in encryption modes"); } - printf("connect to %s:%u ssrc %u\n", m_ip.c_str(), m_port, m_ssrc); + spdlog::get("voice")->info("connect to {}:{} ssrc {}", m_ip, m_port, m_ssrc); m_udp.Connect(m_ip, m_port); m_keepalive_thread = std::thread(&DiscordVoiceClient::KeepaliveThread, this); @@ -270,11 +272,7 @@ void DiscordVoiceClient::HandleGatewayReady(const VoiceGatewayMessage &m) { void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessage &m) { VoiceSessionDescriptionData d = m.Data; - printf("receiving with %s secret key: ", d.Mode.c_str()); - for (auto b : d.SecretKey) { - printf("%02X", b); - } - printf("\n"); + spdlog::get("voice")->debug("receiving with {}, secret key: {:ns}", d.Mode, spdlog::to_hex(std::begin(d.SecretKey), std::end(d.SecretKey))); VoiceSpeakingMessage msg; msg.Delay = 0; @@ -330,10 +328,10 @@ void DiscordVoiceClient::Discovery() { if (response.size() >= 74 && response[0] == 0x00 && response[1] == 0x02) { const char *our_ip = reinterpret_cast(&response[8]); uint16_t our_port = (response[73] << 8) | response[74]; - printf("we are %s:%u\n", our_ip, our_port); + spdlog::get("voice")->debug("IP address discovered: {}:{}\n", our_ip, our_port); SelectProtocol(our_ip, our_port); } else { - puts("received non-discovery packet after discovery"); + spdlog::get("voice")->error("Received non-discovery packet after discovery"); } } @@ -355,7 +353,7 @@ void DiscordVoiceClient::OnUDPData(std::vector data) { static std::array nonce = {}; std::memcpy(nonce.data(), data.data(), 12); if (crypto_secretbox_open_easy(payload, payload, data.size() - 12, nonce.data(), m_secret_key.data())) { - puts("decrypt fail"); + // spdlog::get("voice")->trace("UDP payload decryption failure"); } else { Abaddon::Get().GetAudio().FeedMeOpus(ssrc, { payload, payload + data.size() - 12 - crypto_box_MACBYTES }); } diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 9f53638..95d1036 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -121,6 +121,17 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_signal_gain.emit(val / 100.0); }); + auto *renderer = Gtk::make_managed(); + m_playback_combo.set_valign(Gtk::ALIGN_END); + m_playback_combo.set_hexpand(true); + m_playback_combo.set_halign(Gtk::ALIGN_FILL); + m_playback_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetPlaybackDeviceModel()); + m_playback_combo.pack_start(*renderer); + m_playback_combo.add_attribute(*renderer, "text", 0); + m_playback_combo.signal_changed().connect([this]() { + Abaddon::Get().GetAudio().SetPlaybackDevice(m_playback_combo.get_active()); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); @@ -129,6 +140,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_main.add(m_capture_gate); m_main.add(m_capture_gain); m_main.add(m_scroll); + m_main.add(m_playback_combo); add(m_main); show_all_children(); diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index 8eb02f3..e36846c 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -6,6 +6,7 @@ #include "discord/snowflake.hpp" #include #include +#include #include #include #include @@ -45,6 +46,8 @@ private: Gtk::Scale m_capture_gate; Gtk::Scale m_capture_gain; + Gtk::ComboBox m_playback_combo; + Snowflake m_channel_id; std::unordered_map m_rows; -- cgit v1.2.3 From 047168873235c3d835aaf9836f549de08bd96a47 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Tue, 8 Nov 2022 04:01:54 -0500 Subject: add ability to set capture device --- src/abaddon.cpp | 1 + src/audio/devices.cpp | 31 +++++++++++++++++++++++++++++-- src/audio/devices.hpp | 14 +++++++++++++- src/audio/manager.cpp | 36 +++++++++++++++++++++++++++++++++++- src/audio/manager.hpp | 2 ++ src/discord/discord.cpp | 9 +++++---- src/windows/voicewindow.cpp | 18 +++++++++++++++--- src/windows/voicewindow.hpp | 1 + 8 files changed, 101 insertions(+), 11 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index a0e73f2..67fd2bd 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -1105,6 +1105,7 @@ int main(int argc, char **argv) { spdlog::cfg::load_env_levels(); auto log_audio = spdlog::stdout_color_mt("audio"); auto log_voice = spdlog::stdout_color_mt("voice"); + auto log_discord = spdlog::stdout_color_mt("discord"); Gtk::Main::init_gtkmm_internals(); // why??? return Abaddon::Get().StartGTK(); diff --git a/src/audio/devices.cpp b/src/audio/devices.cpp index c9636b7..5e7720b 100644 --- a/src/audio/devices.cpp +++ b/src/audio/devices.cpp @@ -7,13 +7,18 @@ // clang-format on AudioDevices::AudioDevices() - : m_playback(Gtk::ListStore::create(m_playback_columns)) { + : m_playback(Gtk::ListStore::create(m_playback_columns)) + , m_capture(Gtk::ListStore::create(m_capture_columns)) { } Glib::RefPtr AudioDevices::GetPlaybackDeviceModel() { return m_playback; } +Glib::RefPtr AudioDevices::GetCaptureDeviceModel() { + return m_capture; +} + void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count) { m_playback->clear(); @@ -23,9 +28,18 @@ void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_coun row[m_playback_columns.Name] = d.name; row[m_playback_columns.DeviceID] = d.id; } + + m_capture->clear(); + + for (ma_uint32 i = 0; i < capture_count; i++) { + auto &d = pCapture[i]; + auto row = *m_capture->append(); + row[m_capture_columns.Name] = d.name; + row[m_capture_columns.DeviceID] = d.id; + } } -std::optional AudioDevices::GetDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) { +std::optional AudioDevices::GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { if (iter) { return static_cast((*iter)[m_playback_columns.DeviceID]); } else { @@ -33,8 +47,21 @@ std::optional AudioDevices::GetDeviceIDFromModel(const Gtk::TreeMo } } +std::optional AudioDevices::GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { + if (iter) { + return static_cast((*iter)[m_capture_columns.DeviceID]); + } else { + return std::nullopt; + } +} + AudioDevices::PlaybackColumns::PlaybackColumns() { add(Name); add(DeviceID); } + +AudioDevices::CaptureColumns::CaptureColumns() { + add(Name); + add(DeviceID); +} #endif diff --git a/src/audio/devices.hpp b/src/audio/devices.hpp index c11744b..9d3de7d 100644 --- a/src/audio/devices.hpp +++ b/src/audio/devices.hpp @@ -14,9 +14,11 @@ public: AudioDevices(); Glib::RefPtr GetPlaybackDeviceModel(); + Glib::RefPtr GetCaptureDeviceModel(); void SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count); - std::optional GetDeviceIDFromModel(const Gtk::TreeModel::iterator &iter); + std::optional GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; + std::optional GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; private: class PlaybackColumns : public Gtk::TreeModel::ColumnRecord { @@ -28,5 +30,15 @@ private: }; PlaybackColumns m_playback_columns; Glib::RefPtr m_playback; + + class CaptureColumns : public Gtk::TreeModel::ColumnRecord { + public: + CaptureColumns(); + + Gtk::TreeModelColumn Name; + Gtk::TreeModelColumn DeviceID; + }; + CaptureColumns m_capture_columns; + Glib::RefPtr m_capture; }; #endif diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index c965658..839bfc8 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -186,7 +186,7 @@ void AudioManager::StopCaptureDevice() { void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { spdlog::get("audio")->debug("Setting new playback device"); - const auto device_id = m_devices.GetDeviceIDFromModel(iter); + const auto device_id = m_devices.GetPlaybackDeviceIDFromModel(iter); if (!device_id) { spdlog::get("audio")->error("Requested ID from iterator is invalid"); return; @@ -215,6 +215,40 @@ void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { } } +void AudioManager::SetCaptureDevice(const Gtk::TreeModel::iterator &iter) { + 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_capture_id = *device_id; + + ma_device_uninit(&m_capture_device); + + m_capture_config = ma_device_config_init(ma_device_type_capture); + m_capture_config.capture.format = ma_format_s16; + m_capture_config.capture.channels = 2; + m_capture_config.capture.pDeviceID = &m_capture_id; + m_capture_config.sampleRate = 48000; + m_capture_config.periodSizeInFrames = 480; + m_capture_config.dataCallback = capture_data_callback; + m_capture_config.pUserData = this; + + if (ma_device_init(&m_context, &m_capture_config, &m_capture_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to initialize new device"); + return; + } + + // technically this should probably try and check old state but if you are in the window to change it then you are connected + if (ma_device_start(&m_capture_device) != MA_SUCCESS) { + spdlog::get("audio")->error("Failed to start new device"); + return; + } +} + void AudioManager::SetCapture(bool capture) { m_should_capture = capture; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index ff50390..7805b30 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -33,6 +33,7 @@ public: void StopCaptureDevice(); void SetPlaybackDevice(const Gtk::TreeModel::iterator &iter); + void SetCaptureDevice(const Gtk::TreeModel::iterator &iter); void SetCapture(bool capture); void SetPlayback(bool playback); @@ -75,6 +76,7 @@ private: // capture ma_device m_capture_device; ma_device_config m_capture_config; + ma_device_id m_capture_id; ma_context m_context; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index cc066cc..2a08574 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1,6 +1,7 @@ #include "abaddon.hpp" #include "discord.hpp" #include "util.hpp" +#include #include #include @@ -2168,7 +2169,7 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { VoiceState data = msg.Data; if (data.UserID == m_user_data.ID) { - printf("voice session id: %s\n", data.SessionID.c_str()); + spdlog::get("discord")->debug("Voice session ID: {}", data.SessionID); m_voice.SetSessionID(data.SessionID); } else { if (data.GuildID.has_value() && data.Member.has_value()) { @@ -2197,8 +2198,8 @@ void DiscordClient::HandleGatewayVoiceStateUpdate(const GatewayMessage &msg) { void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { VoiceServerUpdateData data = msg.Data; - printf("endpoint: %s\n", data.Endpoint.c_str()); - printf("token: %s\n", data.Token.c_str()); + spdlog::get("discord")->debug("Voice server endpoint: {}", data.Endpoint); + spdlog::get("discord")->debug("Voice token: {}", data.Token); m_voice.SetEndpoint(data.Endpoint); m_voice.SetToken(data.Token); if (data.GuildID.has_value()) { @@ -2206,7 +2207,7 @@ void DiscordClient::HandleGatewayVoiceServerUpdate(const GatewayMessage &msg) { } else if (data.ChannelID.has_value()) { m_voice.SetServerID(*data.ChannelID); } else { - puts("no guild or channel id in voice server?"); + spdlog::get("discord")->error("No guild or channel ID in voice server?"); } m_voice.SetUserID(m_user_data.ID); m_voice.Start(); diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 95d1036..19d9863 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -121,17 +121,28 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_signal_gain.emit(val / 100.0); }); - auto *renderer = Gtk::make_managed(); + auto *playback_renderer = Gtk::make_managed(); m_playback_combo.set_valign(Gtk::ALIGN_END); m_playback_combo.set_hexpand(true); m_playback_combo.set_halign(Gtk::ALIGN_FILL); m_playback_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetPlaybackDeviceModel()); - m_playback_combo.pack_start(*renderer); - m_playback_combo.add_attribute(*renderer, "text", 0); + m_playback_combo.pack_start(*playback_renderer); + m_playback_combo.add_attribute(*playback_renderer, "text", 0); m_playback_combo.signal_changed().connect([this]() { Abaddon::Get().GetAudio().SetPlaybackDevice(m_playback_combo.get_active()); }); + auto *capture_renderer = Gtk::make_managed(); + m_capture_combo.set_valign(Gtk::ALIGN_END); + m_capture_combo.set_hexpand(true); + m_capture_combo.set_halign(Gtk::ALIGN_FILL); + m_capture_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetCaptureDeviceModel()); + m_capture_combo.pack_start(*capture_renderer); + m_capture_combo.add_attribute(*capture_renderer, "text", 0); + m_capture_combo.signal_changed().connect([this]() { + Abaddon::Get().GetAudio().SetCaptureDevice(m_capture_combo.get_active()); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); @@ -141,6 +152,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_main.add(m_capture_gain); m_main.add(m_scroll); m_main.add(m_playback_combo); + m_main.add(m_capture_combo); add(m_main); show_all_children(); diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index e36846c..f9ea23f 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -47,6 +47,7 @@ private: Gtk::Scale m_capture_gain; Gtk::ComboBox m_playback_combo; + Gtk::ComboBox m_capture_combo; Snowflake m_channel_id; -- cgit v1.2.3 From e2784cd97bd9a4b5995c556d35bd4f08a2f4bad7 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 9 Nov 2022 19:03:53 -0500 Subject: model stuff to track active device also minor refactor --- src/audio/devices.cpp | 62 ++++++++++++++++++++++++++++++++++++++--- src/audio/devices.hpp | 18 ++++++++++-- src/audio/manager.cpp | 68 +++++++++++++++++++++++++++++---------------- src/audio/manager.hpp | 4 +-- src/windows/voicewindow.cpp | 2 ++ 5 files changed, 122 insertions(+), 32 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/devices.cpp b/src/audio/devices.cpp index 5e7720b..dfb7164 100644 --- a/src/audio/devices.cpp +++ b/src/audio/devices.cpp @@ -3,6 +3,8 @@ // clang-format off #include "devices.hpp" +#include +#include // clang-format on @@ -24,35 +26,87 @@ void AudioDevices::SetDevices(ma_device_info *pPlayback, ma_uint32 playback_coun for (ma_uint32 i = 0; i < playback_count; i++) { auto &d = pPlayback[i]; + auto row = *m_playback->append(); row[m_playback_columns.Name] = d.name; row[m_playback_columns.DeviceID] = d.id; + + if (d.isDefault) { + m_default_playback_iter = row; + SetActivePlaybackDevice(row); + } } m_capture->clear(); for (ma_uint32 i = 0; i < capture_count; i++) { auto &d = pCapture[i]; + auto row = *m_capture->append(); row[m_capture_columns.Name] = d.name; row[m_capture_columns.DeviceID] = d.id; + + if (d.isDefault) { + 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"); } } std::optional AudioDevices::GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { if (iter) { return static_cast((*iter)[m_playback_columns.DeviceID]); - } else { - return std::nullopt; } + + return std::nullopt; } std::optional AudioDevices::GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const { if (iter) { return static_cast((*iter)[m_capture_columns.DeviceID]); - } else { - return std::nullopt; } + + return std::nullopt; +} + +std::optional AudioDevices::GetDefaultPlayback() const { + if (m_default_playback_iter) { + return static_cast((*m_default_playback_iter)[m_playback_columns.DeviceID]); + } + + return std::nullopt; +} + +std::optional AudioDevices::GetDefaultCapture() const { + if (m_default_capture_iter) { + return static_cast((*m_default_capture_iter)[m_capture_columns.DeviceID]); + } + + return std::nullopt; +} + +void AudioDevices::SetActivePlaybackDevice(const Gtk::TreeModel::iterator &iter) { + m_active_playback_iter = iter; +} + +void AudioDevices::SetActiveCaptureDevice(const Gtk::TreeModel::iterator &iter) { + m_active_capture_iter = iter; +} + +Gtk::TreeModel::iterator AudioDevices::GetActivePlaybackDevice() { + return m_active_playback_iter; +} + +Gtk::TreeModel::iterator AudioDevices::GetActiveCaptureDevice() { + return m_active_capture_iter; } AudioDevices::PlaybackColumns::PlaybackColumns() { diff --git a/src/audio/devices.hpp b/src/audio/devices.hpp index 9d3de7d..c83bdb4 100644 --- a/src/audio/devices.hpp +++ b/src/audio/devices.hpp @@ -17,8 +17,18 @@ public: Glib::RefPtr GetCaptureDeviceModel(); void SetDevices(ma_device_info *pPlayback, ma_uint32 playback_count, ma_device_info *pCapture, ma_uint32 capture_count); - std::optional GetPlaybackDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; - std::optional GetCaptureDeviceIDFromModel(const Gtk::TreeModel::iterator &iter) const; + + [[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; + + void SetActivePlaybackDevice(const Gtk::TreeModel::iterator &iter); + void SetActiveCaptureDevice(const Gtk::TreeModel::iterator &iter); + + Gtk::TreeModel::iterator GetActivePlaybackDevice(); + Gtk::TreeModel::iterator GetActiveCaptureDevice(); private: class PlaybackColumns : public Gtk::TreeModel::ColumnRecord { @@ -30,6 +40,8 @@ private: }; PlaybackColumns m_playback_columns; Glib::RefPtr m_playback; + Gtk::TreeModel::iterator m_active_playback_iter; + Gtk::TreeModel::iterator m_default_playback_iter; class CaptureColumns : public Gtk::TreeModel::ColumnRecord { public: @@ -40,5 +52,7 @@ private: }; CaptureColumns m_capture_columns; Glib::RefPtr m_capture; + Gtk::TreeModel::iterator m_active_capture_iter; + Gtk::TreeModel::iterator m_default_capture_iter; }; #endif diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 839bfc8..fcfa9f2 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -71,24 +71,31 @@ AudioManager::AudioManager() { return; } + spdlog::get("audio")->info("Audio backend: {}", ma_get_backend_name(m_context.backend)); + Enumerate(); - m_device_config = ma_device_config_init(ma_device_type_playback); - m_device_config.playback.format = ma_format_f32; - m_device_config.playback.channels = 2; - m_device_config.sampleRate = 48000; - m_device_config.dataCallback = data_callback; - m_device_config.pUserData = this; + m_playback_config = ma_device_config_init(ma_device_type_playback); + m_playback_config.playback.format = ma_format_f32; + m_playback_config.playback.channels = 2; + m_playback_config.sampleRate = 48000; + m_playback_config.dataCallback = data_callback; + m_playback_config.pUserData = this; + + if (const auto playback_id = m_devices.GetDefaultPlayback(); playback_id.has_value()) { + m_playback_id = *playback_id; + m_playback_config.playback.pDeviceID = &m_playback_id; + } - if (ma_device_init(nullptr, &m_device_config, &m_device) != MA_SUCCESS) { + if (ma_device_init(&m_context, &m_playback_config, &m_playback_device) != MA_SUCCESS) { spdlog::get("audio")->error("failed to initialize playback device"); m_ok = false; return; } - if (ma_device_start(&m_device) != MA_SUCCESS) { + if (ma_device_start(&m_playback_device) != MA_SUCCESS) { spdlog::get("audio")->error("failed to start playback"); - ma_device_uninit(&m_device); + ma_device_uninit(&m_playback_device); m_ok = false; return; } @@ -101,21 +108,30 @@ AudioManager::AudioManager() { m_capture_config.dataCallback = capture_data_callback; m_capture_config.pUserData = this; - if (ma_device_init(nullptr, &m_capture_config, &m_capture_device) != MA_SUCCESS) { + 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; + } + + if (ma_device_init(&m_context, &m_capture_config, &m_capture_device) != MA_SUCCESS) { spdlog::get("audio")->error("failed to initialize capture device"); m_ok = false; return; } - char device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; - ma_device_get_name(&m_capture_device, ma_device_type_capture, device_name, sizeof(device_name), nullptr); - spdlog::get("audio")->info("using {} as capture device", device_name); + char playback_device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(&m_playback_device, ma_device_type_playback, playback_device_name, sizeof(playback_device_name), nullptr); + spdlog::get("audio")->info("using {} as playback device", playback_device_name); + + char capture_device_name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(&m_capture_device, ma_device_type_capture, capture_device_name, sizeof(capture_device_name), nullptr); + spdlog::get("audio")->info("using {} as capture device", capture_device_name); Glib::signal_timeout().connect(sigc::mem_fun(*this, &AudioManager::DecayVolumeMeters), 40); } AudioManager::~AudioManager() { - ma_device_uninit(&m_device); + ma_device_uninit(&m_playback_device); ma_device_uninit(&m_capture_device); ma_context_uninit(&m_context); RemoveAllSSRCs(); @@ -192,24 +208,26 @@ void AudioManager::SetPlaybackDevice(const Gtk::TreeModel::iterator &iter) { return; } + m_devices.SetActivePlaybackDevice(iter); + m_playback_id = *device_id; - ma_device_uninit(&m_device); + ma_device_uninit(&m_playback_device); - m_device_config = ma_device_config_init(ma_device_type_playback); - m_device_config.playback.format = ma_format_f32; - m_device_config.playback.channels = 2; - m_device_config.playback.pDeviceID = &m_playback_id; - m_device_config.sampleRate = 48000; - m_device_config.dataCallback = data_callback; - m_device_config.pUserData = this; + m_playback_config = ma_device_config_init(ma_device_type_playback); + m_playback_config.playback.format = ma_format_f32; + m_playback_config.playback.channels = 2; + m_playback_config.playback.pDeviceID = &m_playback_id; + m_playback_config.sampleRate = 48000; + m_playback_config.dataCallback = data_callback; + m_playback_config.pUserData = this; - if (ma_device_init(&m_context, &m_device_config, &m_device) != MA_SUCCESS) { + if (ma_device_init(&m_context, &m_playback_config, &m_playback_device) != MA_SUCCESS) { spdlog::get("audio")->error("Failed to initialize new device"); return; } - if (ma_device_start(&m_device) != MA_SUCCESS) { + if (ma_device_start(&m_playback_device) != MA_SUCCESS) { spdlog::get("audio")->error("Failed to start new device"); return; } @@ -224,6 +242,8 @@ void AudioManager::SetCaptureDevice(const Gtk::TreeModel::iterator &iter) { return; } + m_devices.SetActiveCaptureDevice(iter); + m_capture_id = *device_id; ma_device_uninit(&m_capture_device); diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 7805b30..7516918 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -70,8 +70,8 @@ private: bool m_ok; // playback - ma_device m_device; - ma_device_config m_device_config; + ma_device m_playback_device; + ma_device_config m_playback_config; ma_device_id m_playback_id; // capture ma_device m_capture_device; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 19d9863..7b574e4 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -126,6 +126,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_playback_combo.set_hexpand(true); m_playback_combo.set_halign(Gtk::ALIGN_FILL); m_playback_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetPlaybackDeviceModel()); + m_playback_combo.set_active(Abaddon::Get().GetAudio().GetDevices().GetActivePlaybackDevice()); m_playback_combo.pack_start(*playback_renderer); m_playback_combo.add_attribute(*playback_renderer, "text", 0); m_playback_combo.signal_changed().connect([this]() { @@ -137,6 +138,7 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_capture_combo.set_hexpand(true); m_capture_combo.set_halign(Gtk::ALIGN_FILL); m_capture_combo.set_model(Abaddon::Get().GetAudio().GetDevices().GetCaptureDeviceModel()); + m_capture_combo.set_active(Abaddon::Get().GetAudio().GetDevices().GetActiveCaptureDevice()); m_capture_combo.pack_start(*capture_renderer); m_capture_combo.add_attribute(*capture_renderer, "text", 0); m_capture_combo.signal_changed().connect([this]() { -- cgit v1.2.3 From 38c5230a1d194cb2fc4cf36fb6b417fdefb32788 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 14 Nov 2022 01:28:07 -0500 Subject: add window to change more stuff with opus --- src/audio/manager.cpp | 62 ++++++++++++++++++ src/audio/manager.hpp | 9 +++ src/windows/voicesettingswindow.cpp | 125 ++++++++++++++++++++++++++++++++++++ src/windows/voicesettingswindow.hpp | 25 ++++++++ src/windows/voicewindow.cpp | 14 +++- src/windows/voicewindow.hpp | 6 ++ 6 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/windows/voicesettingswindow.cpp create mode 100644 src/windows/voicesettingswindow.hpp (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index fcfa9f2..1a7bda5 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -301,6 +301,66 @@ void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); } +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() { ma_device_info *pPlaybackDeviceInfo; ma_uint32 playbackDeviceCount; @@ -339,7 +399,9 @@ void AudioManager::OnCapturedPCM(const int16_t *pcm, ma_uint32 frames) { if (m_capture_peak_meter / 32768.0 < m_capture_gate) return; + m_enc_mutex.lock(); int 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 { diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 7516918..dbb0b6e 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -44,6 +44,13 @@ public: void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); + void SetEncodingApplication(int application); + [[nodiscard]] int GetEncodingApplication(); + void SetSignalHint(int signal); + [[nodiscard]] int GetSignalHint(); + void SetBitrate(int bitrate); + [[nodiscard]] int GetBitrate(); + void Enumerate(); [[nodiscard]] bool OK() const; @@ -81,6 +88,8 @@ private: ma_context m_context; mutable std::mutex m_mutex; + mutable std::mutex m_enc_mutex; + std::unordered_map, OpusDecoder *>> m_sources; OpusEncoder *m_encoder; diff --git a/src/windows/voicesettingswindow.cpp b/src/windows/voicesettingswindow.cpp new file mode 100644 index 0000000..c009cbf --- /dev/null +++ b/src/windows/voicesettingswindow.cpp @@ -0,0 +1,125 @@ +#ifdef WITH_VOICE + +// clang-format off + +#include "voicesettingswindow.hpp" +#include "abaddon.hpp" +#include "audio/manager.hpp" +#include + +// clang-format on + +VoiceSettingsWindow::VoiceSettingsWindow() + : m_main(Gtk::ORIENTATION_VERTICAL) { + get_style_context()->add_class("app-window"); + set_default_size(300, 300); + + m_encoding_mode.append("Voice"); + m_encoding_mode.append("Music"); + m_encoding_mode.append("Restricted"); + m_encoding_mode.set_tooltip_text( + "Sets the coding mode for the Opus encoder\n" + "Voice - Optimize for voice quality\n" + "Music - Optimize for non-voice signals incl. music\n" + "Restricted - Optimize for non-voice, low latency. Not recommended"); + + const auto mode = Abaddon::Get().GetAudio().GetEncodingApplication(); + if (mode == OPUS_APPLICATION_VOIP) { + m_encoding_mode.set_active(0); + } else if (mode == OPUS_APPLICATION_AUDIO) { + m_encoding_mode.set_active(1); + } else if (mode == OPUS_APPLICATION_RESTRICTED_LOWDELAY) { + m_encoding_mode.set_active(2); + } + + m_encoding_mode.signal_changed().connect([this]() { + const auto mode = m_encoding_mode.get_active_text(); + auto &audio = Abaddon::Get().GetAudio(); + spdlog::get("audio")->debug("Chose encoding mode: {}", mode.c_str()); + if (mode == "Voice") { + audio.SetEncodingApplication(OPUS_APPLICATION_VOIP); + } else if (mode == "Music") { + spdlog::get("audio")->debug("music/audio"); + audio.SetEncodingApplication(OPUS_APPLICATION_AUDIO); + } else if (mode == "Restricted") { + audio.SetEncodingApplication(OPUS_APPLICATION_RESTRICTED_LOWDELAY); + } + }); + + m_signal.append("Auto"); + m_signal.append("Voice"); + m_signal.append("Music"); + m_signal.set_tooltip_text( + "Signal hint. Tells Opus what the current signal is\n" + "Auto - Let Opus figure it out\n" + "Voice - Tell Opus it's a voice signal\n" + "Music - Tell Opus it's a music signal"); + + const auto signal = Abaddon::Get().GetAudio().GetSignalHint(); + if (signal == OPUS_AUTO) { + m_signal.set_active(0); + } else if (signal == OPUS_SIGNAL_VOICE) { + m_signal.set_active(1); + } else if (signal == OPUS_SIGNAL_MUSIC) { + m_signal.set_active(2); + } + + m_signal.signal_changed().connect([this]() { + const auto signal = m_signal.get_active_text(); + auto &audio = Abaddon::Get().GetAudio(); + spdlog::get("audio")->debug("Chose signal hint: {}", signal.c_str()); + if (signal == "Auto") { + audio.SetSignalHint(OPUS_AUTO); + } else if (signal == "Voice") { + audio.SetSignalHint(OPUS_SIGNAL_VOICE); + } else if (signal == "Music") { + audio.SetSignalHint(OPUS_SIGNAL_MUSIC); + } + }); + + // exponential scale for bitrate because higher bitrates dont sound much different + constexpr static auto MAX_BITRATE = 128000.0; + constexpr static auto MIN_BITRATE = 2400.0; + const auto bitrate_scale = [this](double value) -> double { + value /= 100.0; + return (MAX_BITRATE - MIN_BITRATE) * value * value * value + MIN_BITRATE; + }; + + const auto bitrate_scale_r = [this](double value) -> double { + return 100.0 * std::cbrt((value - MIN_BITRATE) / (MAX_BITRATE - MIN_BITRATE)); + }; + + m_bitrate.set_range(0.0, 100.0); + m_bitrate.set_value_pos(Gtk::POS_TOP); + m_bitrate.set_value(bitrate_scale_r(Abaddon::Get().GetAudio().GetBitrate())); + m_bitrate.signal_format_value().connect([this, bitrate_scale](double value) { + const auto scaled = bitrate_scale(value); + if (value <= 99.9) { + return Glib::ustring(std::to_string(static_cast(scaled))); + } else { + return Glib::ustring("MAX"); + } + }); + m_bitrate.signal_value_changed().connect([this, bitrate_scale]() { + const auto value = m_bitrate.get_value(); + const auto scaled = bitrate_scale(value); + if (value <= 99.9) { + Abaddon::Get().GetAudio().SetBitrate(static_cast(scaled)); + } else { + Abaddon::Get().GetAudio().SetBitrate(OPUS_BITRATE_MAX); + } + }); + + m_main.add(m_encoding_mode); + m_main.add(m_signal); + m_main.add(m_bitrate); + add(m_main); + show_all_children(); + + // no need to bring in ManageHeapWindow, no user menu + signal_hide().connect([this]() { + delete this; + }); +} + +#endif diff --git a/src/windows/voicesettingswindow.hpp b/src/windows/voicesettingswindow.hpp new file mode 100644 index 0000000..cf6b477 --- /dev/null +++ b/src/windows/voicesettingswindow.hpp @@ -0,0 +1,25 @@ +#pragma once +#ifdef WITH_VOICE + +// clang-format off + +#include +#include +#include +#include + +// clang-format on + +class VoiceSettingsWindow : public Gtk::Window { +public: + VoiceSettingsWindow(); + + Gtk::Box m_main; + Gtk::ComboBoxText m_encoding_mode; + Gtk::ComboBoxText m_signal; + Gtk::Scale m_bitrate; + +private: +}; + +#endif diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 7b574e4..7c90e96 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -6,6 +6,7 @@ #include "components/lazyimage.hpp" #include "abaddon.hpp" #include "audio/manager.hpp" +#include "voicesettingswindow.hpp" // clang-format on class VoiceWindowUserListEntry : public Gtk::ListBoxRow { @@ -83,7 +84,9 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) , m_controls(Gtk::ORIENTATION_HORIZONTAL) , m_mute("Mute") , m_deafen("Deafen") - , m_channel_id(channel_id) { + , m_channel_id(channel_id) + , m_menu_view("View") + , m_menu_view_settings("More _Settings", true) { get_style_context()->add_class("app-window"); set_default_size(300, 300); @@ -145,9 +148,18 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) Abaddon::Get().GetAudio().SetCaptureDevice(m_capture_combo.get_active()); }); + m_menu_bar.append(m_menu_view); + m_menu_view.set_submenu(m_menu_view_sub); + m_menu_view_sub.append(m_menu_view_settings); + m_menu_view_settings.signal_activate().connect([this]() { + auto *window = new VoiceSettingsWindow; + window->show(); + }); + m_scroll.add(m_user_list); m_controls.add(m_mute); m_controls.add(m_deafen); + m_main.add(m_menu_bar); m_main.add(m_controls); m_main.add(m_capture_volume); m_main.add(m_capture_gate); diff --git a/src/windows/voicewindow.hpp b/src/windows/voicewindow.hpp index f9ea23f..805d94a 100644 --- a/src/windows/voicewindow.hpp +++ b/src/windows/voicewindow.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,11 @@ private: std::unordered_map m_rows; + Gtk::MenuBar m_menu_bar; + Gtk::MenuItem m_menu_view; + Gtk::Menu m_menu_view_sub; + Gtk::MenuItem m_menu_view_settings; + public: using type_signal_mute = sigc::signal; using type_signal_deafen = sigc::signal; -- cgit v1.2.3 From ea04035f0db8fa990dd7ca8dd1a64f56bceb82e2 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 16 Jan 2023 22:52:39 -0500 Subject: persist voice settings, handle volume w/ no ssrc --- src/abaddon.cpp | 5 ++--- src/audio/manager.cpp | 23 +++++++++++++++++++---- src/audio/manager.hpp | 3 +++ src/discord/discord.cpp | 4 ++++ src/discord/discord.hpp | 2 ++ src/discord/voiceclient.cpp | 25 +++++++++++++++++++++++++ src/discord/voiceclient.hpp | 4 ++++ src/windows/voicewindow.cpp | 21 +++++++++++++++------ 8 files changed, 74 insertions(+), 13 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 5ed17be..417d7af 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -474,9 +474,8 @@ void Abaddon::ShowVoiceWindow() { }); wnd->signal_user_volume_changed().connect([this](Snowflake id, double volume) { - if (const auto ssrc = m_discord.GetSSRCOfUser(id); ssrc.has_value()) { - m_audio->SetVolumeSSRC(*ssrc, volume); - } + auto &vc = m_discord.GetVoiceClient(); + vc.SetUserVolume(id, volume); }); wnd->set_position(Gtk::WIN_POS_CENTER); diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 1a7bda5..618773d 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -278,13 +278,21 @@ void AudioManager::SetPlayback(bool playback) { } void AudioManager::SetCaptureGate(double gate) { - m_capture_gate = gate * 0.01; + 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) { @@ -296,9 +304,15 @@ void AudioManager::SetMuteSSRC(uint32_t ssrc, bool mute) { void AudioManager::SetVolumeSSRC(uint32_t ssrc, double volume) { std::lock_guard _(m_mutex); - volume *= 0.01; - constexpr const double E = 2.71828182845904523536; - m_volume_ssrc[ssrc] = (std::exp(volume) - 1) / (E - 1); + 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) { @@ -463,4 +477,5 @@ AudioDevices &AudioManager::GetDevices() { AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { return m_signal_opus_packet; } + #endif diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index dbb0b6e..9cd7f42 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -40,9 +40,12 @@ public: void SetCaptureGate(double gate); void SetCaptureGain(double gain); + [[nodiscard]] double GetCaptureGate() const noexcept; + [[nodiscard]] double GetCaptureGain() const noexcept; void SetMuteSSRC(uint32_t ssrc, bool mute); void SetVolumeSSRC(uint32_t ssrc, double volume); + [[nodiscard]] double GetVolumeSSRC(uint32_t ssrc) const; void SetEncodingApplication(int application); [[nodiscard]] int GetEncodingApplication(); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 2c7358b..45e8919 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1226,6 +1226,10 @@ std::optional DiscordClient::GetVoiceState(Snowflake user_id) const { return std::nullopt; } +DiscordVoiceClient &DiscordClient::GetVoiceClient() { + return m_voice; +} + void DiscordClient::SetVoiceMuted(bool is_mute) { m_mute_requested = is_mute; SendVoiceStateUpdate(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fa399cc..10b1d30 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -191,6 +191,8 @@ public: [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; [[nodiscard]] std::optional GetVoiceState(Snowflake user_id) const; + DiscordVoiceClient &GetVoiceClient(); + void SetVoiceMuted(bool is_mute); void SetVoiceDeafened(bool is_deaf); #endif diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index ed83e3c..3d246b1 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -167,6 +167,7 @@ DiscordVoiceClient::~DiscordVoiceClient() { void DiscordVoiceClient::Start() { SetState(State::ConnectingToWebsocket); + m_ssrc_map.clear(); m_heartbeat_waiter.revive(); m_keepalive_waiter.revive(); m_ws.StartConnection("wss://" + m_endpoint + "/?v=7"); @@ -188,6 +189,8 @@ void DiscordVoiceClient::Stop() { m_keepalive_waiter.kill(); if (m_keepalive_thread.joinable()) m_keepalive_thread.join(); + m_ssrc_map.clear(); + m_signal_disconnected.emit(); } @@ -211,6 +214,20 @@ void DiscordVoiceClient::SetUserID(Snowflake id) { m_user_id = id; } +void DiscordVoiceClient::SetUserVolume(Snowflake id, float volume) { + m_user_volumes[id] = volume; + if (const auto ssrc = GetSSRCOfUser(id); ssrc.has_value()) { + Abaddon::Get().GetAudio().SetVolumeSSRC(*ssrc, volume); + } +} + +[[nodiscard]] float DiscordVoiceClient::GetUserVolume(Snowflake id) const { + if (const auto it = m_user_volumes.find(id); it != m_user_volumes.end()) { + return it->second; + } + return 1.0f; +} + std::optional DiscordVoiceClient::GetSSRCOfUser(Snowflake id) const { if (const auto it = m_ssrc_map.find(id); it != m_ssrc_map.end()) { return it->second; @@ -315,6 +332,14 @@ void DiscordVoiceClient::HandleGatewaySessionDescription(const VoiceGatewayMessa void DiscordVoiceClient::HandleGatewaySpeaking(const VoiceGatewayMessage &m) { VoiceSpeakingData d = m.Data; + + // set volume if already set but ssrc just found + if (const auto iter = m_user_volumes.find(d.UserID); iter != m_user_volumes.end()) { + if (m_ssrc_map.find(d.UserID) == m_ssrc_map.end()) { + Abaddon::Get().GetAudio().SetVolumeSSRC(d.SSRC, iter->second); + } + } + m_ssrc_map[d.UserID] = d.SSRC; m_signal_speaking.emit(d); } diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 916d070..7bf4295 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -195,6 +195,9 @@ public: void SetServerID(Snowflake id); void SetUserID(Snowflake id); + // todo serialize + void SetUserVolume(Snowflake id, float volume); + [[nodiscard]] float GetUserVolume(Snowflake id) const; [[nodiscard]] std::optional GetSSRCOfUser(Snowflake id) const; // Is a websocket and udp connection fully established @@ -241,6 +244,7 @@ private: Snowflake m_user_id; std::unordered_map m_ssrc_map; + std::unordered_map m_user_volumes; std::array m_secret_key; diff --git a/src/windows/voicewindow.cpp b/src/windows/voicewindow.cpp index 7c90e96..c82a0aa 100644 --- a/src/windows/voicewindow.cpp +++ b/src/windows/voicewindow.cpp @@ -24,7 +24,7 @@ public: m_volume.set_value_pos(Gtk::POS_LEFT); m_volume.set_value(100.0); m_volume.signal_value_changed().connect([this]() { - m_signal_volume.emit(m_volume.get_value()); + m_signal_volume.emit(m_volume.get_value() * 0.01); }); m_horz.add(m_avatar); @@ -54,6 +54,10 @@ public: m_meter.SetVolume(frac); } + void RestoreGain(double frac) { + m_volume.set_value(frac * 100.0); + } + private: Gtk::Box m_main; Gtk::Box m_horz; @@ -92,6 +96,8 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) set_default_size(300, 300); auto &discord = Abaddon::Get().GetDiscordClient(); + auto &audio = Abaddon::Get().GetAudio(); + SetUsers(discord.GetUsersInVoiceChannel(m_channel_id)); discord.signal_voice_user_disconnect().connect(sigc::mem_fun(*this, &VoiceWindow::OnUserDisconnect)); @@ -108,17 +114,16 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) m_capture_gate.set_range(0.0, 100.0); m_capture_gate.set_value_pos(Gtk::POS_LEFT); - m_capture_gate.set_value(0.0); + m_capture_gate.set_value(audio.GetCaptureGate() * 100.0); m_capture_gate.signal_value_changed().connect([this]() { - // todo this should probably emit 0-1 i dont think the mgr should be responsible for scaling down - const double val = m_capture_gate.get_value(); + const double val = m_capture_gate.get_value() * 0.01; m_signal_gate.emit(val); - m_capture_volume.SetTick(val / 100.0); + m_capture_volume.SetTick(val); }); m_capture_gain.set_range(0.0, 200.0); m_capture_gain.set_value_pos(Gtk::POS_LEFT); - m_capture_gain.set_value(100.0); + m_capture_gain.set_value(audio.GetCaptureGain() * 100.0); m_capture_gain.signal_value_changed().connect([this]() { const double val = m_capture_gain.get_value(); m_signal_gain.emit(val / 100.0); @@ -174,7 +179,9 @@ VoiceWindow::VoiceWindow(Snowflake channel_id) } void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { + const auto me = Abaddon::Get().GetDiscordClient().GetUserData().ID; for (auto id : user_ids) { + if (id == me) continue; m_user_list.add(*CreateRow(id)); } } @@ -182,6 +189,8 @@ void VoiceWindow::SetUsers(const std::unordered_set &user_ids) { Gtk::ListBoxRow *VoiceWindow::CreateRow(Snowflake id) { auto *row = Gtk::make_managed(id); m_rows[id] = row; + auto &vc = Abaddon::Get().GetDiscordClient().GetVoiceClient(); + row->RestoreGain(vc.GetUserVolume(id)); row->signal_mute_cs().connect([this, id](bool is_muted) { m_signal_mute_user_cs.emit(id, is_muted); }); -- cgit v1.2.3 From d30be3326d43aa57760fdcbc6b4021b3570dc4a3 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 29 Apr 2023 21:27:16 -0400 Subject: make standalone file for miniaudio implementation --- src/audio/ma_impl.cpp | 2 ++ src/audio/manager.cpp | 1 - src/notifications/notifier_gio.cpp | 3 --- 3 files changed, 2 insertions(+), 4 deletions(-) create mode 100644 src/audio/ma_impl.cpp (limited to 'src/audio/manager.cpp') diff --git a/src/audio/ma_impl.cpp b/src/audio/ma_impl.cpp new file mode 100644 index 0000000..662e2b2 --- /dev/null +++ b/src/audio/ma_impl.cpp @@ -0,0 +1,2 @@ +#define MINIAUDIO_IMPLEMENTATION +#include diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 618773d..6e85ed8 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -9,7 +9,6 @@ #include #include #include -#define MINIAUDIO_IMPLEMENTATION #include #include #include diff --git a/src/notifications/notifier_gio.cpp b/src/notifications/notifier_gio.cpp index 22d4c87..128167f 100644 --- a/src/notifications/notifier_gio.cpp +++ b/src/notifications/notifier_gio.cpp @@ -1,9 +1,6 @@ #include "notifier.hpp" #include -#define MINIAUDIO_IMPLEMENTATION -#include - Notifier::Notifier() { #ifdef ENABLE_NOTIFICATION_SOUNDS if (ma_engine_init(nullptr, &m_engine) != MA_SUCCESS) { -- cgit v1.2.3 From b5f2b7171f7c0a19d4c4a2974cd8977fd4955464 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 28 May 2023 16:53:07 -0400 Subject: make RTP timestamp strictly linear this should fix some artificial delay --- src/audio/manager.cpp | 13 +++++++++++++ src/audio/manager.hpp | 4 ++++ src/discord/voiceclient.cpp | 11 ++++++----- src/discord/voiceclient.hpp | 1 - 4 files changed, 23 insertions(+), 6 deletions(-) (limited to 'src/audio/manager.cpp') diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 6e85ed8..ce20940 100644 --- a/src/audio/manager.cpp +++ b/src/audio/manager.cpp @@ -50,6 +50,15 @@ void capture_data_callback(ma_device *pDevice, void *pOutput, const void *pInput if (mgr == nullptr) return; 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; } AudioManager::AudioManager() { @@ -473,6 +482,10 @@ AudioDevices &AudioManager::GetDevices() { return m_devices; } +uint32_t AudioManager::GetRTPTimestamp() const noexcept { + return m_rtp_timestamp; +} + AudioManager::type_signal_opus_packet AudioManager::signal_opus_packet() { return m_signal_opus_packet; } diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp index 9cd7f42..ed40f35 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -63,6 +63,8 @@ public: [[nodiscard]] AudioDevices &GetDevices(); + [[nodiscard]] uint32_t GetRTPTimestamp() const noexcept; + private: void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames); @@ -113,6 +115,8 @@ private: AudioDevices m_devices; + std::atomic m_rtp_timestamp = 0; + public: using type_signal_opus_packet = sigc::signal; type_signal_opus_packet signal_opus_packet(); diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index c37ba7b..741b07f 100644 --- a/src/discord/voiceclient.cpp +++ b/src/discord/voiceclient.cpp @@ -49,17 +49,18 @@ void UDPSocket::SetSSRC(uint32_t ssrc) { void UDPSocket::SendEncrypted(const uint8_t *data, size_t len) { m_sequence++; - m_timestamp += 480; // this is important + + const uint32_t timestamp = Abaddon::Get().GetAudio().GetRTPTimestamp(); std::vector rtp(12 + len + crypto_secretbox_MACBYTES, 0); rtp[0] = 0x80; // ver 2 rtp[1] = 0x78; // payload type 0x78 rtp[2] = (m_sequence >> 8) & 0xFF; rtp[3] = (m_sequence >> 0) & 0xFF; - rtp[4] = (m_timestamp >> 24) & 0xFF; - rtp[5] = (m_timestamp >> 16) & 0xFF; - rtp[6] = (m_timestamp >> 8) & 0xFF; - rtp[7] = (m_timestamp >> 0) & 0xFF; + rtp[4] = (timestamp >> 24) & 0xFF; + rtp[5] = (timestamp >> 16) & 0xFF; + rtp[6] = (timestamp >> 8) & 0xFF; + rtp[7] = (timestamp >> 0) & 0xFF; rtp[8] = (m_ssrc >> 24) & 0xFF; rtp[9] = (m_ssrc >> 16) & 0xFF; rtp[10] = (m_ssrc >> 8) & 0xFF; diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 7bf4295..0112749 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -171,7 +171,6 @@ private: uint32_t m_ssrc; uint16_t m_sequence = 0; - uint32_t m_timestamp = 0; public: using type_signal_data = sigc::signal>; -- cgit v1.2.3