问题描述
我如何在 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,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);
// 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();
}
}
}
}
}
}
}
}
}