C#实现基于程序所在屏幕的音频输出动态切换技术问询
Nice work getting the screen detection and focused app tracking sorted out—you’re already halfway there! Let’s walk through how to connect that to audio output switching, with platform-specific implementations and core logic to tie everything together smoothly.
No matter which OS you’re targeting, the core workflow stays consistent:
- When your module detects the focused app has switched screens
- Fetch the audio output device linked to the target screen (we’ll cover mapping screens to devices below)
- Locate the active audio session/stream belonging to the focused app
- Use platform-native APIs to redirect that app’s audio to the target device
- Handle edge cases (e.g., the app has no active audio, target device is unavailable, permission issues)
Windows (Core Audio API)
Windows’ Core Audio toolkit gives you precise control over per-app audio sessions. Here’s how to implement the switch:
Step 1: Map Screens to Audio Devices
TVs connected as displays expose their speakers as dedicated audio devices. To link a screen to its audio device:
- Use
EnumDisplayMonitorsto get the monitor’s device instance ID (fromMONITORINFOEX’sszDevicefield) - Use SetupAPI to enumerate audio devices, then match the audio device’s parent hardware ID to the monitor’s ID (since the audio component is part of the display hardware)
Step 2: Redirect App Audio to Target Device
Once you have the app’s PID (you already track focused apps, so you should have this) and the target device’s ID, use the PolicyConfigClient API to set that process’s default audio endpoint:
#include <mmdeviceapi.h> #include <policyconfig.h> #include <atlbase.h> HRESULT RedirectAppAudio(DWORD appPid, LPCWSTR targetDeviceId) { CComPtr<IPolicyConfig> policyConfig; HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigClient), nullptr, CLSCTX_ALL, __uuidof(IPolicyConfig), (void**)&policyConfig); if (SUCCEEDED(hr)) { // Set default endpoint for the specific process hr = policyConfig->SetDefaultEndpoint(targetDeviceId, eConsole, appPid); } return hr; }
- Notes: Link against
ole32.libandmmdevapi.lib, and initialize COM withCoInitializeExbefore calling this function. On Windows 10+, your app may need admin privileges or theaudioConfigurationmanifest capability to modify other processes’ audio settings.
macOS (Core Audio + AppKit)
macOS requires a bit more finesse, but Core Audio and AppKit have all the tools you need:
Step 1: Link Screens to Audio Devices
Use NSScreen to extract the audio device ID directly from the display’s metadata (works for displays with built-in audio like smart TVs):
NSScreen *targetScreen = ...; // Your detected target screen NSDictionary *deviceDesc = targetScreen.deviceDescription; AudioDeviceID targetAudioDevice = [[deviceDesc objectForKey:(id)kIODisplayAudioDeviceIDKey] unsignedIntValue];
Step 2: Redirect App Audio
To target a specific app’s audio, use Core Audio’s AudioObjectSetPropertyData to override the output device for the app’s PID. Note: Your app will need Accessibility permissions (in System Settings > Privacy & Security) to modify other apps’ audio:
#import <CoreAudio/CoreAudio.h> BOOL RedirectAppAudio(pid_t appPid, AudioDeviceID targetDevice) { UInt32 pid = appPid; AudioObjectPropertyAddress propAddr = { kAudioSessionProperty_OverrideOutputDevice, kAudioObjectPropertyScope_Global, kAudioObjectPropertyElement_Master }; OSStatus status = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propAddr, sizeof(pid), &pid, sizeof(targetDevice), &targetDevice); return status == noErr; }
Linux (PulseAudio API)
Most modern Linux desktops use PulseAudio, which makes per-app audio routing straightforward:
Step 1: Map Screens to PulseAudio Sinks
Use pactl list sinks to enumerate available audio outputs. HDMI-connected TVs will show up as sinks with names like alsa_output.hdmi-stereo-0. Map screens to sinks by matching the sink’s description to the display name from xrandr output.
Step 2: Move App Audio to Target Sink
First, find the sink input ID linked to the app’s PID, then use PulseAudio’s API to move that stream to the target sink:
#include <pulse/pulseaudio.h> // Callback to locate the app's sink input by PID static void sinkInputCallback(pa_context *ctx, const pa_sink_input_info *info, int eol, void *userdata) { if (eol) return; uint32_t targetPid = *(uint32_t*)userdata; const char *pidStr = pa_proplist_gets(info->proplist, PA_PROP_APPLICATION_PROCESS_ID); if (pidStr && atoi(pidStr) == targetPid) { // Move the stream to your target sink pa_operation *op = pa_context_move_sink_input(ctx, info->index, TARGET_SINK_ID, NULL, NULL); if (op) pa_operation_unref(op); } } // Trigger the search and redirect void FindAndRedirectAppAudio(pa_context *ctx, uint32_t appPid) { pa_context_get_sink_input_info_list(ctx, sinkInputCallback, &appPid); }
- No special permissions needed here—PulseAudio lets users modify their own audio streams by default.
- Debounce screen switches: Don’t trigger an audio switch every time the app’s window moves a pixel. Wait until 80%+ of the window is on the target screen before acting.
- Skip silent apps: Check if the app has an active audio stream before attempting a switch—no need to waste cycles on apps that aren’t playing sound.
- User controls: Add a settings panel where users can manually map screens to audio devices, or exclude certain apps from auto-switching.
- Error handling: Catch cases where the target device is disconnected or your app lacks permissions, and show a friendly message to the user.
内容的提问来源于stack exchange,提问作者DavidAnastasov

