diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1306f007..52ea356e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,8 @@ jobs: mkdir -p 16x16/devices 24x24/devices 32x32/devices 48x48/devices 64x64/devices 96x96/devices scalable/devices mkdir -p 16x16/status 24x24/status 32x32/status 48x48/status 64x64/status 96x96/status scalable/status cd ${GITHUB_WORKSPACE} + cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/icon-theme.cache ${artifact_dir}/share/icons/Adwaita/icon-theme.cache + cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/index.theme ${artifact_dir}/share/icons/Adwaita/index.theme cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/16x16/%.symbolic.png || : cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/24x24/%.symbolic.png || : cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/%.symbolic.png ${artifact_dir}/share/icons/Adwaita/32x32/%.symbolic.png || : diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 5c6f136e..a1ed3431 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -58,8 +58,8 @@ Abaddon::Abaddon() 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); + spdlog::get("voice")->debug("{} SSRC: {}", m.UserID, m.SSRC); + m_audio.AddSSRC(m.SSRC); }); #endif @@ -244,8 +244,7 @@ int Abaddon::StartGTK() { } #ifdef WITH_VOICE - m_audio = std::make_unique(); - if (!m_audio->OK()) { + 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); dlg.set_position(Gtk::WIN_POS_CENTER); dlg.run(); @@ -341,8 +340,8 @@ void Abaddon::StartDiscord() { } void Abaddon::StopDiscord() { - if (m_discord.Stop()) - SaveState(); + if (m_discord.IsStarted()) SaveState(); + m_discord.Stop(); m_main_window->UpdateMenus(); } @@ -441,13 +440,13 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) { #ifdef WITH_VOICE void Abaddon::OnVoiceConnected() { - m_audio->StartCaptureDevice(); + m_audio.StartCaptureDevice(); ShowVoiceWindow(); } void Abaddon::OnVoiceDisconnected() { - m_audio->StopCaptureDevice(); - m_audio->RemoveAllSSRCs(); + m_audio.StopCaptureDevice(); + m_audio.RemoveAllSSRCs(); if (m_voice_window != nullptr) { m_voice_window->close(); } @@ -461,25 +460,25 @@ void Abaddon::ShowVoiceWindow() { wnd->signal_mute().connect([this](bool is_mute) { m_discord.SetVoiceMuted(is_mute); - m_audio->SetCapture(!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); + m_audio.SetPlayback(!is_deaf); }); wnd->signal_gate().connect([this](double gate) { - m_audio->SetCaptureGate(gate); + m_audio.SetCaptureGate(gate); }); wnd->signal_gain().connect([this](double gain) { - m_audio->SetCaptureGain(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); + m_audio.SetMuteSSRC(*ssrc, is_mute); } }); @@ -1080,7 +1079,7 @@ EmojiResource &Abaddon::GetEmojis() { #ifdef WITH_VOICE AudioManager &Abaddon::GetAudio() { - return *m_audio; + return m_audio; } #endif diff --git a/src/abaddon.hpp b/src/abaddon.hpp index 5256cd55..08070ae2 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -12,6 +12,7 @@ #include "imgmanager.hpp" #include "emojis.hpp" #include "notifications/notifications.hpp" +#include "audio/manager.hpp" #define APP_TITLE "Abaddon" @@ -173,7 +174,7 @@ class Abaddon { EmojiResource m_emojis; #ifdef WITH_VOICE - std::unique_ptr m_audio; + AudioManager m_audio; Gtk::Window *m_voice_window = nullptr; #endif diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp index 9aacd293..3a240797 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() { @@ -484,6 +493,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 28d6d740..0083a1f8 100644 --- a/src/audio/manager.hpp +++ b/src/audio/manager.hpp @@ -65,6 +65,8 @@ class AudioManager { [[nodiscard]] AudioDevices &GetDevices(); + [[nodiscard]] uint32_t GetRTPTimestamp() const noexcept; + private: void OnCapturedPCM(const int16_t *pcm, ma_uint32 frames); @@ -116,6 +118,7 @@ class AudioManager { AudioDevices m_devices; DenoiseState *m_rnnoise; + std::atomic m_rtp_timestamp = 0; public: using type_signal_opus_packet = sigc::signal; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 6a530cbb..2a25a26f 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2813,7 +2813,6 @@ void DiscordClient::SetVoiceState(Snowflake user_id, const VoiceState &state) { spdlog::get("discord")->error("SetVoiceState called with missing channel ID"); return; } - spdlog::get("discord")->debug("SetVoiceState: {} -> {}", user_id, *state.ChannelID); auto flags = VoiceStateFlags::Clear; if (state.IsSelfMuted) flags |= VoiceStateFlags::SelfMute; diff --git a/src/discord/snowflake.hpp b/src/discord/snowflake.hpp index a5c0e6c2..2ced46bf 100644 --- a/src/discord/snowflake.hpp +++ b/src/discord/snowflake.hpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include struct Snowflake { Snowflake(); @@ -39,6 +41,13 @@ struct Snowflake { unsigned long long m_num; }; +template<> +struct fmt::formatter : fmt::formatter { + auto format(Snowflake id, format_context &ctx) -> decltype(ctx.out()) { + return format_to(ctx.out(), "[id: {}]", static_cast(id)); + } +}; + namespace std { template<> struct hash { diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp index c37ba7b4..524d9307 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; @@ -382,7 +383,7 @@ void DiscordVoiceClient::Discovery() { const auto response = m_udp.Receive(); if (response.size() >= 74 && response[0] == 0x00 && response[1] == 0x02) { const char *ip = reinterpret_cast(response.data() + 8); - uint16_t port = (response[73] << 8) | response[74]; + uint16_t port = (response[72] << 8) | response[73]; m_log->info("Discovered IP and port: {}:{}", ip, port); SelectProtocol(ip, port); break; diff --git a/src/discord/voiceclient.hpp b/src/discord/voiceclient.hpp index 7bf4295b..0112749f 100644 --- a/src/discord/voiceclient.hpp +++ b/src/discord/voiceclient.hpp @@ -171,7 +171,6 @@ class UDPSocket { uint32_t m_ssrc; uint16_t m_sequence = 0; - uint32_t m_timestamp = 0; public: using type_signal_data = sigc::signal>;