summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorouwou <26526779+ouwou@users.noreply.github.com>2024-03-31 21:32:41 -0400
committerouwou <26526779+ouwou@users.noreply.github.com>2024-03-31 21:32:41 -0400
commitb7906eb918d0df1fa1508a77732113ca94056e4e (patch)
treefa533b611822b25924d4e2947bcf90307c6e5cff
parent1367e162c0581bdea7ec1d2129b18e70d7d43d2b (diff)
parentfda687eaa8b33899648e97798a7fbcaa9edda12b (diff)
downloadabaddon-portaudio-b7906eb918d0df1fa1508a77732113ca94056e4e.tar.gz
abaddon-portaudio-b7906eb918d0df1fa1508a77732113ca94056e4e.zip
Merge branch 'master' into stages
-rw-r--r--README.md9
-rw-r--r--src/audio/jitterbuffer.hpp82
-rw-r--r--src/audio/manager.cpp23
-rw-r--r--src/audio/manager.hpp3
-rw-r--r--src/components/channellist/channellisttree.hpp1
-rw-r--r--src/components/channellist/classic/guildlist.cpp1
-rw-r--r--src/components/channellist/classic/guildlist.hpp1
-rw-r--r--src/components/channellist/classic/guildlistguilditem.hpp2
-rw-r--r--src/discord/discord.hpp2
-rw-r--r--src/discord/httpclient.cpp6
-rw-r--r--src/discord/voiceclient.cpp2
-rw-r--r--src/settings.cpp2
-rw-r--r--src/settings.hpp2
-rw-r--r--src/startup.cpp4
14 files changed, 127 insertions, 13 deletions
diff --git a/README.md b/README.md
index cbbe341..349ab15 100644
--- a/README.md
+++ b/README.md
@@ -330,9 +330,12 @@ For example, memory_db would be set by adding `memory_db = true` under the line
#### voice
-| Setting | Type | Default | Description |
-|---------|--------|------------------------------------|------------------------------------------------------------|
-| `vad` | string | rnnoise if enabled, gate otherwise | Method used for voice activity detection. Changeable in UI |
+| Setting | Type | Default | Description |
+|--------------------------|--------|------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
+| `vad` | string | rnnoise if enabled, gate otherwise | Method used for voice activity detection. Changeable in UI |
+| `backends` | string | empty | Change backend priority when initializing miniaudio: `wasapi;dsound;winmm;coreaudio;sndio;audio4;oss;pulseaudio;alsa;jack` |
+| `jitter_latency_desired` | int | 50 | Desired/Minimum latency for jitter buffer (in milliseconds) |
+| `jitter_latency_maximum` | int | 200 | Maximum latency for jitter buffer before frames are discarded (in milliseconds) |
#### windows
diff --git a/src/audio/jitterbuffer.hpp b/src/audio/jitterbuffer.hpp
new file mode 100644
index 0000000..3da3594
--- /dev/null
+++ b/src/audio/jitterbuffer.hpp
@@ -0,0 +1,82 @@
+#pragma once
+#include <chrono>
+#include <cstdint>
+#include <deque>
+
+// very simple non-RTP-based jitter buffer. does not handle out-of-order
+template<typename SampleFormat>
+class JitterBuffer {
+public:
+ /*
+ * desired_latency: how many milliseconds before audio can be drawn from buffer
+ * maximum_latency: how many milliseconds before old audio starts to be discarded
+ */
+ JitterBuffer(int desired_latency, int maximum_latency, int channels, int sample_rate)
+ : m_desired_latency(desired_latency)
+ , m_maximum_latency(maximum_latency)
+ , m_channels(channels)
+ , m_sample_rate(sample_rate)
+ , m_last_push(std::chrono::steady_clock::now()) {
+ }
+
+ [[nodiscard]] size_t Available() const noexcept {
+ return m_samples.size();
+ }
+
+ bool PopSamples(SampleFormat *ptr, size_t amount) {
+ CheckBuffering();
+ if (m_buffering || Available() < amount) return false;
+ std::copy(m_samples.begin(), m_samples.begin() + amount, ptr);
+ m_samples.erase(m_samples.begin(), m_samples.begin() + amount);
+ return true;
+ }
+
+ void PushSamples(SampleFormat *ptr, size_t amount) {
+ m_samples.insert(m_samples.end(), ptr, ptr + amount);
+ m_last_push = std::chrono::steady_clock::now();
+ const auto buffered = MillisBuffered();
+ if (buffered > m_maximum_latency) {
+ const auto overflow_ms = MillisBuffered() - m_maximum_latency;
+ const auto overflow_samples = overflow_ms * m_channels * m_sample_rate / 1000;
+ m_samples.erase(m_samples.begin(), m_samples.begin() + overflow_samples);
+ }
+ }
+
+private:
+ [[nodiscard]] size_t MillisBuffered() const {
+ return m_samples.size() * 1000 / m_channels / m_sample_rate;
+ }
+
+ void CheckBuffering() {
+ // if we arent buffering but the buffer is empty then we should be
+ if (m_samples.empty()) {
+ if (!m_buffering) {
+ m_buffering = true;
+ }
+ return;
+ }
+
+ if (!m_buffering) return;
+
+ // if we reached desired latency, we are sufficiently buffered
+ const auto millis_buffered = MillisBuffered();
+ if (millis_buffered >= m_desired_latency) {
+ m_buffering = false;
+ }
+ // if we havent buffered to desired latency but max latency has elapsed, exit buffering so it doesnt get stuck
+ const auto now = std::chrono::steady_clock::now();
+ const auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_last_push).count();
+ if (millis >= m_maximum_latency) {
+ m_buffering = false;
+ }
+ }
+
+ int m_desired_latency;
+ int m_maximum_latency;
+ int m_channels;
+ int m_sample_rate;
+ bool m_buffering = true;
+ std::chrono::time_point<std::chrono::steady_clock> m_last_push;
+
+ std::deque<SampleFormat> m_samples;
+};
diff --git a/src/audio/manager.cpp b/src/audio/manager.cpp
index eaac3bf..f9ffdd5 100644
--- a/src/audio/manager.cpp
+++ b/src/audio/manager.cpp
@@ -6,6 +6,7 @@
#endif
#include "manager.hpp"
+#include "abaddon.hpp"
#include <array>
#include <glibmm/main.h>
#include <spdlog/sinks/stdout_color_sinks.h>
@@ -25,6 +26,7 @@ const uint8_t *StripRTPExtensionHeader(const uint8_t *buf, int num_bytes, size_t
return buf;
}
+// frameCount is configured to be 480 samples per channel
void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount) {
AudioManager *mgr = reinterpret_cast<AudioManager *>(pDevice->pUserData);
if (mgr == nullptr) return;
@@ -36,12 +38,14 @@ void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uin
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<size_t>(buf.size()), static_cast<size_t>(frameCount * 2ULL));
- for (size_t i = 0; i < n; i++) {
+
+ static std::array<int16_t, 480 * 2> buf;
+
+ if (!pair.first.PopSamples(buf.data(), 480 * 2)) continue;
+
+ for (size_t i = 0; i < 480 * 2; i++) {
pOutputF32[i] += volume * buf[i] / 32768.F;
}
- buf.erase(buf.begin(), buf.begin() + n);
}
}
@@ -201,7 +205,14 @@ void AudioManager::AddSSRC(uint32_t ssrc) {
int error;
if (m_sources.find(ssrc) == m_sources.end()) {
auto *decoder = opus_decoder_create(48000, 2, &error);
- m_sources.insert(std::make_pair(ssrc, std::make_pair(std::deque<int16_t> {}, decoder)));
+ auto &s = Abaddon::Get().GetSettings();
+ m_sources.insert(std::make_pair(ssrc, std::make_pair(
+ JitterBuffer<int16_t>(
+ s.JitterDesiredLatency,
+ s.JitterMaximumLatency,
+ 2,
+ 48000),
+ decoder)));
}
}
@@ -241,7 +252,7 @@ void AudioManager::FeedMeOpus(uint32_t ssrc, const std::vector<uint8_t> &data) {
} else {
UpdateReceiveVolume(ssrc, pcm.data(), decoded);
auto &buf = it->second.first;
- buf.insert(buf.end(), pcm.begin(), pcm.begin() + decoded * 2);
+ buf.PushSamples(pcm.data(), decoded * 2);
}
}
}
diff --git a/src/audio/manager.hpp b/src/audio/manager.hpp
index 5716fc5..56882fd 100644
--- a/src/audio/manager.hpp
+++ b/src/audio/manager.hpp
@@ -21,6 +21,7 @@
#endif
#include "devices.hpp"
+#include "jitterbuffer.hpp"
// clang-format on
class AudioManager {
@@ -136,7 +137,7 @@ private:
mutable std::mutex m_rnn_mutex;
#endif
- std::unordered_map<uint32_t, std::pair<std::deque<int16_t>, OpusDecoder *>> m_sources;
+ std::unordered_map<uint32_t, std::pair<JitterBuffer<int16_t>, OpusDecoder *>> m_sources;
OpusEncoder *m_encoder;
diff --git a/src/components/channellist/channellisttree.hpp b/src/components/channellist/channellisttree.hpp
index 3841e3c..f61cea7 100644
--- a/src/components/channellist/channellisttree.hpp
+++ b/src/components/channellist/channellisttree.hpp
@@ -9,6 +9,7 @@
#include <gtkmm/treemodel.h>
#include <gtkmm/treestore.h>
#include <gtkmm/treemodelfilter.h>
+#include <gtkmm/treemodelsort.h>
#include <gtkmm/treeview.h>
#include <sigc++/sigc++.h>
#include "discord/discord.hpp"
diff --git a/src/components/channellist/classic/guildlist.cpp b/src/components/channellist/classic/guildlist.cpp
index d756c6f..6a6e3d4 100644
--- a/src/components/channellist/classic/guildlist.cpp
+++ b/src/components/channellist/classic/guildlist.cpp
@@ -1,6 +1,7 @@
#include "guildlist.hpp"
#include "abaddon.hpp"
+#include "util.hpp"
#include "guildlistfolderitem.hpp"
class GuildListDMsButton : public Gtk::EventBox {
diff --git a/src/components/channellist/classic/guildlist.hpp b/src/components/channellist/classic/guildlist.hpp
index 72e88e8..2d745d3 100644
--- a/src/components/channellist/classic/guildlist.hpp
+++ b/src/components/channellist/classic/guildlist.hpp
@@ -1,5 +1,6 @@
#pragma once
#include <gtkmm/listbox.h>
+#include <gtkmm/menu.h>
#include "discord/snowflake.hpp"
#include "discord/usersettings.hpp"
diff --git a/src/components/channellist/classic/guildlistguilditem.hpp b/src/components/channellist/classic/guildlistguilditem.hpp
index 6e2b241..d198e72 100644
--- a/src/components/channellist/classic/guildlistguilditem.hpp
+++ b/src/components/channellist/classic/guildlistguilditem.hpp
@@ -4,6 +4,8 @@
#include <gtkmm/image.h>
#include "discord/guild.hpp"
+struct Message;
+struct MessageAckData;
class GuildListGuildItem : public Gtk::EventBox {
public:
GuildListGuildItem(const GuildData &guild);
diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp
index ed7245e..44a7328 100644
--- a/src/discord/discord.hpp
+++ b/src/discord/discord.hpp
@@ -331,7 +331,7 @@ private:
std::string m_token;
- uint32_t m_build_number = 142000;
+ uint32_t m_build_number = 279382;
void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
std::map<Snowflake, std::set<Snowflake>> m_guild_to_users;
diff --git a/src/discord/httpclient.cpp b/src/discord/httpclient.cpp
index 37436ee..25839f8 100644
--- a/src/discord/httpclient.cpp
+++ b/src/discord/httpclient.cpp
@@ -143,7 +143,13 @@ void HTTPClient::AddHeaders(http::request &r) {
r.set_header(name, val);
}
curl_easy_setopt(r.get_curl(), CURLOPT_COOKIE, m_cookie.c_str());
+ // https://github.com/curl/curl/issues/13226
+ // TODO remove when new release
+#if defined(LIBCURL_VERSION_NUM) && (LIBCURL_VERSION_NUM == 0x080701 || LIBCURL_VERSION_NUM == 0x080700)
+ //
+#else
curl_easy_setopt(r.get_curl(), CURLOPT_ACCEPT_ENCODING, "");
+#endif
}
void HTTPClient::OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb) {
diff --git a/src/discord/voiceclient.cpp b/src/discord/voiceclient.cpp
index 164033f..c0c83b2 100644
--- a/src/discord/voiceclient.cpp
+++ b/src/discord/voiceclient.cpp
@@ -265,6 +265,8 @@ void DiscordVoiceClient::OnGatewayMessage(const std::string &str) {
case VoiceGatewayOp::Speaking:
HandleGatewaySpeaking(msg);
break;
+ case VoiceGatewayOp::HeartbeatAck:
+ break; // stfu
default:
const auto opcode_int = static_cast<int>(msg.Opcode);
m_log->warn("Unhandled opcode: {}", opcode_int);
diff --git a/src/settings.cpp b/src/settings.cpp
index fc76ddb..6dab229 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -130,6 +130,8 @@ void SettingsManager::DefineSettings() {
AddSetting("voice", "vad", "gate"s, &Settings::VAD);
#endif
AddSetting("voice", "backends", ""s, &Settings::Backends);
+ AddSetting("voice", "jitter_latency_desired", 50, &Settings::JitterDesiredLatency);
+ AddSetting("voice", "jitter_latency_maximum", 200, &Settings::JitterMaximumLatency);
}
void SettingsManager::ReadSettings() {
diff --git a/src/settings.hpp b/src/settings.hpp
index 5805452..0b0f6e2 100644
--- a/src/settings.hpp
+++ b/src/settings.hpp
@@ -52,6 +52,8 @@ public:
// [voice]
std::string VAD;
std::string Backends;
+ int JitterDesiredLatency;
+ int JitterMaximumLatency;
// [windows]
bool HideConsole;
diff --git a/src/startup.cpp b/src/startup.cpp
index 78c3b26..4939c2f 100644
--- a/src/startup.cpp
+++ b/src/startup.cpp
@@ -46,8 +46,8 @@ std::optional<Glib::ustring> GetJavascriptFileFromAppPage(const Glib::ustring &c
start_position += str.size();
}
- if (matches.size() >= 10) {
- return matches[matches.size() - 10];
+ if (matches.size() >= 7) {
+ return matches[matches.size() - 7];
}
return {};