Как получить byte[] pcmData внутреннего аудио (Xt Audio для Java)

Как получить byte[] pcmData (так же, как в https://github.com/goxr3plus/Java-Spectrum-Analyser-Tutorials), а на xt audio? Я хотел нарисовать osc (анализатор спектра) внутреннего звука, используя wasapi (выходные динамики) компьютера в реальном времени. например анализирует аудиовыход YouTube в реальном времени, внутренний звук игр и т. д.

редактировать: как мне захватить внутренний звук wasapi pcmdata (внутренний звук, а не звук микрофона), используя xt audio, чтобы проанализировать его на визуализаторе? мне нужен байт[]


person GDjkhp    schedule 04.02.2021    source источник
comment
Откройте поток захвата на петлевом устройстве wasapi. Вы можете протестировать XtDevice.getCapabilities() для XtDeviceCaps.LOOPBACK, чтобы найти петлевые устройства. Однако в настоящее время нет способа найти петлевое устройство, соответствующее устройству вывода по умолчанию. Затем нужные данные находятся в XtBuffer.input, в буфере, переданном обратному вызову XtOnBuffer, который вы указали при открытии потока. Это необработанный указатель, оттуда вы используете JNA для чтения данных в byte[]. В качестве альтернативы вы можете использовать XtSafeBuffer, который дает вам java short[] или float[], который вы затем вручную конвертируете в byte[].   -  person Sjoerd van Kreel    schedule 04.02.2021
comment
о боже, это ты, лол, создатель xtaudio, это огромная честь… пожалуйста, помогите мне, я не знаю, с чего начать, я только вчера скачал xt audio и не могу понять, как мне его использовать, лол, у меня так много вопросы, я использую java, и документы для этого бесполезны, лол, пожалуйста, помогите мне, как мне открыть устройство обратной связи потока захвата? действительно ли XtBuffer.input возвращает pcmdata, представляющий собой массив байтов? с чего мне вообще начать, лол? Я запутался… Java-код очень ценится, но я попробую C++ или другой язык…   -  person GDjkhp    schedule 05.02.2021
comment
См. здесь sjoerdvankreel.github.io/xt-audio на вкладке запись-›java для пример открытия обычного потока захвата. Вам нужно только выбрать другое устройство для обратной связи. См. здесь sjoerdvankreel.github.io/xt-audio/doc/ core/html/index.html для получения полной документации по интерфейсу C и здесь sjoerdvankreel.github.io/xt-audio/doc/java/apidocs/xt/audio/ для фиктивных документов интерфейса java (что почти соответствует интерфейс C, который полностью задокументирован).   -  person Sjoerd van Kreel    schedule 05.02.2021


Ответы (1)


Полный пример см. ниже. Он записывает 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();
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
person Sjoerd van Kreel    schedule 05.02.2021
comment
Хорошо, код на самом деле работает, лол, но только на секунду или путем изменения Thread.sleep, удаления Thread.sleep, но дает мне исключение нулевого указателя в safe.lock (буфер), как я могу это исправить? - person GDjkhp; 07.02.2021
comment
это произошло osc bug.png - person GDjkhp; 07.02.2021
comment
также как я могу получить стереоканалы? мне нужен массив левого и правого каналов - person GDjkhp; 07.02.2021
comment
Нулевой указатель может быть ошибкой в ​​xt-audio. OnBuffer вызывается в отдельном потоке, поэтому может случиться так, что XtSafeBuffer уже выпущен во время выполнения последнего обратного вызова OnBuffer. Рассмотрю это. У вас есть трассировка стека? Кстати, исправление на данный момент состоит в том, чтобы немедленно вернуться из onbuffer, если safe равен нулю. Аудиомассив short[], переданный в processAudio, уже является стереофоническим. Четные образцы слева, нечетные образцы справа. - person Sjoerd van Kreel; 07.02.2021
comment
еще одна ошибка буфера посмотрите на строки, см. рис. ниже, как мне его сгладить? - person GDjkhp; 07.02.2021
comment
Не могу сказать, не глядя на код вашего графика. Кстати, вам действительно нужен анализатор спектра или вы просто рисуете сам сигнал? Это разные вещи. Я предлагаю вам загрузить Audacity или любой другой аудиоредактор, чтобы вы могли проверить, как выглядят записанные данные, а затем сравнить их с тем, что вы рисуете. - person Sjoerd van Kreel; 07.02.2021
comment
также я думаю, что байт [] был ошибкой, как мне сделать его плавающим также странные линии, см. рис. выше, почему линия посередине? - person GDjkhp; 07.02.2021
comment
Можете ли вы опубликовать полный пример? - person Sjoerd van Kreel; 07.02.2021
comment
также длина байтов возвращает 2112, это нормально? - person GDjkhp; 07.02.2021
comment
вот репозиторий github @Sjoerd van Kreel - person GDjkhp; 07.02.2021
comment
как сделать кривые линии плавными? я думаю, это мой вопрос - person GDjkhp; 07.02.2021
comment
Не используйте BYTES.length. Вместо этого рассчитайте количество полезных байтов из счетчика кадров. Если вам нужен совет по части ввода-вывода аудио, я буду рад помочь, но я мало что могу для вас сделать, если проблема в коде визуализации. - person Sjoerd van Kreel; 07.02.2021
comment
Как я могу это сделать? также то, почему я использую длину байтов, просто для тестирования, если это работает, что мне тогда кодировать? - person GDjkhp; 08.02.2021
comment
массив байтов дает мне странные результаты, я передумал, я хочу массив с плавающей запятой, как мне это сделать? - person GDjkhp; 08.02.2021
comment
мне нужен метод, который преобразует BYTES [] (промежуточный буфер) в float [], я думаю, что растяжение массива может решить мою проблему - person GDjkhp; 08.02.2021
comment
мне нужно float[] в диапазоне от -1,0 до 1,0, например, максимальное значение байтов возвращает 1f, разделенные каналы - person GDjkhp; 08.02.2021
comment
Затем преобразуйте шорты (short[] audio) в числа с плавающей запятой напрямую. Замените BYTES размера кадра * канала * 2 на FLOATS размера кадра * канала. Тогда каждый float = ((float)shorts[i])/Short.MAX_VALUE. - person Sjoerd van Kreel; 08.02.2021
comment
хорошо, спасибо, я попробую это позже - person GDjkhp; 08.02.2021
comment
буфер захватывает только основной канал микшера, как мне изменить его только на канал микшера приложения? как мне это назвать? @Шёрд - person GDjkhp; 13.02.2021
comment
Это невозможно. Петля Wasapi всегда фиксирует финальный миксдаун. По-видимому, при поддержке он использует для этого аппаратные средства замыкания на себя, и в этом случае отдельные потоки уровня приложения больше не доступны. См. docs.microsoft.com/en-us/windows/win32. /кораудио/. - person Sjoerd van Kreel; 13.02.2021
comment
как преобразовать буферный массив в массив частотных диапазонов? @Шёрд ван Крил - person GDjkhp; 29.03.2021
comment
С помощью фф, наверное. Лучше спросить на dsp stackexchange. - person Sjoerd van Kreel; 01.04.2021