我如何获取内部音频的 byte[] pcmData(Java 的 Xt 音频)

How do i get byte[] pcmData of internal audio (Xt Audio for Java)

如何在 xt 音频上获取 byte[] pcmData(就像在 https://github.com/goxr3plus/Java-Spectrum-Analyser-Tutorials 中一样)?我想使用计算机的 wasapi(输出扬声器)实时绘制内部音频的 osc(频谱分析仪)。例如实时分析youtube音频输出,游戏内部音频等

编辑:我如何使用 xt 音频捕获内部音频 wasapi pcmdata(内部声音,而不是麦克风声音)以在可视化工具上对其进行分析?我需要字节[]

查看下面的完整示例。它为每个环回设备记录 1 秒的音频数据,将其转换为字节数组,然后将其转储到具有设备名称的文件中。我希望它足够不言自明。

package sample;

import com.sun.jna.Pointer;
import java.io.FileOutputStream;
import java.util.EnumSet;
import xt.audio.Enums.XtDeviceCaps;
import xt.audio.Enums.XtEnumFlags;
import xt.audio.Enums.XtSample;
import xt.audio.Enums.XtSystem;
import xt.audio.Structs.XtBuffer;
import xt.audio.Structs.XtBufferSize;
import xt.audio.Structs.XtChannels;
import xt.audio.Structs.XtDeviceStreamParams;
import xt.audio.Structs.XtFormat;
import xt.audio.Structs.XtMix;
import xt.audio.Structs.XtStreamParams;
import xt.audio.XtAudio;
import xt.audio.XtDevice;
import xt.audio.XtDeviceList;
import xt.audio.XtPlatform;
import xt.audio.XtSafeBuffer;
import xt.audio.XtService;
import xt.audio.XtStream;

public class Sample {

    // intermediate buffer
    static byte[] BYTES;
    // dump to file (never do this, see below)
    static FileOutputStream fos;

    // audio streaming callback
    static int onBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        if(safe == null) return 0;
        // lock buffer from native into java
        safe.lock(buffer);
        // short[] because we specified INT16 below
        // this is the captured audio data
        short[] audio = (short[])safe.getInput();
        // you want a spectrum analyzer, i dump to a file
        // but actually never dump to a file in any serious app
        // see http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing
        processAudio(audio, buffer.frames);
        // unlock buffer from java into native
        safe.unlock(buffer);
        return 0;
    }

    static void processAudio(short[] audio, int frames) throws Exception {
        // convert from short[] to byte[]
        for(int frame = 0; frame < frames; frame++) {
            // for 2 channels
            for(int channel = 0; channel < 2; channel++) {
                // 2 = channels again
                int sampleIndex = frame * 2 + channel;
                // 2 = 2 bytes for each short
                int byteIndex0 = sampleIndex * 2;
                int byteIndex1 = sampleIndex * 2 + 1;
                // probably some library method for this, somewhere
                BYTES[byteIndex0] = (byte)(audio[sampleIndex] & 0x000000FF);
                BYTES[byteIndex1] = (byte)((audio[sampleIndex] & 0x0000FF00) >> 8);
            }
        }

        // by now BYTES contains the data you want,
        // but be sure to account for frame count
        // (i.e. not all off BYTES may contain useful data,
        // might be some unused garbage at the end)

        // compute total bytes this round
        // = frame count * 2 channels * 2 bytes per short (INT16)
        int byteCount = frames * 2 * 2;

        // write to file - again, never do this in a real app
        fos.write(BYTES, 0, byteCount);
    }

    public static void main(String[] args) throws Exception {
        // this initializes platform dependent stuff like COM
        try(XtPlatform platform = XtAudio.init(null, Pointer.NULL, null)) {
            // works on windows only, obviously
            XtService service = platform.getService(XtSystem.WASAPI);
            // list input devices (this includes loopback)
            try(XtDeviceList list = service.openDeviceList(EnumSet.of( XtEnumFlags.INPUT))) {
                for(int i = 0; i < list.getCount(); i++) {
                    String deviceId = list.getId(i);
                    EnumSet<XtDeviceCaps> caps = list.getCapabilities(deviceId);
                    // filter loopback devices
                    if(caps.contains(XtDeviceCaps.LOOPBACK)) {
                        String deviceName = list.getName(deviceId);
                        // just to check what output we're recording
                        System.out.println(deviceName);
                        // open device
                        try(XtDevice device = service.openDevice(deviceId)) {
                            // 16 bit 48khz
                            XtMix mix = new XtMix(48000, XtSample.INT16);
                            // 2 channels input, no masking
                            XtChannels channels = new XtChannels(2, 0, 0, 0);
                            // final audio format
                            XtFormat format = new XtFormat(mix, channels);
                            // query min/max/default buffer sizes
                            XtBufferSize bufferSize = device.getBufferSize(format);
                            // true->interleaved, onBuffer->audio stream callback
                            XtStreamParams streamParams = new XtStreamParams(true, Sample::onBuffer, null, null);
                            // final initialization params with default buffer size
                            XtDeviceStreamParams deviceParams = new XtDeviceStreamParams(streamParams, format, bufferSize.current);
                            // run stream
                            // safe buffer allows you to get java short[] instead on jna Pointer in the callback
                            try(XtStream stream = device.openStream(deviceParams, null);
                                var safeBuffer = XtSafeBuffer.register(stream, true)) {
                                // max frames to enter onBuffer * channels * bytes per sample
                                BYTES = new byte[stream.getFrames() * 2 * 2];
                                // make filename valid
                                String fileName = deviceName.replaceAll("[\\/:*?\"<>|]", "");
                                try(FileOutputStream fos0 = new FileOutputStream(fileName + ".raw")) {
                                    // make filestream accessible to the callback
                                    // could also be done by passsing as userdata to openStream
                                    fos = fos0;
                                    // run for 1 second
                                    stream.start();
                                    Thread.sleep(1000);
                                    stream.stop();
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}