Skip to content

Audio playout never starts after the first PeerConnection is closed when one AudioDeviceModule is reused #500

Closed

Description

@RabbitHareLu

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_ONLY transceiver, 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

  1. Fix JNI memory-safety and thread-safety bugs in the native bridge #283 (0.18.0) made RTCPeerConnection.close() call pc->Release().
  2. Destroying the last PeerConnection drops ConnectionContext::media_engine_reference_count_
    to 0, so ReleaseMediaEngine() calls media_engine_->Terminate() →
    WebRtcVoiceEngine::Terminate() → adm()->Terminate().
  3. The next PeerConnection triggers AddRefMediaEngine() → media_engine_->Init() →
    WebRtcVoiceEngine::Init() → adm_helpers::Init(adm) → adm->Init().
  4. AudioDeviceLinuxPulse::Init() re-spawns webrtc_audio_module_play_thread but never resets
    quit_
    , which Terminate() set to true. quit_ is written in only two places: the
    constructor (false) and Terminate() (true).
  5. PlayThreadProcess() hits if (quit_) return false; and the thread exits. _playStartEvent is
    therefore never signalled, and StartPlayout() waits its full 10 s timeout, logs
    failed to activate playout and 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 display is 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 latency is only emitted once a play stream exists. It appears only on the 1st
    connection
    .
  • Line numbers in the native log (172, 1199, 1872) match branch-heads/7977 exactly, 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 shutdown

With 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_ = false in AudioDeviceLinuxPulse::Init().
  • webrtc-java: keep the media engine referenced for the factory's lifetime, or avoid
    adm->Terminate() when the last PeerConnection goes away.

The above content was generated by AI based on AI analysis and hands-on testing. It is provided for reference only, and we hope it can be helpful to the project.

Activity

  1. devopvoid commented on Oct 31, 2026

    @devopvoid
    Owner

    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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions