Skip to content

Commit 8d5d5ea

Browse files
Change SimpleRoom to play a welcome wav file rather than noise
1 parent e568069 commit 8d5d5ea

6 files changed

Lines changed: 236 additions & 9 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data/welcome.wav filter=lfs diff=lfs merge=lfs -text

data/welcome.wav

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e5a61a07be0392ac689876d33b92b75dc73442217188ff5d7ed23e7597d1ae98
3+
size 666724

examples/CMakeLists.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
cmake_minimum_required(VERSION 3.31.0)
22
project (livekit-examples)
33

4-
add_executable(SimpleRoom simple_room/main.cpp)
4+
add_executable(SimpleRoom
5+
simple_room/main.cpp
6+
simple_room/wav_audio_source.cpp
7+
simple_room/wav_audio_source.h
8+
)
9+
510
target_link_libraries(SimpleRoom livekit)
11+
12+
add_custom_command(TARGET SimpleRoom POST_BUILD
13+
COMMAND ${CMAKE_COMMAND} -E copy_directory
14+
${CMAKE_SOURCE_DIR}/data
15+
${CMAKE_CURRENT_BINARY_DIR}/data
16+
)

examples/simple_room/main.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <vector>
1010

1111
#include "livekit/livekit.h"
12+
#include "wav_audio_source.h"
1213

1314
// TODO(shijing), remove this livekit_ffi.h as it should be internal only.
1415
#include "livekit_ffi.h"
@@ -126,19 +127,13 @@ void runNoiseCaptureLoop(const std::shared_ptr<AudioSource> &source) {
126127
const int frame_ms = 10;
127128
const int samples_per_channel = sample_rate * frame_ms / 1000;
128129

129-
std::mt19937 rng(std::random_device{}());
130-
std::uniform_int_distribution<int16_t> noise_dist(-5000, 5000);
130+
WavAudioSource WavAudioSource("data/welcome.wav", 48000, 1, false);
131131
using Clock = std::chrono::steady_clock;
132132
auto next_deadline = Clock::now();
133133
while (g_running.load(std::memory_order_relaxed)) {
134134
AudioFrame frame =
135135
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-
}
136+
WavAudioSource.fillFrame(frame);
142137
try {
143138
source->captureFrame(frame);
144139
} catch (const std::exception &e) {
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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+
#include "wav_audio_source.h"
18+
19+
#include <cstring>
20+
#include <fstream>
21+
#include <stdexcept>
22+
23+
// --------------------------------------------------
24+
// Minimal WAV loader (16-bit PCM only)
25+
// --------------------------------------------------
26+
WavData load_wav16(const std::string &path) {
27+
std::ifstream file(path, std::ios::binary);
28+
if (!file) {
29+
throw std::runtime_error("Failed to open WAV file: " + path);
30+
}
31+
32+
auto read_u32 = [&](uint32_t &out_value) {
33+
file.read(reinterpret_cast<char *>(&out_value), 4);
34+
};
35+
auto read_u16 = [&](uint16_t &out_value) {
36+
file.read(reinterpret_cast<char *>(&out_value), 2);
37+
};
38+
39+
char riff[4];
40+
file.read(riff, 4);
41+
if (std::strncmp(riff, "RIFF", 4) != 0) {
42+
throw std::runtime_error("Not a RIFF file");
43+
}
44+
45+
uint32_t chunk_size = 0;
46+
read_u32(chunk_size);
47+
48+
char wave[4];
49+
file.read(wave, 4);
50+
if (std::strncmp(wave, "WAVE", 4) != 0) {
51+
throw std::runtime_error("Not a WAVE file");
52+
}
53+
54+
uint16_t audio_format = 0;
55+
uint16_t num_channels = 0;
56+
uint32_t sample_rate = 0;
57+
uint16_t bits_per_sample = 0;
58+
59+
bool have_fmt = false;
60+
bool have_data = false;
61+
std::vector<int16_t> samples;
62+
63+
while (!have_data && file) {
64+
char sub_id[4];
65+
file.read(sub_id, 4);
66+
67+
uint32_t sub_size = 0;
68+
read_u32(sub_size);
69+
70+
if (std::strncmp(sub_id, "fmt ", 4) == 0) {
71+
have_fmt = true;
72+
73+
read_u16(audio_format);
74+
read_u16(num_channels);
75+
read_u32(sample_rate);
76+
77+
uint32_t byte_rate = 0;
78+
uint16_t block_align = 0;
79+
read_u32(byte_rate);
80+
read_u16(block_align);
81+
read_u16(bits_per_sample);
82+
83+
if (sub_size > 16) {
84+
file.seekg(sub_size - 16, std::ios::cur);
85+
}
86+
87+
if (audio_format != 1) {
88+
throw std::runtime_error("Only PCM WAV supported");
89+
}
90+
if (bits_per_sample != 16) {
91+
throw std::runtime_error("Only 16-bit WAV supported");
92+
}
93+
94+
} else if (std::strncmp(sub_id, "data", 4) == 0) {
95+
if (!have_fmt) {
96+
throw std::runtime_error("data chunk appeared before fmt chunk");
97+
}
98+
99+
have_data = true;
100+
const std::size_t count = sub_size / sizeof(int16_t);
101+
samples.resize(count);
102+
file.read(reinterpret_cast<char *>(samples.data()), sub_size);
103+
104+
} else {
105+
// Unknown chunk: skip it
106+
file.seekg(sub_size, std::ios::cur);
107+
}
108+
}
109+
110+
if (!have_data) {
111+
throw std::runtime_error("No data chunk in WAV file");
112+
}
113+
114+
WavData out;
115+
out.sample_rate = static_cast<int>(sample_rate);
116+
out.num_channels = static_cast<int>(num_channels);
117+
out.samples = std::move(samples);
118+
return out;
119+
}
120+
121+
WavAudioSource::WavAudioSource(const std::string &path,
122+
int expected_sample_rate, int expected_channels,
123+
bool loop_enabled)
124+
: loop_enabled_(loop_enabled) {
125+
wav_ = load_wav16(path);
126+
127+
if (wav_.sample_rate != expected_sample_rate) {
128+
throw std::runtime_error("WAV sample rate mismatch");
129+
}
130+
if (wav_.num_channels != expected_channels) {
131+
throw std::runtime_error("WAV channel count mismatch");
132+
}
133+
134+
sample_rate_ = wav_.sample_rate;
135+
num_channels_ = wav_.num_channels;
136+
137+
playhead_ = 0;
138+
}
139+
140+
void WavAudioSource::fillFrame(AudioFrame &frame) {
141+
const std::size_t frame_samples =
142+
static_cast<std::size_t>(frame.num_channels()) *
143+
static_cast<std::size_t>(frame.samples_per_channel());
144+
145+
int16_t *dst = frame.data().data();
146+
const std::size_t total_wav_samples = wav_.samples.size();
147+
148+
for (std::size_t i = 0; i < frame_samples; ++i) {
149+
if (playhead_ < total_wav_samples) {
150+
dst[i] = wav_.samples[playhead_];
151+
++playhead_;
152+
} else if (loop_enabled_ && total_wav_samples > 0) {
153+
playhead_ = 0;
154+
dst[i] = wav_.samples[playhead_];
155+
++playhead_;
156+
} else {
157+
dst[i] = 0;
158+
}
159+
}
160+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
/*
3+
* Copyright 2025 LiveKit
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an “AS IS” BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#pragma once
19+
20+
#include "livekit/livekit.h"
21+
#include <cstddef>
22+
#include <cstdint>
23+
#include <string>
24+
#include <vector>
25+
26+
// Simple WAV container for 16-bit PCM files
27+
struct WavData {
28+
int sample_rate = 0;
29+
int num_channels = 0;
30+
std::vector<int16_t> samples;
31+
};
32+
33+
// Helper that loads 16-bit PCM WAV (16-bit, PCM only)
34+
WavData loadWav16(const std::string &path);
35+
36+
using namespace livekit;
37+
38+
class WavAudioSource {
39+
public:
40+
// loop_enabled: whether to loop when reaching the end
41+
WavAudioSource(const std::string &path, int expected_sample_rate,
42+
int expected_channels, bool loop_enabled = true);
43+
44+
// Fill a frame with the next chunk of audio.
45+
// This does NOT call captureFrame(): you do that outside.
46+
void fillFrame(AudioFrame &frame);
47+
48+
private:
49+
void initLoopDelayCounter();
50+
51+
WavData wav_;
52+
std::size_t playhead_ = 0;
53+
54+
const bool loop_enabled_;
55+
int sample_rate_;
56+
int num_channels_;
57+
};

0 commit comments

Comments
 (0)