33#include < csignal>
44#include < cstdlib>
55#include < iostream>
6+ #include < random>
67#include < string>
78#include < thread>
89#include < vector>
@@ -28,7 +29,7 @@ void print_usage(const char *prog) {
2829 << " LIVEKIT_URL, LIVEKIT_TOKEN\n " ;
2930}
3031
31- void handle_sigint (int ) { g_running = false ; }
32+ void handle_sigint (int ) { g_running. store ( false ) ; }
3233
3334bool parse_args (int argc, char *argv[], std::string &url, std::string &token) {
3435 // 1) --help
@@ -118,6 +119,47 @@ class SimpleRoomDelegate : public livekit::RoomDelegate {
118119 }
119120};
120121
122+ // Test utils to run a capture loop to publish noisy audio frames to the room
123+ void runNoiseCaptureLoop (const std::shared_ptr<AudioSource> &source) {
124+ const int sample_rate = source->sample_rate ();
125+ const int num_channels = source->num_channels ();
126+ const int frame_ms = 10 ;
127+ const int samples_per_channel = sample_rate * frame_ms / 1000 ;
128+
129+ std::mt19937 rng (std::random_device{}());
130+ std::uniform_int_distribution<int16_t > noise_dist (-5000 , 5000 );
131+ using Clock = std::chrono::steady_clock;
132+ auto next_deadline = Clock::now ();
133+ while (g_running.load (std::memory_order_relaxed)) {
134+ AudioFrame frame =
135+ AudioFrame::create (sample_rate, num_channels, samples_per_channel);
136+ const std::size_t total_samples =
137+ static_cast <std::size_t >(num_channels) *
138+ static_cast <std::size_t >(samples_per_channel);
139+ for (std::size_t i = 0 ; i < total_samples; ++i) {
140+ frame.data ()[i] = noise_dist (rng);
141+ }
142+ try {
143+ source->captureFrame (frame);
144+ } catch (const std::exception &e) {
145+ // If something goes wrong, log and break out
146+ std::cerr << " Error in captureFrame: " << e.what () << std::endl;
147+ break ;
148+ }
149+
150+ // Pace the loop to roughly real-time
151+ next_deadline += std::chrono::milliseconds (frame_ms);
152+ std::this_thread::sleep_until (next_deadline);
153+ }
154+
155+ // Optionally clear queued audio on exit
156+ try {
157+ source->clearQueue ();
158+ } catch (...) {
159+ // ignore errors on shutdown
160+ std::cout << " Error in clearQueue" << std::endl;
161+ }
162+ }
121163} // namespace
122164
123165int main (int argc, char *argv[]) {
@@ -194,12 +236,20 @@ int main(int argc, char *argv[]) {
194236 std::cerr << " Failed to publish track: " << e.what () << std::endl;
195237 }
196238
239+ // TODO, if we have pre-buffering feature, we might consider starting the
240+ // thread right after creating the source.
241+ std::thread audioThread (runNoiseCaptureLoop, audioSource);
197242 // Keep the app alive until Ctrl-C so we continue receiving events,
198243 // similar to asyncio.run(main()) keeping the loop running.
199244 while (g_running.load ()) {
200245 std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
201246 }
202247
248+ // Shutdown the audio thread.
249+ if (audioThread.joinable ()) {
250+ audioThread.join ();
251+ }
252+
203253 FfiClient::instance ().shutdown ();
204254 std::cout << " Exiting.\n " ;
205255 return 0 ;
0 commit comments