You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Java程序实现可调节多程序音量的虚拟音频输出设备?

Per-App Audio Volume Control in Java (Windows Mixer Clone)

Hey there! Great project idea—building a simplified Windows volume mixer in Java is totally doable, but you’re right that the standard AudioSystem class won’t cut it for per-app audio control. Let’s break down how to solve this core problem.

Why AudioSystem Isn’t Enough

The javax.sound.sampled.AudioSystem class operates at the device or system level—it can access audio input/output hardware, but it has no way to isolate audio streams from individual processes. Windows handles per-app audio via audio sessions (tied directly to running processes), and this functionality lives in native Windows APIs, not Java’s cross-platform standard library.

Solution: Use JNA to Call Windows Core Audio APIs

To tap into per-process audio control, you’ll need to use JNA (Java Native Access) to interact with Windows’ Core Audio COM interfaces. These interfaces let you enumerate active audio sessions, link them to their parent processes, and adjust their volume independently. The key interfaces you’ll work with are:

  • IAudioSessionManager2: Manages audio sessions for a target audio device.
  • IAudioSessionEnumerator: Lists all active audio sessions on the system.
  • IAudioSessionControl2: Retrieves process details (like executable name) for a session.
  • ISimpleAudioVolume: Controls volume and mute state for an individual session.

Step 1: Add JNA Dependencies

First, include JNA in your project. If using Maven, add these to your pom.xml:

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-core</artifactId>
    <version>5.13.0</version>
</dependency>
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.13.0</version>
</dependency>

Step 2: Map Core Audio Interfaces with JNA

You’ll need to define JNA interfaces that mirror Windows’ COM APIs. Here’s a simplified set of mappings (full implementations exist in open-source projects like jna-platform):

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.ptr.PointerByReference;

// IAudioSessionManager2 interface
public interface IAudioSessionManager2 extends com.sun.jna.platform.win32.COM.IUnknown {
    Guid.IID IID_IAUDIOSESSIONMANAGER2 = new Guid.IID("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F");
    WinNT.HRESULT GetSessionEnumerator(PointerByReference ppSessionEnum);
}

// IAudioSessionEnumerator interface
public interface IAudioSessionEnumerator extends com.sun.jna.platform.win32.COM.IUnknown {
    Guid.IID IID_IAUDIOSESSIONENUMERATOR = new Guid.IID("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8");
    WinNT.HRESULT GetCount(WinDef.UINTByReference pdwCount);
    WinNT.HRESULT GetSession(WinDef.UINT dwSession, PointerByReference ppSession);
}

// IAudioSessionControl2 interface
public interface IAudioSessionControl2 extends com.sun.jna.platform.win32.COM.IUnknown {
    Guid.IID IID_IAUDIOSESSIONCONTROL2 = new Guid.IID("87CE5498-68D6-44E5-9215-6DA47EF883D8");
    WinNT.HRESULT GetProcessId(WinDef.DWORDByReference pdwProcessId);
}

// ISimpleAudioVolume interface
public interface ISimpleAudioVolume extends com.sun.jna.platform.win32.COM.IUnknown {
    Guid.IID IID_ISIMPLEAUDIOVOLUME = new Guid.IID("87CE5498-68D6-44E5-9215-6DA47EF883D8");
    WinNT.HRESULT SetMasterVolume(float fLevel, Pointer pEventContext);
    WinNT.HRESULT GetMasterVolume(WinDef.FLOATByReference pfLevel);
}

Step 3: Enumerate Sessions and Control Volume

Here’s a simplified code snippet to list active audio sessions, link them to processes, and adjust their volume:

import com.sun.jna.platform.win32.AudioClient;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.ptr.PointerByReference;

public class AudioMixerController {
    public static void main(String[] args) {
        // Initialize audio client and get session manager
        AudioClient.IAudioClient audioClient = AudioClient.INSTANCE;
        PointerByReference pSessionManager = new PointerByReference();
        WinNT.HRESULT hr = audioClient.GetService(AudioClient.IID_IAUDIOSESSIONMANAGER2, pSessionManager);
        COMUtils.checkRC(hr);

        IAudioSessionManager2 sessionManager = Native.load(pSessionManager.getValue(), IAudioSessionManager2.class);

        // Get session enumerator
        PointerByReference pSessionEnum = new PointerByReference();
        hr = sessionManager.GetSessionEnumerator(pSessionEnum);
        COMUtils.checkRC(hr);

        IAudioSessionEnumerator sessionEnum = Native.load(pSessionEnum.getValue(), IAudioSessionEnumerator.class);

        // Get number of active sessions
        WinDef.UINTByReference sessionCount = new WinDef.UINTByReference();
        hr = sessionEnum.GetCount(sessionCount);
        COMUtils.checkRC(hr);

        // Iterate through each session
        for (int i = 0; i < sessionCount.getValue().intValue(); i++) {
            PointerByReference pSessionControl = new PointerByReference();
            hr = sessionEnum.GetSession(new WinDef.UINT(i), pSessionControl);
            COMUtils.checkRC(hr);

            // Get process ID for the session
            PointerByReference pSessionControl2 = new PointerByReference();
            hr = ((com.sun.jna.platform.win32.COM.IUnknown) Native.load(pSessionControl.getValue(), com.sun.jna.platform.win32.COM.IUnknown.class))
                    .QueryInterface(IAudioSessionControl2.IID_IAUDIOSESSIONCONTROL2, pSessionControl2);
            COMUtils.checkRC(hr);

            IAudioSessionControl2 sessionControl2 = Native.load(pSessionControl2.getValue(), IAudioSessionControl2.class);
            WinDef.DWORDByReference processId = new WinDef.DWORDByReference();
            hr = sessionControl2.GetProcessId(processId);
            COMUtils.checkRC(hr);

            // Get process name (implement using JNA's Kernel32 to fetch from PID)
            String processName = getProcessName(processId.getValue().intValue());

            // Get volume control interface
            PointerByReference pSimpleVolume = new PointerByReference();
            hr = ((com.sun.jna.platform.win32.COM.IUnknown) Native.load(pSessionControl.getValue(), com.sun.jna.platform.win32.COM.IUnknown.class))
                    .QueryInterface(ISimpleAudioVolume.IID_ISIMPLEAUDIOVOLUME, pSimpleVolume);
            COMUtils.checkRC(hr);

            ISimpleAudioVolume simpleVolume = Native.load(pSimpleVolume.getValue(), ISimpleAudioVolume.class);

            // Example: Set volume to 50% (range 0.0 to 1.0)
            hr = simpleVolume.SetMasterVolume(0.5f, null);
            COMUtils.checkRC(hr);

            System.out.printf("Adjusted volume for: %s (PID: %d)%n", processName, processId.getValue().intValue());

            // Release COM interfaces to avoid memory leaks
            sessionControl2.Release();
            simpleVolume.Release();
        }

        sessionEnum.Release();
        sessionManager.Release();
    }

    private static String getProcessName(int pid) {
        // Implement using JNA's Kernel32 to open the process and fetch its executable path
        return "Unknown Process"; // Replace with actual implementation
    }
}

Key Considerations

  • COM Interface Cleanup: Always call Release() on COM interfaces after use to prevent memory leaks.
  • Permissions: Your program may need elevated privileges to access audio sessions from system processes.
  • Architecture Compatibility: Ensure your JNA binaries match your JVM’s architecture (64-bit JVM needs 64-bit JNA).
  • Real-Time Updates: To track new/closed audio sessions, implement IAudioSessionNotification to listen for session events.

Next Steps for Your Mixer

Once you have per-session volume control working, you can expand with features like:

  • A GUI (Swing/JavaFX) with volume sliders for each active app.
  • Saving and restoring volume presets for specific programs.
  • One-click mute/unmute for individual apps.

内容的提问来源于stack exchange,提问作者Will

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:51:20