Skip to content

Commit 4c5b55c

Browse files
Merge branch 'main' of github.com:livekit-examples/cpp-example-collection into feature/token_source_examples
2 parents 3424acc + 50628ed commit 4c5b55c

28 files changed

Lines changed: 526 additions & 888 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ add_subdirectory(logging_levels/basic_usage logging_levels_basic_usage)
9090
add_subdirectory(logging_levels/custom_sinks logging_levels_custom_sinks)
9191
add_subdirectory(hello_livekit/sender hello_livekit_sender)
9292
add_subdirectory(hello_livekit/receiver hello_livekit_receiver)
93-
add_subdirectory(simple_joystick/sender simple_joystick_sender)
94-
add_subdirectory(simple_joystick/receiver simple_joystick_receiver)
93+
add_subdirectory(platform_audio)
9594
add_subdirectory(ping_pong/ping ping_pong_ping)
9695
add_subdirectory(ping_pong/pong ping_pong_pong)
9796
add_subdirectory(user_timestamped_video)

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,21 @@ For example:
7373
./build/basic_room/basic_room --url <ws-url> --token <token>
7474
```
7575

76+
### PlatformAudio
77+
78+
The `platform_audio` examples show microphone capture and speaker playout using
79+
WebRTC's platform Audio Device Module:
80+
81+
```bash
82+
./build/platform_audio/player/PlatformAudioPlayer <ws-url> <player-token>
83+
./build/platform_audio/sender/PlatformAudioSender <ws-url> <sender-token>
84+
```
85+
7686
### Supported platforms
7787

7888
Prebuilt SDKs are downloaded automatically for:
7989
* Windows: x64
8090
* macOS: x64, arm64 (Apple Silicon)
8191
* Linux: x64
8292

83-
If no matching SDK is available for your platform, CMake configuration will fail with a clear error.
93+
If no matching SDK is available for your platform, CMake configuration will fail with a clear error.

basic_room/main.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ int main(int argc, char* argv[]) {
132132

133133
std::shared_ptr<LocalTrackPublication> audioPub;
134134
try {
135-
room->localParticipant()->publishTrack(audioTrack, audioOpts);
135+
auto lp = room->localParticipant().lock();
136+
if (!lp) throw std::runtime_error("local participant unavailable");
137+
lp->publishTrack(audioTrack, audioOpts);
136138
audioPub = audioTrack->publication();
137139
std::cout << "Published audio: sid=" << audioPub->sid() << "\n";
138140
} catch (const std::exception& e) {
@@ -151,7 +153,9 @@ int main(int argc, char* argv[]) {
151153

152154
std::shared_ptr<LocalTrackPublication> videoPub;
153155
try {
154-
room->localParticipant()->publishTrack(videoTrack, videoOpts);
156+
auto lp = room->localParticipant().lock();
157+
if (!lp) throw std::runtime_error("local participant unavailable");
158+
lp->publishTrack(videoTrack, videoOpts);
155159
videoPub = videoTrack->publication();
156160
std::cout << "Published video: sid=" << videoPub->sid() << "\n";
157161
} catch (const std::exception& e) {
@@ -179,8 +183,10 @@ int main(int argc, char* argv[]) {
179183

180184
// Best-effort unpublish
181185
try {
182-
if (audioPub) room->localParticipant()->unpublishTrack(audioPub->sid());
183-
if (videoPub) room->localParticipant()->unpublishTrack(videoPub->sid());
186+
if (auto lp = room->localParticipant().lock()) {
187+
if (audioPub) lp->unpublishTrack(audioPub->sid());
188+
if (videoPub) lp->unpublishTrack(videoPub->sid());
189+
}
184190
} catch (...) {
185191
}
186192

hello_livekit/receiver/main.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
/// LIVEKIT_URL, LIVEKIT_RECEIVER_TOKEN, LIVEKIT_SENDER_IDENTITY
2626

2727
#include <atomic>
28-
#include <cassert>
2928
#include <chrono>
3029
#include <csignal>
3130
#include <cstdlib>
3231
#include <iostream>
32+
#include <stdexcept>
3333
#include <thread>
3434

3535
#include "livekit/livekit.h"
@@ -83,8 +83,10 @@ int main(int argc, char* argv[]) {
8383
return 1;
8484
}
8585

86-
LocalParticipant* lp = room->localParticipant();
87-
assert(lp);
86+
auto lp = room->localParticipant().lock();
87+
if (!lp) {
88+
throw std::runtime_error("[receiver] local participant is null");
89+
}
8890

8991
std::cout << "[info] [receiver] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name
9092
<< "'; subscribing to sender identity='" << sender_identity << "'\n";

hello_livekit/sender/main.cpp

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@
2424
/// LIVEKIT_URL, LIVEKIT_SENDER_TOKEN
2525

2626
#include <atomic>
27-
#include <cassert>
2827
#include <chrono>
2928
#include <csignal>
3029
#include <cstdlib>
3130
#include <iomanip>
3231
#include <iostream>
3332
#include <sstream>
33+
#include <stdexcept>
3434
#include <thread>
3535

3636
#include "livekit/livekit.h"
@@ -84,27 +84,33 @@ int main(int argc, char* argv[]) {
8484
return 1;
8585
}
8686

87-
LocalParticipant* lp = room->localParticipant();
88-
assert(lp);
87+
std::shared_ptr<LocalDataTrack> data_track;
88+
std::shared_ptr<LocalVideoTrack> video_track;
89+
std::shared_ptr<VideoSource> video_source;
90+
{
91+
auto lp = room->localParticipant().lock();
92+
if (!lp) {
93+
throw std::runtime_error("[sender] local participant is null");
94+
}
8995

90-
std::cout << "[info] [sender] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name
91-
<< "' — pass this identity to HelloLivekitReceiver\n";
96+
std::cout << "[info] [sender] Connected as identity='" << lp->identity() << "' room='" << room->roomInfo().name
97+
<< "' — pass this identity to HelloLivekitReceiver\n";
9298

93-
auto video_source = std::make_shared<VideoSource>(kWidth, kHeight);
99+
video_source = std::make_shared<VideoSource>(kWidth, kHeight);
94100

95-
std::shared_ptr<LocalVideoTrack> video_track =
96-
lp->publishVideoTrack(kVideoTrackName, video_source, TrackSource::SOURCE_CAMERA);
101+
video_track = lp->publishVideoTrack(kVideoTrackName, video_source, TrackSource::SOURCE_CAMERA);
97102

98-
auto publish_result = lp->publishDataTrack(kDataTrackName);
99-
if (!publish_result) {
100-
const auto& error = publish_result.error();
101-
std::cerr << "[error] Failed to publish data track: code=" << static_cast<std::uint32_t>(error.code)
102-
<< " message=" << error.message << "\n";
103-
room.reset();
104-
livekit::shutdown();
105-
return 1;
103+
auto publish_result = lp->publishDataTrack(kDataTrackName);
104+
if (!publish_result) {
105+
const auto& error = publish_result.error();
106+
std::cerr << "[error] Failed to publish data track: code=" << static_cast<std::uint32_t>(error.code)
107+
<< " message=" << error.message << "\n";
108+
room.reset();
109+
livekit::shutdown();
110+
return 1;
111+
}
112+
data_track = publish_result.value();
106113
}
107-
std::shared_ptr<LocalDataTrack> data_track = publish_result.value();
108114

109115
const auto t0 = std::chrono::steady_clock::now();
110116
std::uint64_t count = 0;

ping_pong/ping/main.cpp

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919
/// identity is `ping`.
2020

2121
#include <atomic>
22-
#include <cassert>
2322
#include <csignal>
2423
#include <cstdint>
2524
#include <exception>
2625
#include <iostream>
2726
#include <memory>
2827
#include <mutex>
2928
#include <optional>
29+
#include <stdexcept>
3030
#include <string>
3131
#include <thread>
3232
#include <unordered_map>
@@ -98,24 +98,29 @@ int main(int argc, char* argv[]) {
9898
return 1;
9999
}
100100

101-
LocalParticipant* local_participant = room->localParticipant();
102-
assert(local_participant);
101+
std::shared_ptr<LocalDataTrack> ping_track;
102+
{
103+
auto local_participant = room->localParticipant().lock();
104+
if (!local_participant) {
105+
throw std::runtime_error("[ping] local participant is null");
106+
}
103107

104-
std::cout << "[info] ping connected as identity='" << local_participant->identity() << "' room='"
105-
<< room->roomInfo().name << "'\n";
108+
std::cout << "[info] ping connected as identity='" << local_participant->identity() << "' room='"
109+
<< room->roomInfo().name << "'\n";
106110

107-
auto publish_result = local_participant->publishDataTrack(ping_pong::kPingTrackName);
108-
if (!publish_result) {
109-
const auto& error = publish_result.error();
110-
std::cerr << "[error] Failed to publish ping data track: code=" << static_cast<std::uint32_t>(error.code)
111-
<< " message=" << error.message << "\n";
112-
room->setDelegate(nullptr);
113-
room.reset();
114-
livekit::shutdown();
115-
return 1;
111+
auto publish_result = local_participant->publishDataTrack(ping_pong::kPingTrackName);
112+
if (!publish_result) {
113+
const auto& error = publish_result.error();
114+
std::cerr << "[error] Failed to publish ping data track: code=" << static_cast<std::uint32_t>(error.code)
115+
<< " message=" << error.message << "\n";
116+
room->setDelegate(nullptr);
117+
room.reset();
118+
livekit::shutdown();
119+
return 1;
120+
}
121+
ping_track = publish_result.value();
116122
}
117123

118-
std::shared_ptr<LocalDataTrack> ping_track = publish_result.value();
119124
std::unordered_map<std::uint64_t, ping_pong::PingMessage> sent_messages;
120125
std::mutex sent_messages_mutex;
121126

ping_pong/pong/main.cpp

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
/// on the "pong" data track. Use a token whose identity is `pong`.
1919

2020
#include <atomic>
21-
#include <cassert>
2221
#include <csignal>
2322
#include <cstdint>
2423
#include <exception>
2524
#include <iostream>
2625
#include <memory>
2726
#include <optional>
27+
#include <stdexcept>
2828
#include <string>
2929
#include <thread>
3030
#include <vector>
@@ -77,25 +77,30 @@ int main(int argc, char* argv[]) {
7777
return 1;
7878
}
7979

80-
LocalParticipant* local_participant = room->localParticipant();
81-
assert(local_participant);
82-
83-
std::cout << "[info] pong connected as identity='" << local_participant->identity() << "' room='"
84-
<< room->roomInfo().name << "'\n";
85-
86-
auto publish_result = local_participant->publishDataTrack(ping_pong::kPongTrackName);
87-
if (!publish_result) {
88-
const auto& error = publish_result.error();
89-
std::cerr << "[error] Failed to publish pong data track: code=" << static_cast<std::uint32_t>(error.code)
90-
<< " message=" << error.message << "\n";
91-
room->setDelegate(nullptr);
92-
room.reset();
93-
livekit::shutdown();
94-
return 1;
80+
std::shared_ptr<LocalDataTrack> pong_track;
81+
{
82+
// limit the scope of the local participant
83+
auto local_participant = room->localParticipant().lock();
84+
if (!local_participant) {
85+
throw std::runtime_error("[pong] local participant is null");
86+
}
87+
88+
std::cout << "[info] pong connected as identity='" << local_participant->identity() << "' room='"
89+
<< room->roomInfo().name << "'\n";
90+
91+
auto publish_result = local_participant->publishDataTrack(ping_pong::kPongTrackName);
92+
if (!publish_result) {
93+
const auto& error = publish_result.error();
94+
std::cerr << "[error] Failed to publish pong data track: code=" << static_cast<std::uint32_t>(error.code)
95+
<< " message=" << error.message << "\n";
96+
room->setDelegate(nullptr);
97+
room.reset();
98+
livekit::shutdown();
99+
return 1;
100+
}
101+
pong_track = publish_result.value();
95102
}
96103

97-
std::shared_ptr<LocalDataTrack> pong_track = publish_result.value();
98-
99104
const auto callback_id = room->addOnDataFrameCallback(
100105
ping_pong::kPingParticipantIdentity, ping_pong::kPingTrackName,
101106
[pong_track](const std::vector<std::uint8_t>& payload, std::optional<std::uint64_t> /*user_timestamp*/) {

platform_audio/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2026 LiveKit, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
add_subdirectory(sender)
16+
add_subdirectory(player)

platform_audio/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# PlatformAudio
2+
3+
These examples demonstrate the platform Audio Device Module path:
4+
5+
- `PlatformAudioSender`: publishes microphone audio with echo cancellation, noise suppression, and auto gain control.
6+
- `PlatformAudioPlayer`: joins the same room and plays subscribed remote audio through the platform output device.
7+
8+
Build the collection, then run the player and sender with different participant tokens for the same room:
9+
10+
```bash
11+
./build/platform_audio/player/PlatformAudioPlayer <ws-url> <player-token>
12+
./build/platform_audio/sender/PlatformAudioSender <ws-url> <sender-token>
13+
```
14+
15+
Environment fallbacks:
16+
17+
```bash
18+
export LIVEKIT_URL=wss://your-livekit-host
19+
export LIVEKIT_PLAYER_TOKEN=<player-token>
20+
export LIVEKIT_SENDER_TOKEN=<sender-token>
21+
```
22+
23+
## Test environments
24+
25+
The sender captures the mic and runs the AEC/NS/AGC front-end; the player drives
26+
hardware playout. The acoustic echo cancellation (AEC) reference is per Audio
27+
Device Module (ADM), so the sender can only cancel playout from *its own* ADM.
28+
Pick an environment based on what you want to prove out.
29+
30+
| Environment | What it proves | Notes |
31+
|-------------|----------------|-------|
32+
| **Two machines** (sender on A, player on B) | End-to-end capture → publish → subscribe → hardware playout over the network, like a real call. | Closest to a real LiveKit call. For full duplex (both apps on both boxes), use headphones or separate rooms to avoid feedback. |
33+
| **One machine + headphones** | Mic capture and ADM playout both work on one box, with no acoustic feedback path. | Best for confirming device selection and round-trip latency in isolation. |
34+
| **Noise suppression (NS)** | Steady background noise is attenuated while speech passes. | Add a fan / AC hum / typing near the mic. A/B by setting `noise_suppression = false` in `sender/main.cpp`. |
35+
| **Auto gain control (AGC)** | Quiet vs. loud / near vs. far speech is normalized on the player side. | A/B by setting `auto_gain_control = false` in `sender/main.cpp`. |
36+
| **`prefer_hardware = true`** | Platform hardware voice processing engages (e.g. macOS voice-processing I/O). | Set in the sender options; compare CPU and audio character vs. the software path. |
37+
| **Device / hot-plug sanity** | `recordingDevices()` / `playoutDevices()` reflect attached hardware and route correctly. | Plug/unplug a USB or Bluetooth mic/headset before launch and check the startup device logs. |
38+
39+
> **AEC caveat:** these split sender/player apps cannot demonstrate AEC against
40+
> each other on one machine over open speakers — the sender's AEC has no
41+
> reference to the player's separate ADM, so the speaker output is treated as
42+
> external sound and you get an echo/feedback loop. Genuine AEC requires a
43+
> single application that both plays remote audio and captures the mic through
44+
> the *same* ADM. Use headphones to avoid feedback with these examples.
Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
add_library(simple_joystick_sender_support STATIC
16-
json_utils.cpp
17-
json_utils.h
18-
utils.cpp
19-
utils.h
20-
)
21-
22-
target_include_directories(simple_joystick_sender_support PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
23-
target_link_libraries(simple_joystick_sender_support PUBLIC nlohmann_json::nlohmann_json)
24-
25-
add_executable(SimpleJoystickSender
15+
add_executable(PlatformAudioPlayer
2616
main.cpp
2717
)
2818

29-
target_include_directories(SimpleJoystickSender PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
30-
target_link_libraries(SimpleJoystickSender PRIVATE simple_joystick_sender_support ${LIVEKIT_CORE_TARGET})
19+
target_include_directories(PlatformAudioPlayer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
20+
target_link_libraries(PlatformAudioPlayer PRIVATE ${LIVEKIT_CORE_TARGET})
3121

32-
livekit_copy_windows_runtime_dlls(SimpleJoystickSender)
22+
livekit_copy_windows_runtime_dlls(PlatformAudioPlayer)

0 commit comments

Comments
 (0)