[Android] AudioRecorder reopens a disconnected input stream with no limit: a re-route on every open (Samsung, concurrent capture) loops hundreds of times a minute and records nothing

Aperta
#1,416 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
android, cpp, react-native

Direzione di ricerca

Start with packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp, following onErrorAfterClose to restartCapture() to understand the repeated reopen path. Review how recorder state and stop, pause, and resume are handled before deciding how bounded retries and capture-down reporting fit the API. Done means repeated disconnects no longer cause an unbounded reopen loop, and the recording session can still be controlled and finalized while retries wait.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Description

On Android, AudioRecorder reopens its input stream on every ErrorDisconnected with no delay or limit (AndroidAudioRecorder::onErrorAfterClose → restartCapture()). When the system moves the stream to a different input device right after every open, this becomes a tight open/disconnect loop: the recorder reopens hundreds of times a minute, writes no audio, and nothing reaches JS (no error, no event), so the app keeps showing an active recording that saves nothing.

We hit it on Samsung phones when the app runs a second capture client at the same time (an RTC SDK, Agora, capturing with MIC) and the recorder uses the default input preset (VOICE_RECOGNITION):

  1. The recorder's stream opens on Built-In Mic.
  2. ~20 ms later Samsung's audio policy re-routes the shared input to Built-In 2 Mic (AUDIO_DEVICE_IN_2MIC, a Samsung vendor device), because a shared input is routed for the highest-priority active source and MIC outranks VOICE_RECOGNITION.
  3. AAudio turns any device change on a running stream into a disconnect (AudioStreamLegacy::onAudioDeviceUpdate — same rule in Android 13–16; the MMAP path disconnects on a device change too).
  4. restartCapture() reopens immediately into the same re-route. Repeat.
APM_AudioPolicyManager: getInputForAttr() … Source: AUDIO_SOURCE_VOICE_RECOGNITION … selectedDeviceId 19
AudioStreamLegacy: onAudioDeviceUpdate() request DISCONNECT in data callback, device 19 => 22
AudioRecord: processAudioBuffer(): EVENT_MORE_DATA requested 7680 bytes but callback returned -1 bytes
AAudio: AAudioStream_requestStop(s#1) called
… (repeats ~740×/min for the whole session)

Observed on a Galaxy S24 Ultra (Android 16) and a Galaxy S20 FE (Android 13). Pixels map both sources to the same mic, so they don't reproduce it.

Where this was reproduced vs. checked: we reproduced it on our fork, which is based on 0.13.6 and opens the input Shared / PerformanceMode::None. In 1.0.0 (ce354a54) and current main (96afff59) the same unbounded path is still there: onErrorAfterClose calls restartCapture() on every ErrorDisconnected, and restartCapture() has no delay or attempt limit. Those versions open Exclusive, but with another capture client active the input falls back to shared, and the disconnect rule applies on both paths, so we expect the same loop. We haven't run 1.0.0 on the Samsung devices yet.

Workaround: pass androidInputPreset: 'generic' (MIC) so both clients share one device. We confirmed this on the S20 FE: 0 disconnects over an 8.5-minute recording with the RTC SDK active.

Suggested fix: bound the reclaim. For example, treat a stream that dies within ~1 s of starting as a fast disconnect, reopen immediately for the first couple, then back off exponentially (we use 250 ms doubling to a 5 s cap) and reset once a reopened stream survives. Also tell JS that capture is down while backing off: an error, or a recorder event or state change, whichever fits the library's API. Keep the session's state (recording/paused, outputs) while it waits, so stop() during the wait still finalizes the file and pause()/resume() still apply. We have this working in our fork (gtest-covered policy, waits on oboe's error thread on a condition variable that stop()/start() cancel). We're happy to open a PR against main if you agree with the approach and tell us how you'd like the "capture down" state surfaced.

Steps to reproduce
  1. On a Samsung Android phone, start an in-app capture client with MIC (e.g. join an Agora channel as broadcaster with AudioScenarioGameStreaming).
  2. While it is capturing, start an AudioRecorder with file output and no androidInputPreset (default VOICE_RECOGNITION).
  3. Watch adb logcat | grep -E "onAudioDeviceUpdate|AAudioStream_requestStop": a DISCONNECT … X => Y and a stop on every open, continuously.
  4. Stop the recorder: the file holds well under a second of audio, captured only after the other client released the mic.
Snack or a link to a repository

No public repro yet. It needs a Samsung device and a second capture client in the same app; the code path is packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp (onErrorAfterClose → restartCapture). We can share a minimal repro app if that helps.

React Native Audio API version

Reproduced on a 0.13.6-based fork; unbounded restart confirmed by code in 1.0.0 and main (96afff59).

React Native version

0.88.1

Platforms

Android

JavaScript runtime

Hermes

Workflow

Expo Dev Client

Architecture

Fabric (New Architecture)

Build type

Debug and release

Device

Real device

Device model

Samsung Galaxy S24 Ultra (SM-S928U1, Android 16); Samsung Galaxy S20 FE 5G (SM-G781U1, Android 13)

Lingua principale
C++
Stelle
856
Fork
100
Merge medio
2g 1h
PR unite (30g)
34

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di software-mansion/react-native-audio-api

Tutte le issue di software-mansion/react-native-audio-api

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.