Skip to content

Add microphone streaming support for Apollo-compatible hosts#537

Open
logabell wants to merge 2 commits intoClassicOldSong:moonlight-noirfrom
logabell:codex/mic-android
Open

Add microphone streaming support for Apollo-compatible hosts#537
logabell wants to merge 2 commits intoClassicOldSong:moonlight-noirfrom
logabell:codex/mic-android

Conversation

@logabell
Copy link
Copy Markdown

Summary

Ports microphone streaming support from my Moonlight mic forks into Artemis Android so the client can capture microphone audio locally and stream it to Apollo-compatible hosts.

This is intended to work with ClassicOldSong/Apollo#1428.

Dependency

This draft depends on the companion moonlight-common-c PR:

The current branch keeps the submodule URL pointed at my fork so the Android branch can be checked out and tested immediately. Once the common-c PR lands, I can rebase this PR back onto the upstream submodule URL and commit.

User-facing changes

  • add a microphone enable toggle in stream settings
  • request RECORD_AUDIO when microphone capture is enabled
  • add microphone input device selection on Android M+
  • add a live microphone preview meter in settings
  • fall back to the default microphone if a saved input device disappears
  • start and stop microphone capture with the stream lifecycle

Implementation

  • thread enableMicrophone through Game -> StreamConfiguration -> NvConnection -> MoonBridge
  • add JNI/native microphone setup, PCM queueing, streaming, and teardown
  • capture 48 kHz mono PCM16 with AudioRecord
  • encode and send Opus microphone frames through moonlight-common-c
  • add Robolectric coverage for microphone preference persistence and MoonBridge forwarding
  • improve preview sensitivity and deduplicate repeated Android input device entries seen on some tablets

Source forks

Testing

Focused verification completed on my fork:

  • :app:assembleNonRoot_gameDebug
  • NvConnectionMicrophoneTest
  • StreamConfigurationTest
  • PreferenceConfigurationTest

A test build is available via Obtainium from my fork:

On device, Obtainium can be pointed at the fork repo above to install the published arm64-v8a debug APK for manual validation. The test build uses the Artemis-mic app label so it can be installed alongside an existing Artemis install.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f16c1b5df5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +352 to +356
.setSampleRate(SAMPLE_RATE)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
.build())
.setBufferSizeInBytes(bufferSizeBytes)
.build();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Catch AudioRecord.Builder failures before trying fallback sources

createCaptureConfig() intentionally probes UNPROCESSED, then VOICE_RECOGNITION, then MIC, but buildAudioRecord() lets AudioRecord.Builder.build() throw on unsupported parameters/device states. On Android N+ devices that advertise no usable UNPROCESSED input, opening Stream Settings or starting a stream with microphone enabled will crash here instead of falling back to the later sources, so the new microphone feature is unavailable on a substantial set of phones/tablets.

Useful? React with 👍 / 👎.

Comment on lines +89 to +91
String label = describeDevice(deviceInfo);
if (!uniqueEntries.containsKey(label)) {
uniqueEntries.put(label, new InputDeviceEntry(deviceInfo.getId(), label));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disambiguate duplicate microphone labels instead of dropping devices

This map uses the human-readable label as the uniqueness key, but describeDevice() returns generic names such as "Built-in microphone" and "USB microphone" for many inputs. On devices with multiple built-in mics or two identical USB/Bluetooth headsets, only the first device survives this loop, so users cannot select the intended input and a saved device ID can be silently lost when the list is rebuilt.

Useful? React with 👍 / 👎.

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