Add an in-session microphone toggle (#1506)

This commit is contained in:
Kurt Himebauch authored and GitHub committed 2026-10-25 08:37:49 -04:00
1 parent 1f42f512e2
commit 0cf5b42788
13 files changed
+347 -12

No files matched your search

@@ -818,6 +818,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
lossless = lossless,
oscMode = oscMode, onScreenButtonsVisible = onScreenButtonsVisible, suspendPolicy = suspendPolicy,
steamDownloadsInBackground = steamDownloadsInBackground, touchMode = touchMode,
micAvailable = SessionState.micAvailable && !SessionState.suspended,
micEnabled = SessionState.micEnabled,
onMicEnabled = { SessionService.setMicEnabled(this@SessionActivity, it) },
touchAuto = getString(if (usingTouchpad()) R.string.session_touch_auto_touchpad else R.string.session_touch_auto_direct),
fexPreset = fexPreset,
secondScreenMode = secondScreenMode,
@@ -3,6 +3,7 @@ package com.droiddeck.launcher.audio;
import android.os.Process;
import android.util.Log;
import com.droiddeck.launcher.core.FileUtils;
import com.droiddeck.launcher.core.SessionPart;
import com.droiddeck.launcher.core.HostProcess;
@@ -19,10 +20,13 @@ public class DirectAudioRelayComponent extends SessionPart {
private volatile int pid = -1;
private final Object micLock = new Object();
private boolean micHot;
private final File micControl;
public java.util.function.Consumer<Boolean> onMicEnabled;
public DirectAudioRelayComponent(File socketPath, File micFifoPath) {
this.socketPath = socketPath;
this.micFifoPath = micFifoPath;
this.micControl = micFifoPath == null ? null : new File(micFifoPath.getParentFile(), "mic-enabled");
}
public void setLogFile(File file) {
@@ -63,8 +67,13 @@ public class DirectAudioRelayComponent extends SessionPart {
StringBuilder command = new StringBuilder(binary.getAbsolutePath());
command.append(" --socket ").append(socketPath.getAbsolutePath());
command.append(" --log");
if (micFifoPath != null) {
final boolean withMic = micFifoPath != null && setMicEnabled(true);
if (withMic) {
command.append(" --mic-fifo ").append(micFifoPath.getAbsolutePath());
command.append(" --mic-control ").append(micControl.getAbsolutePath());
} else if (micFifoPath != null) {
Log.w(TAG, "microphone control unavailable; starting with capture disabled");
command.append(" --mic-control /dev/null");
}
ArrayList<String> env = new ArrayList<>();
env.add("HOME=" + app().getFilesDir());
@@ -73,7 +82,7 @@ public class DirectAudioRelayComponent extends SessionPart {
try {
out = new java.io.PrintWriter(new java.io.FileWriter(logFile, true));
out.println("== DirectAudio relay helper starting"
+ (micFifoPath != null ? " with the microphone" : " (no microphone)"));
+ (withMic ? " with the microphone" : " (no microphone)"));
out.flush();
} catch (Exception e) {
Log.w(TAG, "could not open " + logFile, e);
@@ -84,14 +93,23 @@ public class DirectAudioRelayComponent extends SessionPart {
app().getFilesDir(), null,
line -> {
Log.i(TAG, line);
trackMic(line);
if (withMic) trackMic(line);
if (log != null) synchronized (log) { log.println(line); log.flush(); }
});
Log.i(TAG, "started pid=" + pid + " socket=" + socketPath
+ (micFifoPath != null ? " mic=" + micFifoPath : " (no microphone)"));
+ (withMic ? " mic=" + micFifoPath : " (no microphone)"));
}
public synchronized boolean setMicEnabled(boolean enabled) {
if (micControl == null) return false;
File next = new File(micControl.getPath() + ".new");
return FileUtils.writeString(next, enabled ? "1" : "0") && next.renameTo(micControl);
}
private void trackMic(String line) {
if (line.startsWith("DA-Relay: mic enabled=") && onMicEnabled != null) {
onMicEnabled.accept(line.equals("DA-Relay: mic enabled=1"));
}
boolean start = line.startsWith("DA-Relay: mic start ");
if (!start && !line.startsWith("DA-Relay: mic stop ")) return;
synchronized (micLock) {
@@ -278,6 +278,16 @@ class SessionService : Service() {
updateSuspendPolicy()
return START_NOT_STICKY
}
ACTION_MIC_CHANGED -> {
if (SessionState.running && SessionState.micAvailable && !SessionState.suspended) {
val enabled = intent.getBooleanExtra("enabled", false)
val relay = components.firstNotNullOfOrNull { it as? DirectAudioRelayComponent }
if (relay?.setMicEnabled(enabled) != true) {
android.widget.Toast.makeText(this, R.string.drawer_mic_failed, android.widget.Toast.LENGTH_LONG).show()
}
}
return START_NOT_STICKY
}
ACTION_SUSPEND_POLICY_CHANGED -> {
if (!SessionState.running) return START_NOT_STICKY
suspendPolicy = SessionPrefs.suspendPolicy(this, SessionState.mode)
@@ -329,6 +339,8 @@ class SessionService : Service() {
freezePending = false
suspendAttemptFailed = false
suspendController = null
SessionState.micAvailable = false
SessionState.micEnabled = false
SessionState.suspended = false
SessionState.resumeRequested = false
SessionState.resuming = false
@@ -761,6 +773,15 @@ class SessionService : Service() {
components.add(pulse)
if (wantsDirectAudio || wantsMic) {
val relay = DirectAudioRelayComponent(relaySocket, micFifo)
val gen = sessionGen
relay.onMicEnabled = java.util.function.Consumer { enabled ->
mainHandler.post {
if (gen == sessionGen && SessionState.running && wantsMic) {
SessionState.micAvailable = true
SessionState.micEnabled = enabled
}
}
}
relay.setLogFile(audioLog)
relay.attach(this)
components.add(relay)
@@ -1656,6 +1677,8 @@ class SessionService : Service() {
SessionState.running = false
SessionPaths.take() to SessionState.steamClientArch
}
SessionState.micAvailable = false
SessionState.micEnabled = false
xrActivityVisible = false
updateDownloadMonitoring()
val stoppedGen = sessionGen
@@ -1948,6 +1971,7 @@ class SessionService : Service() {
const val ACTION_LAUNCH_GAME = "com.droiddeck.launcher.LAUNCH_STEAM_GAME"
const val ACTION_HOME_GUIDE = "com.droiddeck.launcher.HOME_GUIDE"
const val ACTION_AGENT_START = "com.droiddeck.launcher.AGENT_START"
private const val ACTION_MIC_CHANGED = "com.droiddeck.launcher.MIC_CHANGED"
private const val ACTION_SUSPEND_POLICY_CHANGED = "com.droiddeck.launcher.SUSPEND_POLICY_CHANGED"
private const val ACTION_ACTIVITY_VISIBLE = "com.droiddeck.launcher.ACTIVITY_VISIBLE"
private const val ACTION_ACTIVITY_HIDDEN = "com.droiddeck.launcher.ACTIVITY_HIDDEN"
@@ -2078,6 +2102,11 @@ class SessionService : Service() {
}.getOrDefault(false)
}
fun setMicEnabled(context: Context, enabled: Boolean) {
context.startService(Intent(context, SessionService::class.java).setAction(ACTION_MIC_CHANGED)
.putExtra("enabled", enabled))
}
fun suspendPolicyChanged(context: Context) {
if (!SessionState.running) return
context.startService(Intent(context, SessionService::class.java).setAction(ACTION_SUSPEND_POLICY_CHANGED))
@@ -46,6 +46,8 @@ object SessionState {
var activityDisplayId = 0
@Volatile
var runtimeDriver: String? = null
var micAvailable by mutableStateOf(false)
var micEnabled by mutableStateOf(false)
var suspended by mutableStateOf(false)
var resumeRequested by mutableStateOf(false)
var resuming by mutableStateOf(false)
@@ -208,6 +208,9 @@ class DrawerActions(
val onScreenButtonsVisible: Boolean,
val suspendPolicy: String,
val steamDownloadsInBackground: Boolean = false,
val micAvailable: Boolean = false,
val micEnabled: Boolean = false,
val onMicEnabled: (Boolean) -> Unit = {},
val backActionsInverted: Boolean,
val padType: String? = null,
val onPadType: (String) -> Unit = {},
@@ -669,6 +672,9 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
}
}
SettingsGroup(stringResource(R.string.drawer_session_behavior)) {
if (a.micAvailable) ToggleRow(host, "microphone", stringResource(R.string.mode_mic),
stringResource(R.string.drawer_mic_hint), a.micEnabled,
chipModifier = focus.track(page, "microphone"), onChange = a.onMicEnabled)
ChoiceRow(
host, "suspend", stringResource(R.string.mode_suspend),
null,
+2
View File
@@ -389,6 +389,8 @@
<string name="mode_directaudio_hint">Bypasses PulseAudio for lower latency.</string>
<string name="mode_mic">Microphone</string>
<string name="mode_mic_hint">For voice chat.</string>
<string name="drawer_mic_hint">Turn microphone capture on or off for this session.</string>
<string name="drawer_mic_failed">Could not change the microphone. Please try again.</string>
<string name="mode_network">Network</string>
<string name="mode_wifi_discovery">Wi-Fi discovery</string>
<string name="mode_wifi_discovery_hint">Show Wi-Fi names and nearby networks in Steam.</string>
@@ -0,0 +1,86 @@
package com.droiddeck.launcher.audio
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.rules.TemporaryFolder
import org.robolectric.RuntimeEnvironment
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.util.ReflectionHelpers
import java.io.File
import java.util.function.Consumer
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28])
class DirectAudioMicrophoneTest {
@get:Rule val folder = TemporaryFolder()
@Test fun toggleOnlyChangesTheSessionCaptureControl() {
val fifo = File(folder.root, "mic.fifo")
val relay = DirectAudioRelayComponent(File(folder.root, "relay.sock"), fifo)
val changes = mutableListOf<Boolean>()
relay.onMicEnabled = Consumer { changes.add(it) }
val control = File(folder.root, "mic-enabled")
assertTrue(relay.setMicEnabled(false))
assertEquals("0", control.readText())
assertTrue(changes.isEmpty())
log(relay, "DA-Relay: mic enabled=0")
assertEquals(listOf(false), changes)
assertTrue(relay.setMicEnabled(true))
assertEquals("1", control.readText())
log(relay, "DA-Relay: mic enabled=1")
assertEquals(listOf(false, true), changes)
assertFalse(fifo.exists())
}
@Test fun sessionWithoutCaptureCannotEnableIt() {
val relay = DirectAudioRelayComponent(File(folder.root, "relay.sock"), null)
assertFalse(relay.setMicEnabled(true))
assertFalse(File(folder.root, "mic-enabled").exists())
}
@Test fun captureDemandDoesNotOverrideTheAcknowledgedToggle() {
val relay = DirectAudioRelayComponent(File(folder.root, "relay.sock"), File(folder.root, "mic.fifo"))
val changes = mutableListOf<Boolean>()
relay.onMicEnabled = Consumer { changes.add(it) }
log(relay, "DA-Relay: mic enabled=0")
log(relay, "DA-Relay: mic start (game wants it, 1 consumer(s))")
log(relay, "DA-Relay: mic stop (game)")
assertEquals(listOf(false), changes)
assertTrue(relay.awaitMicReleased(0))
}
@Test fun controlWriteFailureStillStartsPlaybackWithCaptureDisabled() {
val context = RuntimeEnvironment.getApplication()
val info = context.applicationInfo
val previous = info.nativeLibraryDir
val nativeDir = folder.newFolder("native")
val args = File(folder.root, "arguments")
File(nativeDir, DirectAudioRelayComponent.BINARY).apply {
writeText("#!/bin/sh\nprintf '%s\\n' \"\$@\" > '${args.path}.new'\nmv '${args.path}.new' '${args.path}'\n")
setExecutable(true)
}
val fifo = folder.newFile("mic.fifo")
File(folder.root, "mic-enabled.new").mkdir()
val relay = DirectAudioRelayComponent(File(folder.root, "relay.sock"), fifo)
relay.attach(context)
try {
info.nativeLibraryDir = nativeDir.path
relay.start()
val deadline = System.nanoTime() + 3_000_000_000L
while (!args.isFile && System.nanoTime() < deadline) Thread.sleep(10)
assertTrue("Playback helper did not start", args.isFile)
val command = args.readLines()
assertTrue(command.contains("--socket"))
assertFalse(command.contains("--mic-fifo"))
assertEquals(listOf("--mic-control", "/dev/null"), command.takeLast(2))
} finally {
info.nativeLibraryDir = previous
}
}
private fun log(relay: DirectAudioRelayComponent, line: String) =
ReflectionHelpers.callInstanceMethod<Unit>(relay, "trackMic", ReflectionHelpers.ClassParameter.from(String::class.java, line))
}
@@ -9,6 +9,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.compose.ui.test.*
import androidx.compose.ui.semantics.SemanticsProperties.StateDescription
import androidx.compose.runtime.MutableState
import com.droiddeck.launcher.R
import com.droiddeck.launcher.SessionActivity
@@ -97,6 +98,42 @@ class SessionSecondScreenTest {
}
}
@Test fun sessionMicrophoneCanBeTurnedOff() {
val changes = mutableListOf<Boolean>()
compose.setContent {
DroidDeckTheme {
SessionDrawer(true, SessionDrawerPage.SESSION, false, {},
actions(true, emptyList(), micAvailable = true, micEnabled = true, onMic = changes::add))
}
}
compose.onNodeWithTag("setting-microphone").assertIsEnabled()
.assert(SemanticsMatcher.expectValue(StateDescription, context.getString(R.string.widgets_on))).performClick()
assertEquals(listOf(false), changes)
}
@Test fun sessionMicrophoneCanBeTurnedBackOn() {
val changes = mutableListOf<Boolean>()
compose.setContent {
DroidDeckTheme {
SessionDrawer(true, SessionDrawerPage.SESSION, false, {},
actions(true, emptyList(), micAvailable = true, onMic = changes::add))
}
}
compose.onNodeWithTag("setting-microphone").assertIsEnabled()
.assert(SemanticsMatcher.expectValue(StateDescription, context.getString(R.string.widgets_off))).performClick()
assertEquals(listOf(true), changes)
}
@Test fun sessionWithoutMicrophoneOmitsCaptureControl() {
compose.setContent {
DroidDeckTheme {
SessionDrawer(true, SessionDrawerPage.SESSION, false, {}, actions(false, emptyList()))
}
}
compose.onNodeWithTag("setting-microphone").assertDoesNotExist()
compose.onNodeWithText(context.getString(R.string.mode_mic)).assertDoesNotExist()
}
private fun checkModes(steam: Boolean) {
SessionState.mode = if (steam) SessionService.MODE_STEAM else SessionService.MODE_DESKTOP
val selected = mutableListOf<SecondScreenMode>()
@@ -145,8 +182,11 @@ class SessionSecondScreenTest {
if (view is ViewGroup) for (index in 0 until view.childCount) yieldAll(descendants(view.getChildAt(index)))
}
private fun actions(steam: Boolean, displays: List<SecondScreenDisplay>, select: (SecondScreenMode) -> Unit = {}) =
private fun actions(steam: Boolean, displays: List<SecondScreenDisplay>,
micAvailable: Boolean = false, micEnabled: Boolean = false, onMic: (Boolean) -> Unit = {},
select: (SecondScreenMode) -> Unit = {}) =
DrawerActions(steam = steam, isHomeApp = false, androidApps = emptyList(), hudOn = false,
micAvailable = micAvailable, micEnabled = micEnabled, onMicEnabled = onMic,
frameGen = FrameGen.Mode.OFF, lossless = Lossless.State.NONE, oscMode = "never",
onScreenButtonsVisible = false, suspendPolicy = "auto", backActionsInverted = false,
touchMode = "auto", touchAuto = "trackpad", fexPreset = "default",
+2 -1
View File
@@ -110,7 +110,8 @@
],
"outputs": [
"app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so"
]
],
"ndk": true
},
"audio": {
"script": "tools/components/recipes/audio.sh",
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root=$1
source_dir=$2
: "${NDK:=${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_HOME:-${ANDROID_NDK_ROOT:-}}}}"
: "${NDK:?set NDK to the Android NDK root}"
case "$(uname -s)" in
Darwin) host=darwin-x86_64 ;;
Linux) host=linux-x86_64 ;;
*) echo 'Unsupported NDK host' >&2; exit 1 ;;
esac
toolchain="$NDK/toolchains/llvm/prebuilt/$host/bin"
patch -d "$source_dir" -p1 < "$repo_root/tools/directaudio/microphone-toggle.patch"
"$toolchain/aarch64-linux-android28-clang" -O2 -Wl,-z,max-page-size=16384 \
"$source_dir/directaudio-relay.c" -o "$repo_root/app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so" \
-laaudio -llog -pthread
"$toolchain/llvm-strip" --strip-unneeded "$repo_root/app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so"
+12 -5
View File
@@ -3,7 +3,7 @@ set -euo pipefail
repo_root=$1
sink_out=$2
. "${repo_root}/tools/directaudio/release.env"
for v in DIRECTAUDIO_REPO DIRECTAUDIO_TAG DIRECTAUDIO_WINE11_SHA256 DIRECTAUDIO_SYSTHREAD_SHA256 DIRECTAUDIO_RELAY_SHA256 DIRECTAUDIO_SINK_SHA256; do
for v in DIRECTAUDIO_REPO DIRECTAUDIO_TAG DIRECTAUDIO_WINE11_SHA256 DIRECTAUDIO_SYSTHREAD_SHA256 DIRECTAUDIO_SINK_SHA256 DIRECTAUDIO_SOURCE_COMMIT DIRECTAUDIO_SOURCE_SHA256; do
[[ -n "${!v:-}" && "${!v}" != TBD ]] || { echo "tools/directaudio/release.env: ${v} is not set" >&2; exit 1; }
done
work=$(mktemp -d)
@@ -24,7 +24,15 @@ fetch() {
}
fetch directaudio-linux-wine11.zip "${DIRECTAUDIO_WINE11_SHA256}"
fetch directaudio-linux-wine11-systhread.zip "${DIRECTAUDIO_SYSTHREAD_SHA256}"
fetch directaudio-linux-relay.zip "${DIRECTAUDIO_RELAY_SHA256}"
source_archive="${cache}/${DIRECTAUDIO_SOURCE_SHA256}-directaudio-source.tar.gz"
if ! echo "${DIRECTAUDIO_SOURCE_SHA256} ${source_archive}" | sha256sum -c - >/dev/null 2>&1; then
curl -fsSL --retry 3 -o "${work}/source.tar.gz" \
"https://github.com/${DIRECTAUDIO_REPO}/archive/${DIRECTAUDIO_SOURCE_COMMIT}.tar.gz"
echo "${DIRECTAUDIO_SOURCE_SHA256} ${work}/source.tar.gz" | sha256sum -c - >/dev/null
mv "${work}/source.tar.gz" "${source_archive}"
fi
mkdir "${work}/source"
tar -xzf "${source_archive}" --strip-components=1 -C "${work}/source"
fetch directaudio-linux-sink.zip "${DIRECTAUDIO_SINK_SHA256}"
assets="${repo_root}/app/src/main/assets/directaudio"
@@ -40,9 +48,8 @@ done
grep -q '^abi wine11$' "${assets}/linux-wine11/version.txt"
grep -q '^abi systhread$' "${assets}/linux-wine11-systhread/version.txt"
mkdir -p "${work}/relay" "${work}/sink" "${sink_out}" "${repo_root}/app/src/main/jniLibs/arm64-v8a"
unzip -q "${work}/directaudio-linux-relay.zip" -d "${work}/relay"
install -m755 "${work}/relay/libdirectaudiorelay.so" "${repo_root}/app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so"
mkdir -p "${work}/sink" "${sink_out}" "${repo_root}/app/src/main/jniLibs/arm64-v8a"
"${repo_root}/tools/directaudio/build-relay.sh" "${repo_root}" "${work}/source"
unzip -q "${work}/directaudio-linux-sink.zip" -d "${work}/sink"
install -m755 "${work}/sink/module-directaudio-native-sink.so" "${sink_out}/"
echo "DirectAudio ${DIRECTAUDIO_TAG}: two driver sets, the relay helper and the client sink in place"
+123
View File
@@ -0,0 +1,123 @@
--- a/directaudio-relay.c
+++ b/directaudio-relay.c
@@ -70,6 +70,9 @@
* session log captures it too. Event-level lines are always on: they fire a
* handful of times per session and are what makes a field report actionable. */
static int g_verbose;
+static const char *g_mic_control;
+static int g_mic_enabled = 1;
+static pthread_mutex_t g_mic_operations = PTHREAD_MUTEX_INITIALIZER;
static void rlog(const char *fmt, ...)
{
@@ -735,6 +738,9 @@
(void)user;
if (aq != __atomic_load_n(&g_mic.cq, __ATOMIC_SEQ_CST) && __atomic_load_n(&g_mic.cq, __ATOMIC_SEQ_CST))
return AAUDIO_CALLBACK_RESULT_CONTINUE;
+ if (!__atomic_load_n(&g_mic_enabled, __ATOMIC_ACQUIRE))
+ memset(audioData, 0, numFrames * g_mic.in_channels *
+ (g_mic.in_format == AAUDIO_FORMAT_PCM_I16 ? sizeof(int16_t) : sizeof(float)));
__atomic_add_fetch(&g_mic.cb_count, 1, __ATOMIC_RELAXED);
pthread_mutex_lock(&g_clients_lock);
@@ -832,13 +838,14 @@
AAudioStream *aq = NULL;
int start = 0, stop = 0;
+ pthread_mutex_lock(&g_mic_operations);
pthread_mutex_lock(&g_mic.lock);
g_mic.want += delta;
if (g_mic.want < 0) g_mic.want = 0;
if (g_mic.cq)
{
- if (g_mic.want > 0 && !g_mic.started) { g_mic.started = 1; start = 1; aq = g_mic.cq; }
- if (g_mic.want == 0 && g_mic.started) { g_mic.started = 0; stop = 1; aq = g_mic.cq; }
+ if (g_mic.want > 0 && __atomic_load_n(&g_mic_enabled, __ATOMIC_ACQUIRE) && !g_mic.started) { g_mic.started = 1; start = 1; aq = g_mic.cq; }
+ if ((g_mic.want == 0 || !__atomic_load_n(&g_mic_enabled, __ATOMIC_ACQUIRE)) && g_mic.started) { g_mic.started = 0; stop = 1; aq = g_mic.cq; }
}
pthread_mutex_unlock(&g_mic.lock);
@@ -856,8 +863,23 @@
else if (stop)
{
AAudioStream_requestStop(aq);
- REVENT("mic stop (%s was the last consumer)", who);
- }
+ REVENT("mic stop (%s)", who);
+ }
+ pthread_mutex_unlock(&g_mic_operations);
+}
+
+static void mic_control_update(void)
+{
+ FILE *f;
+ int enabled;
+ if (!g_mic_control) return;
+ f = fopen(g_mic_control, "r");
+ enabled = f && fgetc(f) == '1';
+ if (f) fclose(f);
+ if (enabled == __atomic_load_n(&g_mic_enabled, __ATOMIC_ACQUIRE)) return;
+ __atomic_store_n(&g_mic_enabled, enabled, __ATOMIC_RELEASE);
+ mic_want(0, "session toggle");
+ REVENT("mic enabled=%d", enabled);
}
static void *mic_reopen_thread(void *unused)
@@ -870,8 +892,9 @@
__atomic_store_n(&g_mic.reopen_redo, 0, __ATOMIC_SEQ_CST);
+ pthread_mutex_lock(&g_mic_operations);
pthread_mutex_lock(&g_mic.lock);
- want_start = g_mic.want > 0;
+ want_start = g_mic.want > 0 && __atomic_load_n(&g_mic_enabled, __ATOMIC_ACQUIRE);
pthread_mutex_unlock(&g_mic.lock);
if (open_input(&neu) == AAUDIO_OK)
@@ -902,6 +925,7 @@
AAudioStream_close(old);
}
+ pthread_mutex_unlock(&g_mic_operations);
if (__atomic_load_n(&g_mic.reopen_redo, __ATOMIC_SEQ_CST)) { usleep(20000); continue; }
__atomic_store_n(&g_mic.reopen_running, 0, __ATOMIC_SEQ_CST);
if (!__atomic_load_n(&g_mic.reopen_redo, __ATOMIC_SEQ_CST)) break;
@@ -1348,7 +1372,8 @@
/* ---- housekeeping: watchdog tick + mailbox watch, once a second ------------- */
static void *housekeeping_thread(void *unused)
{
- struct timespec iv = { 1, 0 };
+ struct timespec iv = { 0, 100000000 };
+ unsigned tick = 0;
(void)unused;
for (;;)
@@ -1357,6 +1382,8 @@
int reload = 0;
nanosleep(&iv, NULL);
+ mic_control_update();
+ if (++tick % 10) continue;
if (g_rt_path[0])
{
@@ -1402,8 +1429,9 @@
{
if (!strcmp(argv[i], "--socket") && i + 1 < argc) path = argv[++i];
else if (!strcmp(argv[i], "--mic-fifo") && i + 1 < argc) fifo_path = argv[++i];
+ else if (!strcmp(argv[i], "--mic-control") && i + 1 < argc) g_mic_control = argv[++i];
else if (!strcmp(argv[i], "--log")) g_verbose = 1;
- else { fprintf(stderr, "usage: directaudio-relay --socket <path> [--mic-fifo <path>] [--log]\n"); return 2; }
+ else { fprintf(stderr, "usage: directaudio-relay --socket <path> [--mic-fifo <path>] [--mic-control <path>] [--log]\n"); return 2; }
}
if (!path)
{
@@ -1429,6 +1457,8 @@
chmod(path, 0666);
if (listen(srv, 8) != 0) { REVENT("listen: %d", errno); return 1; }
+ mic_control_update();
+ REVENT("mic enabled=%d", __atomic_load_n(&g_mic_enabled, __ATOMIC_ACQUIRE));
if (pthread_create(&th, NULL, housekeeping_thread, NULL) == 0) pthread_detach(th);
if (fifo_path && fifo_setup(fifo_path) != 0)
REVENT("mic-fifo: disabled");
+2 -1
View File
@@ -2,5 +2,6 @@ DIRECTAUDIO_REPO=The412Banner/directaudio
DIRECTAUDIO_TAG=directaudio-linux-v1.1.0
DIRECTAUDIO_WINE11_SHA256=835e6cab072f5148893c43379e60506483aeb3f1444e777b430c295cbeb472c9
DIRECTAUDIO_SYSTHREAD_SHA256=2ddc47f446142d5940540ad40a423ca8d47fd536e3225b888df1077c6be7329e
DIRECTAUDIO_RELAY_SHA256=b68eb6d7a892fd92a1cd73fac36dc05cabb77cc73792e2c582d3eeccad355942
DIRECTAUDIO_SINK_SHA256=1d5d3ed709e5389ef7603f019c62b07e4e8428bb42882193d4a144a5c15425b6
DIRECTAUDIO_SOURCE_COMMIT=52aedbf8ddd42874a5bbd21df20e1046367f442e
DIRECTAUDIO_SOURCE_SHA256=a20c9a0684ae3b1e60a6b9c17b7f1d5ec2501627ae9683aa9f391576a6c1974d