Skip to content

Commit 4d26f68

Browse files
platform_audio
1 parent bbf0fdf commit 4d26f68

8 files changed

Lines changed: 412 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ add_subdirectory(logging_levels/basic_usage logging_levels_basic_usage)
8989
add_subdirectory(logging_levels/custom_sinks logging_levels_custom_sinks)
9090
add_subdirectory(hello_livekit/sender hello_livekit_sender)
9191
add_subdirectory(hello_livekit/receiver hello_livekit_receiver)
92+
add_subdirectory(platform_audio)
9293
add_subdirectory(ping_pong/ping ping_pong_ping)
9394
add_subdirectory(ping_pong/pong ping_pong_pong)
9495
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.

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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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_executable(PlatformAudioPlayer
16+
main.cpp
17+
)
18+
19+
target_include_directories(PlatformAudioPlayer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
20+
target_link_libraries(PlatformAudioPlayer PRIVATE ${LIVEKIT_CORE_TARGET})
21+
22+
livekit_copy_windows_runtime_dlls(PlatformAudioPlayer)

platform_audio/player/main.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright 2026 LiveKit, Inc.
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+
/// Plays subscribed room audio using PlatformAudio.
18+
///
19+
/// Usage:
20+
/// PlatformAudioPlayer <ws-url> <player-token>
21+
///
22+
/// Or via environment variables:
23+
/// LIVEKIT_URL, LIVEKIT_PLAYER_TOKEN
24+
25+
#include <atomic>
26+
#include <chrono>
27+
#include <csignal>
28+
#include <cstdlib>
29+
#include <exception>
30+
#include <iostream>
31+
#include <memory>
32+
#include <string>
33+
#include <thread>
34+
35+
#include "livekit/livekit.h"
36+
37+
using namespace livekit;
38+
39+
namespace {
40+
41+
std::atomic<bool> g_running{true};
42+
43+
void handleSignal(int) { g_running.store(false); }
44+
45+
std::string getenvOrEmpty(const char* name) {
46+
const char* value = std::getenv(name);
47+
return value ? std::string(value) : std::string{};
48+
}
49+
50+
void printUsage() {
51+
std::cerr << "[error] Usage: PlatformAudioPlayer <ws-url> <player-token>\n"
52+
<< " or set LIVEKIT_URL and LIVEKIT_PLAYER_TOKEN\n";
53+
}
54+
55+
class PlayerDelegate final : public RoomDelegate {
56+
public:
57+
void onParticipantConnected(Room&, const ParticipantConnectedEvent& event) override {
58+
if (event.participant) {
59+
std::cout << "[info] [platform-audio-player] Participant connected identity='" << event.participant->identity()
60+
<< "'\n";
61+
}
62+
}
63+
64+
void onTrackSubscribed(Room&, const TrackSubscribedEvent& event) override {
65+
if (!event.track || event.track->kind() != TrackKind::KIND_AUDIO) {
66+
return;
67+
}
68+
69+
const std::string participant_identity = event.participant ? event.participant->identity() : std::string("unknown");
70+
const std::string publication_name = event.publication ? event.publication->name() : event.track->name();
71+
std::cout << "[info] [platform-audio-player] Playing audio track '" << publication_name
72+
<< "' from participant identity='" << participant_identity << "'\n";
73+
}
74+
75+
void onTrackSubscriptionFailed(Room&, const TrackSubscriptionFailedEvent& event) override {
76+
const std::string participant_identity = event.participant ? event.participant->identity() : std::string("unknown");
77+
std::cerr << "[warn] [platform-audio-player] Audio subscription failed for participant identity='"
78+
<< participant_identity << "' track_sid='" << event.track_sid << "'\n";
79+
}
80+
};
81+
82+
} // namespace
83+
84+
int main(int argc, char* argv[]) {
85+
std::string url = getenvOrEmpty("LIVEKIT_URL");
86+
std::string player_token = getenvOrEmpty("LIVEKIT_PLAYER_TOKEN");
87+
88+
if (argc >= 3) {
89+
url = argv[1];
90+
player_token = argv[2];
91+
}
92+
93+
if (url.empty() || player_token.empty()) {
94+
printUsage();
95+
return 1;
96+
}
97+
98+
std::signal(SIGINT, handleSignal);
99+
#ifdef SIGTERM
100+
std::signal(SIGTERM, handleSignal);
101+
#endif
102+
103+
livekit::initialize(livekit::LogLevel::Info);
104+
105+
try {
106+
PlatformAudio platform_audio;
107+
108+
auto playout_devices = platform_audio.playoutDevices();
109+
std::cout << "[info] [platform-audio-player] Playout devices: " << playout_devices.size() << "\n";
110+
for (const auto& device : playout_devices) {
111+
std::cout << " [" << device.index << "] " << device.name << " id=" << device.id << "\n";
112+
}
113+
114+
auto room = std::make_unique<Room>();
115+
PlayerDelegate delegate;
116+
room->setDelegate(&delegate);
117+
118+
RoomOptions options;
119+
options.auto_subscribe = true;
120+
options.dynacast = false;
121+
122+
if (!room->connect(url, player_token, options)) {
123+
std::cerr << "[error] [platform-audio-player] Failed to connect\n";
124+
room.reset();
125+
livekit::shutdown();
126+
return 1;
127+
}
128+
129+
if (auto local_participant = room->localParticipant().lock()) {
130+
std::cout << "[info] [platform-audio-player] Connected as identity='" << local_participant->identity()
131+
<< "' room='" << room->roomInfo().name << "'\n";
132+
} else {
133+
throw std::runtime_error("unable to lock local participant");
134+
}
135+
136+
std::cout << "[info] [platform-audio-player] Waiting for remote audio; Ctrl-C to exit\n";
137+
while (g_running.load()) {
138+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
139+
}
140+
141+
std::cout << "[info] [platform-audio-player] Disconnecting\n";
142+
room->setDelegate(nullptr);
143+
room.reset();
144+
} catch (const std::exception& error) {
145+
std::cerr << "[error] [platform-audio-player] " << error.what() << "\n";
146+
livekit::shutdown();
147+
return 1;
148+
}
149+
150+
livekit::shutdown();
151+
return 0;
152+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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_executable(PlatformAudioSender
16+
main.cpp
17+
)
18+
19+
target_include_directories(PlatformAudioSender PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
20+
target_link_libraries(PlatformAudioSender PRIVATE ${LIVEKIT_CORE_TARGET})
21+
22+
livekit_copy_windows_runtime_dlls(PlatformAudioSender)

0 commit comments

Comments
 (0)