Adding thread checker for audio record on native APIs Implemented thread safety checks in WebRtcAudioRecord.java using ThreadUtils.ThreadChecker, matching the pattern in WebRtcAudioTrack.java. The checker is detached in the constructor and guards @CalledByNative control APIs (initRecording, startRecording, stopRecording, enableBuiltInAEC, enableBuiltInNS) to ensure they are called consistently from the same C++ worker thread. To prevent crashes, Java-only public APIs (such as setPreferredDevice and setMicrophoneMute) and read-only query APIs remain unguarded as they are expected to be called from application or stats threads. Bug: webrtc:524395191 Change-Id: I432b9f6f8da1ead5e3ff021dfef4c6e12ef6557f Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/484740 Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org> Reviewed-by: Henrik Andreassson <henrika@webrtc.org> Commit-Queue: Tim Na <natim@google.com> Cr-Commit-Position: refs/heads/main@{#48068}
diff --git a/sdk/android/src/java/org/webrtc/audio/WebRtcAudioRecord.java b/sdk/android/src/java/org/webrtc/audio/WebRtcAudioRecord.java index 510cc04..26a1bfa 100644 --- a/sdk/android/src/java/org/webrtc/audio/WebRtcAudioRecord.java +++ b/sdk/android/src/java/org/webrtc/audio/WebRtcAudioRecord.java
@@ -107,6 +107,7 @@ private final @Nullable SamplesReadyCallback audioSamplesReadyCallback; private final boolean isAcousticEchoCancelerSupported; private final boolean isNoiseSuppressorSupported; + private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.ThreadChecker(); /** * Audio thread which keeps calling ByteBuffer.read() waiting for audio @@ -207,6 +208,7 @@ @Nullable AudioRecordStateCallback stateCallback, @Nullable SamplesReadyCallback audioSamplesReadyCallback, boolean isAcousticEchoCancelerSupported, boolean isNoiseSuppressorSupported) { + threadChecker.detachThread(); if (isAcousticEchoCancelerSupported && !WebRtcAudioEffects.isAcousticEchoCancelerSupported()) { throw new IllegalArgumentException("HW AEC not supported"); } @@ -264,18 +266,27 @@ @CalledByNative private boolean enableBuiltInAEC(boolean enable) { + threadChecker.checkIsOnValidThread(); Logging.d(TAG, "enableBuiltInAEC(" + enable + ")"); return effects.setAEC(enable); } @CalledByNative private boolean enableBuiltInNS(boolean enable) { + threadChecker.checkIsOnValidThread(); Logging.d(TAG, "enableBuiltInNS(" + enable + ")"); return effects.setNS(enable); } @CalledByNative private int initRecording(int sampleRate, int channels) { + try { + threadChecker.checkIsOnValidThread(); + } catch (IllegalStateException e) { + reportWebRtcAudioRecordInitError("threadChecker.checkIsOnValidThread failed: " + + e.getMessage()); + return -1; + } Logging.d(TAG, "initRecording(sampleRate=" + sampleRate + ", channels=" + channels + ")"); if (audioRecord != null) { reportWebRtcAudioRecordInitError("InitRecording called twice without StopRecording."); @@ -384,6 +395,7 @@ @CalledByNative private boolean startRecording() { + threadChecker.checkIsOnValidThread(); Logging.d(TAG, "startRecording"); assertTrue(audioRecord != null); assertTrue(audioThread == null); @@ -408,6 +420,7 @@ @CalledByNative private boolean stopRecording() { + threadChecker.checkIsOnValidThread(); Logging.d(TAG, "stopRecording"); assertTrue(audioThread != null); if (future != null) {