|
16 | 16 |
|
17 | 17 | #include <chrono> |
18 | 18 | #include <condition_variable> |
| 19 | +#include <cstdint> |
19 | 20 | #include <memory> |
20 | 21 | #include <mutex> |
21 | 22 | #include <set> |
@@ -218,4 +219,80 @@ TEST_F(PlatformAudioIntegrationTest, MultipleSourcesFromOneManagerPublish) { |
218 | 219 | EXPECT_TRUE(both_subscribed) << "Receiver did not subscribe to both platform audio tracks"; |
219 | 220 | } |
220 | 221 |
|
| 222 | +// Audio captured by the platform Audio Device Module must actually stream to a |
| 223 | +// remote participant as decoded frames, not merely produce a subscribed track. |
| 224 | +// PlatformAudioSource captures the real microphone (silence on headless |
| 225 | +// runners), so this verifies frames *flow* end-to-end without asserting on |
| 226 | +// their content. |
| 227 | +TEST_F(PlatformAudioIntegrationTest, PlatformAudioFramesReachRemote) { |
| 228 | + EXPECT_TRUE(config_.available) << "Missing integration configuration"; |
| 229 | + |
| 230 | + std::unique_ptr<PlatformAudio> platform_audio; |
| 231 | + EXPECT_NO_THROW(platform_audio = std::make_unique<PlatformAudio>()); |
| 232 | + |
| 233 | + RoomOptions options; |
| 234 | + options.auto_subscribe = true; |
| 235 | + |
| 236 | + PlatformTrackState receiver_state; |
| 237 | + PlatformTrackCollectorDelegate receiver_delegate(receiver_state); |
| 238 | + |
| 239 | + auto receiver_room = std::make_unique<Room>(); |
| 240 | + receiver_room->setDelegate(&receiver_delegate); |
| 241 | + ASSERT_TRUE(receiver_room->connect(config_.url, config_.token_b, options)) << "Receiver failed to connect"; |
| 242 | + |
| 243 | + auto sender_room = std::make_unique<Room>(); |
| 244 | + ASSERT_TRUE(sender_room->connect(config_.url, config_.token_a, options)) << "Sender failed to connect"; |
| 245 | + |
| 246 | + const std::string sender_identity = lockLocalParticipant(*sender_room)->identity(); |
| 247 | + |
| 248 | + const auto source = platform_audio->createAudioSource(); |
| 249 | + ASSERT_NE(source, nullptr); |
| 250 | + |
| 251 | + const std::string track_name = "platform-mic-frames"; |
| 252 | + const auto track = LocalAudioTrack::createLocalAudioTrack(track_name, source); |
| 253 | + ASSERT_NE(track, nullptr); |
| 254 | + |
| 255 | + // A few hundred ms of audio (10ms frames) is plenty to confirm the media path |
| 256 | + // is live without making the test slow. |
| 257 | + constexpr int kRequiredFrames = 10; |
| 258 | + constexpr auto kFrameTimeout = 20s; |
| 259 | + |
| 260 | + std::mutex frame_mutex; |
| 261 | + std::condition_variable frame_cv; |
| 262 | + int received_frames = 0; |
| 263 | + |
| 264 | + // The reader thread is only started when the subscription event fires and a |
| 265 | + // matching callback is already registered, so register before publishing. |
| 266 | + receiver_room->setOnAudioFrameCallback(sender_identity, track_name, [&](const AudioFrame& frame) { |
| 267 | + if (frame.totalSamples() == 0) { |
| 268 | + return; |
| 269 | + } |
| 270 | + { |
| 271 | + std::lock_guard<std::mutex> lock(frame_mutex); |
| 272 | + ++received_frames; |
| 273 | + } |
| 274 | + frame_cv.notify_all(); |
| 275 | + }); |
| 276 | + |
| 277 | + TrackPublishOptions publish_options; |
| 278 | + publish_options.source = TrackSource::SOURCE_MICROPHONE; |
| 279 | + lockLocalParticipant(*sender_room)->publishTrack(track, publish_options); |
| 280 | + |
| 281 | + { |
| 282 | + std::unique_lock<std::mutex> lock(receiver_state.mutex); |
| 283 | + ASSERT_TRUE(receiver_state.cv.wait_for(lock, kSubscriptionTimeout, [&]() { |
| 284 | + return receiver_state.subscribed_audio_names.count(track_name) > 0; |
| 285 | + })) << "Receiver never subscribed to the platform audio track"; |
| 286 | + } |
| 287 | + |
| 288 | + bool frames_received = false; |
| 289 | + { |
| 290 | + std::unique_lock<std::mutex> lock(frame_mutex); |
| 291 | + frames_received = frame_cv.wait_for(lock, kFrameTimeout, [&]() { return received_frames >= kRequiredFrames; }); |
| 292 | + } |
| 293 | + EXPECT_TRUE(frames_received) << "Receiver did not get platform audio frames from the remote"; |
| 294 | + |
| 295 | + receiver_room->clearOnAudioFrameCallback(sender_identity, track_name); |
| 296 | +} |
| 297 | + |
221 | 298 | } // namespace livekit::test |
0 commit comments