1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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;
};
|