Skip to content

feat: Bluetooth SCO support for microphone recording#857

Open
1d10t wants to merge 5 commits intotermux:masterfrom
1d10t:feat/bluetooth-sco-microphone
Open

feat: Bluetooth SCO support for microphone recording#857
1d10t wants to merge 5 commits intotermux:masterfrom
1d10t:feat/bluetooth-sco-microphone

Conversation

@1d10t
Copy link
Copy Markdown

@1d10t 1d10t commented Apr 11, 2026

Summary

  • MicRecorderAPI: when source=7 (VOICE_COMMUNICATION) is requested, automatically activates Bluetooth SCO before starting recording and tears it down on stop/error. Android 12+: setCommunicationDevice(), older: startBluetoothSco() + BroadcastReceiver waiting for SCO_AUDIO_STATE_CONNECTED.
  • AudioScoAPI: new API for standalone SCO channel management — enable/disable/status. Exposed as termux-audio-sco shell utility (see companion termux-api-package PR).
  • AndroidManifest: added BLUETOOTH, BLUETOOTH_CONNECT (Android 12+), MODIFY_AUDIO_SETTINGS permissions.

Usage

# Record from Bluetooth microphone (SCO activated automatically)
termux-microphone-record -s 7 -f /sdcard/rec.m4a

# Or manage SCO manually
termux-audio-sco enable
termux-microphone-record -s 7 -f /sdcard/rec.m4a
termux-audio-sco disable

Notes

Test plan

  • termux-microphone-record -s 7 records audio from Bluetooth headset microphone
  • termux-audio-sco returns JSON status
  • termux-audio-sco enable / disable toggles SCO channel
  • Recording with default -s 1 (MIC) still works unchanged
  • SCO is torn down correctly on termux-microphone-record -q

🤖 Generated with Claude Code

- MicRecorderAPI: extract startRecording() as standalone method,
  add setupScoAndRecord() with async SCO setup (Android 12+:
  setCommunicationDevice, older: startBluetoothSco + BroadcastReceiver),
  add teardownSco() called on stop/error
- AudioScoAPI: new API for standalone SCO channel management
  (enable/disable/status), exposed as termux-audio-sco utility
- TermuxApiReceiver: route "AudioSco" to AudioScoAPI
- AndroidManifest: add BLUETOOTH, BLUETOOTH_CONNECT, MODIFY_AUDIO_SETTINGS

When source=VOICE_COMMUNICATION (7) is passed to MicRecorder, SCO is
activated automatically before recording starts and torn down after.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…kVersion to 33

- AudioScoAPI: pass apiReceiver (not context) to ResultReturner.returnData()
  to prevent unix socket from hanging; reply immediately, setup SCO in background
- MicRecorderAPI: fix List<AudioDeviceInfo> type (getAvailableCommunicationDevices
  returns List, not array); remove postRecordCommandResult from async SCO callbacks
- gradle.properties: targetSdkVersion 28 → 33 (required for BLUETOOTH_CONNECT
  runtime permission grant via pm on Android 12+)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@1d10t
Copy link
Copy Markdown
Author

1d10t commented Apr 11, 2026

Build & Test Scripts

Scripts for building the APK and testing Bluetooth SCO recording are available as a gist:

https://gist.github.com/1d10t/2ce712e7b506c529beafb65ca56e84aa

Quick start

# 1. (First time only) Install Termux debug APK — same signing key as our build
bash 0-install-termux.sh

# 2. Build APK (requires Android SDK + NDK)
bash 1-build-apk.sh

# 3. Install APK and grant permissions via ADB
bash 2-install-apk.sh

# 4. Push updated shell scripts to device
bash 3-install-scripts.sh
# then inside Termux on device, run the cp commands printed by the script

# 5. Test recording from Bluetooth headset (run inside Termux on device)
bash test-bt-mic.sh

Tested on Samsung SM-A325F (Android 12), Bluetooth headset, targetSdkVersion=33.

devops and others added 3 commits April 12, 2026 09:15
handleEnable now waits for the actual SCO connection result before
responding, instead of returning "SCO enable initiated" immediately.
Uses CountDownLatch to block the ResultReturner writer thread until
the async callback (getProfileProxy on Android 12+, SCO state broadcast
on older versions) signals completion. 5-second timeout for pre-12.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ew fixes

- Add isScoActive() helper that checks SCO state on both Android 12+
  (getCommunicationDevice) and older (isBluetoothScoOn)
- handleEnable: early return if SCO already active, skip getProfileProxy
- handleDisable: early return if SCO already inactive, skip audio reset
- handleStatus: refactored to use shared isScoActive() helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ableCommunicationDevices directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant