mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
Add an in-session microphone toggle (#1506)
This commit is contained in:
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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -110,7 +110,8 @@
|
||||
],
|
||||
"outputs": [
|
||||
"app/src/main/jniLibs/arm64-v8a/libdirectaudiorelay.so"
|
||||
]
|
||||
],
|
||||
"ndk": true
|
||||
},
|
||||
"audio": {
|
||||
"script": "tools/components/recipes/audio.sh",
|
||||
|
||||
Executable
+17
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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,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
|
||||
Reference in new issue
Block a user