Repository navigation
Description
Audio playout never starts after the first PeerConnection is closed when one AudioDeviceModule is reused
Summary
When a single PeerConnectionFactory + AudioDeviceModule is reused across connections, the
first connection works normally, but every subsequent connection waits exactly 10 seconds
inside setRemoteDescription and never plays audio:
ERROR (audio_device_pulse_linux.cc:1199): failed to activate playout
Environment
- Ubuntu 22.04 / ARM64 (aarch64), PulseAudio backend
- Java 25
- webrtc-java 0.18.0, 0.19.0 and 0.20.0 all reproduce
- webrtc-java 0.14.0 – 0.17.0 do not reproduce
- Media: audio only,
RECV_ONLYtransceiver, remote Answer over HTTPS signaling
Steps to reproduce
AudioDeviceModule adm = new AudioDeviceModule(); // platform default (PulseAudio)
adm.setPlayoutDevice(playoutDevice);
adm.initPlayout();
PeerConnectionFactory factory = new PeerConnectionFactory(adm);
for (int i = 0; i < 5; i++) {
RTCPeerConnection pc = factory.createPeerConnection(new RTCConfiguration(), observer);
RTCRtpTransceiverInit init = new RTCRtpTransceiverInit();
init.direction = RTCRtpTransceiverDirection.RECV_ONLY;
AudioTrackSource src = factory.createAudioSource(new AudioOptions());
AudioTrack track = factory.createAudioTrack("dummy", src);
pc.addTransceiver(track, init);
// createOffer -> setLocalDescription -> exchange SDP via signaling
// -> setRemoteDescription(answer)
// The 1st iteration completes in a few ms, every later one takes 10 s.
pc.close();
}Observed timings (same process, same factory)
| Connection | setRemoteDescription success |
Playback |
|---|---|---|
| 1st | 5 ms | started |
| 2nd | 10004 ms | never started |
| 3rd | 10005 ms | never started |
| 4th | 10010 ms | never started |
While the 10 s delay is pending, close() on that connection also blocks — it waits for the
remaining part of the timeout (measured 6239 ms and 7001 ms).
Root cause
- Fix JNI memory-safety and thread-safety bugs in the native bridge #283 (0.18.0) made
RTCPeerConnection.close()callpc->Release(). - Destroying the last PeerConnection drops
ConnectionContext::media_engine_reference_count_
to 0, soReleaseMediaEngine()callsmedia_engine_->Terminate()→
WebRtcVoiceEngine::Terminate()→adm()->Terminate(). - The next PeerConnection triggers
AddRefMediaEngine()→media_engine_->Init()→
WebRtcVoiceEngine::Init()→adm_helpers::Init(adm)→adm->Init(). AudioDeviceLinuxPulse::Init()re-spawnswebrtc_audio_module_play_threadbut never resets
quit_, whichTerminate()set totrue.quit_is written in only two places: the
constructor (false) andTerminate()(true).PlayThreadProcess()hitsif (quit_) return false;and the thread exits._playStartEventis
therefore never signalled, andStartPlayout()waits its full 10 s timeout, logs
failed to activate playoutand returns -1.
Before 0.18.0, close() never released the PeerConnection, so the media engine reference count
never reached 0 and the ADM was never terminated — which is why 0.14.0 – 0.17.0 accidentally avoid
the bug (at the cost of leaking the PeerConnection on every cycle).
Evidence
audio_device_pulse_linux.cc:172: failed to open X displayis logged from inside
AudioDeviceLinuxPulse::Init(). It appears only from the 2nd connection on, proving the ADM
was terminated and re-initialised between connections.Can't query latencyis only emitted once a play stream exists. It appears only on the 1st
connection.- Line numbers in the native log (
172,1199,1872) matchbranch-heads/7977exactly, i.e. the
library was built from the pinned WebRTC revision.
Workaround (verified on device, 5 consecutive connections)
Keep one idle PeerConnection alive per PeerConnectionFactory:
this.keepAlivePeerConnection =
factory.createPeerConnection(new RTCConfiguration(), observer); // never closed until shutdownWith the reference count pinned above 0, Terminate() never runs, the ADM stays initialised, and
every connection applies the Answer in 3–8 ms with playback starting normally.
Note: the keep-alive connection also prevents adm_helpers::Init() from resetting the playout
device to index 0 on every re-initialisation.
Suggested fix
- libwebrtc: reset
quit_ = falseinAudioDeviceLinuxPulse::Init(). - webrtc-java: keep the media engine referenced for the factory's lifetime, or avoid
adm->Terminate()when the last PeerConnection goes away.
Activity
devopvoid commentedon Oct 31, 2026
Confirmed on Linux x86_64 with PipeWire/PulseAudio: the second connection stalled for 10 seconds. PR #501 resets quit_ before restarting the audio threads. Thanks for the detailed report!