diff --git a/webrtc-jni/src/main/cpp/include/media/audio/CustomAudioSource.h b/webrtc-jni/src/main/cpp/include/media/audio/CustomAudioSource.h index 56679726..3a81c5de 100644 --- a/webrtc-jni/src/main/cpp/include/media/audio/CustomAudioSource.h +++ b/webrtc-jni/src/main/cpp/include/media/audio/CustomAudioSource.h @@ -19,6 +19,7 @@ #include "api/media_stream_interface.h" #include "rtc_base/ref_counted_object.h" +#include "rtc_base/synchronization/mutex.h" #include "media/SyncClock.h" @@ -50,9 +51,13 @@ namespace jni void SetAudioCaptureDelay(int64_t delay_us); private: + // Guards sinks_ and audio_capture_delay_us_ against concurrent + // AddSink()/RemoveSink() (called by WebRTC's own signaling/worker + // thread as tracks attach/detach) racing with PushAudioData() and + // SetAudioCaptureDelay() (called by the application's own threads). + webrtc::Mutex mutex_; std::vector sinks_; std::shared_ptr clock_; - //webrtc::CriticalSection crit_; std::atomic total_samples_captured_; int64_t audio_capture_delay_us_; }; diff --git a/webrtc-jni/src/main/cpp/src/media/audio/CustomAudioSource.cpp b/webrtc-jni/src/main/cpp/src/media/audio/CustomAudioSource.cpp index 0b90cbd6..b0b67aea 100644 --- a/webrtc-jni/src/main/cpp/src/media/audio/CustomAudioSource.cpp +++ b/webrtc-jni/src/main/cpp/src/media/audio/CustomAudioSource.cpp @@ -24,14 +24,14 @@ namespace jni void CustomAudioSource::AddSink(webrtc::AudioTrackSinkInterface * sink) { - //webrtc::CritScope lock(&crit_); + webrtc::MutexLock lock(&mutex_); sinks_.push_back(sink); } void CustomAudioSource::RemoveSink(webrtc::AudioTrackSinkInterface * sink) { - //webrtc::CritScope lock(&crit_); + webrtc::MutexLock lock(&mutex_); sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); } @@ -50,7 +50,7 @@ namespace jni int sample_rate, size_t number_of_channels, size_t number_of_frames) { - //webrtc::CritScope lock(&crit_); + webrtc::MutexLock lock(&mutex_); // Calculate absolute capture time int64_t timestamp_us = clock_->GetTimestampUs(); @@ -77,6 +77,8 @@ namespace jni void CustomAudioSource::SetAudioCaptureDelay(int64_t delay_us) { + webrtc::MutexLock lock(&mutex_); + audio_capture_delay_us_ = delay_us; } } \ No newline at end of file diff --git a/webrtc/src/test/java/dev/onvoid/webrtc/media/audio/CustomAudioSourceTest.java b/webrtc/src/test/java/dev/onvoid/webrtc/media/audio/CustomAudioSourceTest.java index 26a96ab5..1a0033a9 100644 --- a/webrtc/src/test/java/dev/onvoid/webrtc/media/audio/CustomAudioSourceTest.java +++ b/webrtc/src/test/java/dev/onvoid/webrtc/media/audio/CustomAudioSourceTest.java @@ -83,6 +83,37 @@ void pushAudioData() { testAudioFormat(16, 48000, 2, 480); } + @Test + void concurrentAddRemoveSinkDoesNotCrash() throws InterruptedException { + // Regression test: AddSink()/RemoveSink() (called here from this thread, + // mirroring the internal thread WebRTC uses as tracks attach/detach) used + // to race unsynchronized with PushAudioData() (the application's capture + // thread) over the native sinks_ vector. + AudioTrack audioTrack = factory.createAudioTrack("audioTrack", customAudioSource); + AudioTrackSink sink = (data, bitsPerSample, sampleRate, channels, frames) -> { }; + + byte[] audioData = new byte[480 * 2 * 2]; // 10ms of 48kHz stereo 16-bit audio + + AtomicBoolean running = new AtomicBoolean(true); + + Thread pushThread = new Thread(() -> { + while (running.get()) { + customAudioSource.pushAudio(audioData, 16, 48000, 2, 480); + } + }); + pushThread.start(); + + for (int i = 0; i < 5000; i++) { + audioTrack.addSink(sink); + audioTrack.removeSink(sink); + } + + running.set(false); + pushThread.join(5000); + + audioTrack.dispose(); + } + @Test void pushAudioWithDifferentFormats() { testAudioFormat(8, 8000, 1, 80); // 8-bit, 8kHz, mono, 10ms