Skip to content

Commit a48a726

Browse files
add some more RPC stress tests and audio latency tests (#74)
* use a real LLM response and save it to data_rpc_test_data.txt, use it for the rpc stress tests * remove the un-needed logs * adding more rpc / audio latency stress tests * address the comments
1 parent 32c593f commit a48a726

5 files changed

Lines changed: 1372 additions & 1 deletion

File tree

src/tests/common/audio_utils.h

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2025 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <atomic>
20+
#include <chrono>
21+
#include <cmath>
22+
#include <cstdint>
23+
#include <livekit/livekit.h>
24+
#include <memory>
25+
#include <thread>
26+
27+
namespace livekit {
28+
namespace test {
29+
30+
// Default audio parameters for tests
31+
constexpr int kDefaultAudioSampleRate = 48000;
32+
constexpr int kDefaultAudioChannels = 1;
33+
constexpr int kDefaultAudioFrameMs = 10;
34+
constexpr int kDefaultSamplesPerChannel =
35+
kDefaultAudioSampleRate * kDefaultAudioFrameMs / 1000;
36+
37+
/// Create an AudioFrame with the given parameters for a 10ms duration.
38+
/// @param sample_rate Sample rate in Hz.
39+
/// @param num_channels Number of audio channels.
40+
/// @return A new AudioFrame with the appropriate size.
41+
inline AudioFrame create10msFrame(int sample_rate, int num_channels) {
42+
const int samples_per_channel = sample_rate / 100; // 10ms worth of samples
43+
return AudioFrame::create(sample_rate, num_channels, samples_per_channel);
44+
}
45+
46+
/// Run an audio tone generation loop.
47+
/// @param source The AudioSource to capture frames to.
48+
/// @param running Atomic flag to control when to stop the loop.
49+
/// @param base_freq_hz Base frequency in Hz for the tone.
50+
/// @param siren_mode If true, modulates the frequency to create a siren effect.
51+
/// @param sample_rate Audio sample rate (default: 48000).
52+
/// @param num_channels Number of audio channels (default: 1).
53+
/// @param frame_ms Duration of each frame in milliseconds (default: 10).
54+
inline void runToneLoop(const std::shared_ptr<AudioSource> &source,
55+
std::atomic<bool> &running, double base_freq_hz,
56+
bool siren_mode,
57+
int sample_rate = kDefaultAudioSampleRate,
58+
int num_channels = kDefaultAudioChannels,
59+
int frame_ms = kDefaultAudioFrameMs) {
60+
double phase = 0.0;
61+
constexpr double kTwoPi = 6.283185307179586;
62+
const int samples_per_channel = sample_rate * frame_ms / 1000;
63+
64+
while (running.load(std::memory_order_relaxed)) {
65+
AudioFrame frame =
66+
AudioFrame::create(sample_rate, num_channels, samples_per_channel);
67+
auto &samples = frame.data();
68+
69+
const double time_sec =
70+
static_cast<double>(
71+
std::chrono::duration_cast<std::chrono::milliseconds>(
72+
std::chrono::steady_clock::now().time_since_epoch())
73+
.count()) /
74+
1000.0;
75+
const double freq =
76+
siren_mode ? (700.0 + 250.0 * std::sin(time_sec * 2.0)) : base_freq_hz;
77+
78+
const double phase_inc = kTwoPi * freq / static_cast<double>(sample_rate);
79+
for (int i = 0; i < samples_per_channel; ++i) {
80+
samples[static_cast<std::size_t>(i)] =
81+
static_cast<std::int16_t>(std::sin(phase) * 12000.0);
82+
phase += phase_inc;
83+
if (phase > kTwoPi) {
84+
phase -= kTwoPi;
85+
}
86+
}
87+
88+
try {
89+
source->captureFrame(frame);
90+
} catch (...) {
91+
break;
92+
}
93+
94+
std::this_thread::sleep_for(std::chrono::milliseconds(frame_ms));
95+
}
96+
}
97+
98+
/// Fill an AudioFrame with a sine wave tone.
99+
/// @param frame The AudioFrame to fill.
100+
/// @param freq_hz Frequency of the tone in Hz.
101+
/// @param sample_rate Sample rate in Hz.
102+
/// @param phase Reference to phase accumulator (updated after call).
103+
/// @param amplitude Amplitude of the tone (default: 12000).
104+
inline void fillToneFrame(AudioFrame &frame, double freq_hz, int sample_rate,
105+
double &phase, double amplitude = 12000.0) {
106+
constexpr double kTwoPi = 6.283185307179586;
107+
auto &samples = frame.data();
108+
const double phase_inc = kTwoPi * freq_hz / static_cast<double>(sample_rate);
109+
110+
for (std::size_t i = 0; i < samples.size(); ++i) {
111+
samples[i] = static_cast<std::int16_t>(std::sin(phase) * amplitude);
112+
phase += phase_inc;
113+
if (phase > kTwoPi) {
114+
phase -= kTwoPi;
115+
}
116+
}
117+
}
118+
119+
} // namespace test
120+
} // namespace livekit

src/tests/common/video_utils.h

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2025 LiveKit
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <atomic>
20+
#include <chrono>
21+
#include <cstdint>
22+
#include <livekit/livekit.h>
23+
#include <memory>
24+
#include <thread>
25+
26+
namespace livekit {
27+
namespace test {
28+
29+
// Default video dimensions for tests
30+
constexpr int kDefaultVideoWidth = 640;
31+
constexpr int kDefaultVideoHeight = 360;
32+
33+
/// Fill a VideoFrame with webcam-like colors (green tint with varying blue).
34+
/// @param frame The VideoFrame to fill (must be ARGB format).
35+
/// @param frame_index Frame counter used to vary the blue channel.
36+
inline void fillWebcamLikeFrame(VideoFrame &frame, std::uint64_t frame_index) {
37+
// ARGB layout: [A, R, G, B]
38+
std::uint8_t *data = frame.data();
39+
const std::size_t size = frame.dataSize();
40+
const std::uint8_t blue = static_cast<std::uint8_t>((frame_index * 3) % 255);
41+
for (std::size_t i = 0; i < size; i += 4) {
42+
data[i + 0] = 255; // A
43+
data[i + 1] = 0; // R
44+
data[i + 2] = 170; // G
45+
data[i + 3] = blue;
46+
}
47+
}
48+
49+
/// Fill a VideoFrame with solid red and encode metadata in the first 16 pixels.
50+
/// @param frame The VideoFrame to fill (must be ARGB format).
51+
/// @param frame_index Frame counter encoded into metadata.
52+
/// @param timestamp_us Timestamp in microseconds encoded into metadata.
53+
inline void fillRedFrameWithMetadata(VideoFrame &frame,
54+
std::uint64_t frame_index,
55+
std::uint64_t timestamp_us) {
56+
// ARGB layout: [A, R, G, B]
57+
std::uint8_t *data = frame.data();
58+
const std::size_t size = frame.dataSize();
59+
for (std::size_t i = 0; i < size; i += 4) {
60+
data[i + 0] = 255; // A
61+
data[i + 1] = 255; // R
62+
data[i + 2] = 0; // G
63+
data[i + 3] = 0; // B
64+
}
65+
66+
// Encode frame counter + timestamp into first 16 pixels for easy debugging.
67+
std::uint8_t meta[16];
68+
for (int i = 0; i < 8; ++i) {
69+
meta[i] = static_cast<std::uint8_t>((frame_index >> (i * 8)) & 0xFF);
70+
meta[8 + i] = static_cast<std::uint8_t>((timestamp_us >> (i * 8)) & 0xFF);
71+
}
72+
for (int i = 0; i < 16; ++i) {
73+
const std::size_t px = static_cast<std::size_t>(i) * 4;
74+
if (px + 3 < size) {
75+
data[px + 0] = 255;
76+
data[px + 1] = 255;
77+
data[px + 2] = meta[i];
78+
data[px + 3] = meta[(15 - i)];
79+
}
80+
}
81+
}
82+
83+
/// Wrapper for fillWebcamLikeFrame with timestamp parameter (ignored).
84+
inline void fillWebcamWrapper(VideoFrame &frame, std::uint64_t frame_index,
85+
std::uint64_t /*timestamp_us*/) {
86+
fillWebcamLikeFrame(frame, frame_index);
87+
}
88+
89+
/// Wrapper for fillRedFrameWithMetadata.
90+
inline void fillRedWrapper(VideoFrame &frame, std::uint64_t frame_index,
91+
std::uint64_t timestamp_us) {
92+
fillRedFrameWithMetadata(frame, frame_index, timestamp_us);
93+
}
94+
95+
/// Type alias for video frame fill functions.
96+
using VideoFrameFillFn = void (*)(VideoFrame &, std::uint64_t, std::uint64_t);
97+
98+
/// Run a video capture loop, calling the fill function for each frame.
99+
/// @param source The VideoSource to capture frames to.
100+
/// @param running Atomic flag to control when to stop the loop.
101+
/// @param fill_fn Function to fill each frame with content.
102+
/// @param width Video frame width (default: kDefaultVideoWidth).
103+
/// @param height Video frame height (default: kDefaultVideoHeight).
104+
/// @param frame_interval Frame interval (default: 33ms for ~30fps).
105+
inline void runVideoLoop(
106+
const std::shared_ptr<VideoSource> &source, std::atomic<bool> &running,
107+
VideoFrameFillFn fill_fn, int width = kDefaultVideoWidth,
108+
int height = kDefaultVideoHeight,
109+
std::chrono::milliseconds frame_interval = std::chrono::milliseconds(33)) {
110+
VideoFrame frame = VideoFrame::create(width, height, VideoBufferType::ARGB);
111+
std::uint64_t frame_index = 0;
112+
while (running.load(std::memory_order_relaxed)) {
113+
const auto now = std::chrono::steady_clock::now().time_since_epoch();
114+
const auto ts_us = static_cast<std::uint64_t>(
115+
std::chrono::duration_cast<std::chrono::microseconds>(now).count());
116+
fill_fn(frame, frame_index, ts_us);
117+
try {
118+
source->captureFrame(frame, static_cast<std::int64_t>(ts_us),
119+
VideoRotation::VIDEO_ROTATION_0);
120+
} catch (...) {
121+
break;
122+
}
123+
frame_index++;
124+
std::this_thread::sleep_for(frame_interval);
125+
}
126+
}
127+
128+
} // namespace test
129+
} // namespace livekit

0 commit comments

Comments
 (0)