XT-Audio

Releases (changelog):
2.2: June 16, 2024: bugfix release
2.0.1: March 10, 2024: bugfix release
2.0: December 24, 2021: bugfix release
1.9: February 14, 2021: improve error handling
1.8: January 21, 2021: nuget/mvn central support
1.7: January 2, 2021: major cleanup/rewrite release
1.0.6: July 8, 2020: .net core/java9 support
1.0.5: February 14, 2018: bugfix release

Streaming audio focused on low latency, platform independence and API simplicity.

Binaries: Download latest
Previous releases: Browse
Screenshot: WASAPI example
Get the source: XT-Audio on GitHub

Features

• Aggregation: combine multiple devices into a single stream
• DirectSound, WASAPI, ASIO, Pulse, ALSA and JACK backends
• Channel masks, timestamps, full-duplex, (non)interleaved mode
• x86/x64 Windows/Linux (C/C++), JVM, .NET Framework/Core, Mono

Docs

Core / C++ / Java / .NET
Only the core (C) API is fully documented. Code samples are provided for all languages except C. Refer to the core documentation's main page for differences between the C API and other language APIs.


• Print device names to stdout.
package xt.sample;

import java.util.EnumSet;
import xt.audio.Enums.XtEnumFlags;
import xt.audio.Enums.XtSystem;
import xt.audio.XtAudio;
import xt.audio.XtDeviceList;
import xt.audio.XtPlatform;
import xt.audio.XtService;

public class PrintSimple {

    public static void main() throws Exception {
        try(XtPlatform platform = XtAudio.init(null, null)) {
            for(XtSystem system: platform.getSystems()) {
                XtService service = platform.getService(system);
                try(XtDeviceList list = service.openDeviceList(EnumSet.of(XtEnumFlags.ALL))) {
                    for(int d = 0; d < list.getCount(); d++) {
                        String id = list.getId(d);
                        System.out.println(system + ": " + list.getName(id));
                    }
                }
            }
        }
    }
}
using System;

namespace Xt
{
    public static class PrintSimple
    {
        [STAThread]
        public static void Main()
        {
            using XtPlatform platform = XtAudio.Init(null, IntPtr.Zero);
            foreach (XtSystem system in platform.GetSystems())
            {
                XtService service = platform.GetService(system);
                using XtDeviceList list = service.OpenDeviceList(XtEnumFlags.All);
                for (int d = 0; d < list.GetCount(); d++)
                {
                    string id = list.GetId(d);
                    Console.WriteLine(system + ": " + list.GetName(id));
                }
            }
        }
    }
}
#include <xt/XtAudio.hpp>

#include <memory>
#include <cstdint>
#include <iostream>

int 
PrintSimpleMain() 
{
  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  for(Xt::System system: platform->GetSystems()) 
  {
    std::unique_ptr<Xt::Service> service = platform->GetService(system);
    std::unique_ptr<Xt::DeviceList> list = service->OpenDeviceList(Xt::EnumFlagsAll);
    for(int32_t d = 0; d < list->GetCount(); d++)
    {
      std::string id = list->GetId(d);
      std::cout << system << ": " << list->GetName(id) << "\n";
    }
  }
  return 0;
}

• Initialize with application name and error logging callback.
• Query audio setups, service capabilities and default devices.
• Query device capabilities, mix, maximum channels and (non)interleaved access support.
package xt.sample;

import java.util.EnumSet;
import xt.audio.Enums.XtEnumFlags;
import xt.audio.Enums.XtSetup;
import xt.audio.Enums.XtSystem;
import xt.audio.Structs.XtMix;
import xt.audio.Structs.XtVersion;
import xt.audio.XtAudio;
import xt.audio.XtDevice;
import xt.audio.XtDeviceList;
import xt.audio.XtException;
import xt.audio.XtPlatform;
import xt.audio.XtService;
import java.util.Optional;

public class PrintDetailed {

    static void onError(String message) {
        System.out.println(message);
    }

    static void printDevices(XtService service, XtDeviceList list) {
        for(int d = 0; d < list.getCount(); d++) {
            String id = list.getId(d);
            try(XtDevice device = service.openDevice(id)) {
                Optional<XtMix> mix = device.getMix();
                System.out.println("    Device " + id + ":");
                System.out.println("      Name: " + list.getName(id));
                System.out.println("      Capabilities: " + list.getCapabilities(id));
                System.out.println("      Input channels: " + device.getChannelCount(false));
                System.out.println("      Output channels: " + device.getChannelCount(true));
                System.out.println("      Interleaved access: " + device.supportsAccess(true));
                System.out.println("      Non-interleaved access: " + device.supportsAccess(false));
                if(mix.isPresent())
                    System.out.println("      Current mix: " + mix.get().rate + " " + mix.get().sample);
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }

    public static void main() throws Exception {
        XtAudio.setOnError(PrintDetailed::onError);
        try(XtPlatform platform = XtAudio.init("Sample", null)) {
            XtVersion version = XtAudio.getVersion();
            System.out.println("Version: " + version.major + "." + version.minor);
            XtSystem pro = platform.setupToSystem(XtSetup.PRO_AUDIO);
            System.out.println("Pro Audio: " + pro + " (" + (platform.getService(pro) != null) + ")");
            XtSystem system = platform.setupToSystem(XtSetup.SYSTEM_AUDIO);
            System.out.println("System Audio: " + system + " (" + (platform.getService(system) != null) + ")");
            XtSystem consumer = platform.setupToSystem(XtSetup.CONSUMER_AUDIO);
            System.out.println("Consumer Audio: " + consumer + " (" + (platform.getService(consumer) != null) + ")");

            for(XtSystem s: platform.getSystems()) {
                XtService service = platform.getService(s);
                System.out.println("System " + s + ":");
                System.out.println("  Capabilities: " + service.getCapabilities());
                try(XtDeviceList all = service.openDeviceList(EnumSet.of(XtEnumFlags.ALL))) {
                    String defaultInputId = service.getDefaultDeviceId(false);
                    if(defaultInputId != null) {
                        String name = all.getName(defaultInputId);
                        System.out.println("  Default input: " + name + " (" + defaultInputId + ")");
                    }
                    String defaultOutputId = service.getDefaultDeviceId(true);
                    if(defaultOutputId != null) {
                        String name = all.getName(defaultOutputId);
                        System.out.println("  Default output: " + name + " (" + defaultOutputId + ")");
                    }
                }
                try(XtDeviceList inputs = service.openDeviceList(EnumSet.of(XtEnumFlags.INPUT))) {
                    System.out.println("  Input device count: " + inputs.getCount());
                    printDevices(service, inputs);
                }
                try(XtDeviceList outputs = service.openDeviceList(EnumSet.of(XtEnumFlags.OUTPUT))) {
                    System.out.println("  Output device count: " + outputs.getCount());
                    printDevices(service, outputs);
                }
            }
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }
}
using System;

namespace Xt
{
    public static class PrintDetailed
    {
        static void OnError(string message)
        => Console.WriteLine(message);

        static void PrintDevices(XtService service, XtDeviceList list)
        {
            for (int d = 0; d < list.GetCount(); d++)
            {
                string id = list.GetId(d);
                try
                {
                    using XtDevice device = service.OpenDevice(id);
                    XtMix? mix = device.GetMix();
                    Console.WriteLine("    Device " + id + ":");
                    Console.WriteLine("      Name: " + list.GetName(id));
                    Console.WriteLine("      Capabilities: " + list.GetCapabilities(id));
                    Console.WriteLine("      Input channels: " + device.GetChannelCount(false));
                    Console.WriteLine("      Output channels: " + device.GetChannelCount(true));
                    Console.WriteLine("      Interleaved access: " + device.SupportsAccess(true));
                    Console.WriteLine("      Non-interleaved access: " + device.SupportsAccess(false));
                    if (mix != null) Console.WriteLine("      Current mix: " + mix.Value.rate + " " + mix.Value.sample);
                } catch (Exception e)
                { Console.WriteLine(e); }
            }
        }

        [STAThread]
        public static void Main()
        {
            XtAudio.SetOnError(OnError);
            using XtPlatform platform = XtAudio.Init("Sample", IntPtr.Zero);
            try
            {
                XtVersion version = XtAudio.GetVersion();
                Console.WriteLine("Version: " + version.major + "." + version.minor);
                XtSystem pro = platform.SetupToSystem(XtSetup.ProAudio);
                Console.WriteLine("Pro Audio: " + pro + " (" + (platform.GetService(pro) != null) + ")");
                XtSystem system = platform.SetupToSystem(XtSetup.SystemAudio);
                Console.WriteLine("System Audio: " + system + " (" + (platform.GetService(system) != null) + ")");
                XtSystem consumer = platform.SetupToSystem(XtSetup.ConsumerAudio);
                Console.WriteLine("Consumer Audio: " + consumer + " (" + (platform.GetService(consumer) != null) + ")");

                foreach (XtSystem s in platform.GetSystems())
                {
                    XtService service = platform.GetService(s);
                    using XtDeviceList all = service.OpenDeviceList(XtEnumFlags.All);
                    Console.WriteLine("System: " + s);
                    Console.WriteLine("  Capabilities: " + service.GetCapabilities());
                    string defaultInput = service.GetDefaultDeviceId(false);
                    if (defaultInput != null)
                    {
                        string name = all.GetName(defaultInput);
                        Console.WriteLine("  Default input: " + name + " (" + defaultInput + ")");
                    }
                    string defaultOutput = service.GetDefaultDeviceId(true);
                    if (defaultOutput != null)
                    {
                        string name = all.GetName(defaultOutput);
                        Console.WriteLine("  Default output: " + name + " (" + defaultOutput + ")");
                    }
                    using XtDeviceList inputs = service.OpenDeviceList(XtEnumFlags.Input);
                    Console.WriteLine("  Input device count: " + inputs.GetCount());
                    PrintDevices(service, inputs);
                    using XtDeviceList outputs = service.OpenDeviceList(XtEnumFlags.Output);
                    Console.WriteLine("  Output device count: " + outputs.GetCount());
                    PrintDevices(service, outputs);
                }
            } catch (Exception e)
            { Console.WriteLine(e); }
        }
    }
}
#include <xt/XtAudio.hpp>

#include <memory>
#include <cstdint>
#include <cstdlib>
#include <iostream>

static void 
OnError(std::string const& message)
{ std::cout << message << std::endl; }

void
PrintDevices(Xt::Service const* service, Xt::DeviceList const* list)
{
  for(int32_t d = 0; d < list->GetCount(); d++)
  {
    std::string id = list->GetId(d);
    try
    {
      std::unique_ptr<Xt::Device> device = service->OpenDevice(id);
      std::optional<Xt::Mix> mix = device->GetMix();
      std::cout << "    Device " << id << ":\n";
      std::cout << "      Name: " << list->GetName(id) << "\n";
      std::cout << "      Capabilities: " << list->GetCapabilities(id) << "\n";
      std::cout << "      Input channels: " << device->GetChannelCount(false) << "\n";
      std::cout << "      Output channels: " << device->GetChannelCount(true) << "\n";
      std::cout << "      Interleaved access: " << device->SupportsAccess(true) << "\n";
      std::cout << "      Non-interleaved access: " << device->SupportsAccess(false) << "\n";
      if(mix) std::cout << "      Current mix: " << mix->rate << " " << mix->sample << "\n";
    } catch(std::exception const& e)
    { std::cout << e.what() << "\n"; }
  }
}

int 
PrintDetailedMain()
{
  Xt::Audio::SetOnError(OnError);
  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  try 
  {
    Xt::Version version = Xt::Audio::GetVersion();
    std::cout << "Version: " << version.major << "." << version.minor << "\n";    
    Xt::System pro = platform->SetupToSystem(Xt::Setup::ProAudio);
    std::cout << "Pro Audio: " << pro << " (" << (platform->GetService(pro) != nullptr) << ")\n";
    Xt::System system = platform->SetupToSystem(Xt::Setup::SystemAudio);
    std::cout << "System Audio: " << system << " (" << (platform->GetService(system) != nullptr) << ")\n";
    Xt::System consumer = platform->SetupToSystem(Xt::Setup::ConsumerAudio);
    std::cout << "Consumer Audio: " << consumer << " (" << (platform->GetService(consumer) != nullptr) << ")\n";

    for(Xt::System s: platform->GetSystems()) 
    {
      std::unique_ptr<Xt::Service> service = platform->GetService(s);
      std::unique_ptr<Xt::DeviceList> all = service->OpenDeviceList(Xt::EnumFlagsAll);
      std::cout << "System " << s << ":\n";
      std::cout << "  Capabilities: " << service->GetCapabilities() << "\n";
      std::optional<std::string> defaultInput = service->GetDefaultDeviceId(false);
      if(defaultInput.has_value())
      {
        std::string name = all->GetName(defaultInput.value());
        std::cout << "  Default input: " << name << " (" << defaultInput.value() << ")\n";
      }
      std::optional<std::string> defaultOutput = service->GetDefaultDeviceId(true);
      if(defaultOutput.has_value())
      {
        std::string name = all->GetName(defaultOutput.value());
        std::cout << "  Default output: " << name << " (" << defaultOutput.value() << ")\n";
      }
      std::unique_ptr<Xt::DeviceList> inputs = service->OpenDeviceList(Xt::EnumFlagsInput);
      std::cout << "  Input device count: " << inputs->GetCount() << "\n";
      PrintDevices(service.get(), inputs.get());
      std::unique_ptr<Xt::DeviceList> outputs = service->OpenDeviceList(Xt::EnumFlagsOutput);
      std::cout << "  Output device count: " << outputs->GetCount() << "\n";
      PrintDevices(service.get(), outputs.get());
    }
    return EXIT_SUCCESS;
  } catch(std::exception const& e)
  {
    std::cout << e.what() << "\n"; 
    return EXIT_FAILURE;
  }
}

• Render sinewave to the default output device.
package xt.sample;

import xt.audio.Enums.XtSample;
import xt.audio.Enums.XtSetup;
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.XtPlatform;
import xt.audio.XtSafeBuffer;
import xt.audio.XtService;
import xt.audio.XtStream;

public class RenderSimple {

    static float _phase = 0.0f;
    static final float FREQUENCY = 440.0f;
    static final XtMix MIX = new XtMix(44100, XtSample.FLOAT32);
    static final XtChannels CHANNELS = new XtChannels(0, 0, 1, 0);
    static final XtFormat FORMAT = new XtFormat(MIX, CHANNELS);

    static float nextSample() {
        _phase += FREQUENCY / FORMAT.mix.rate;
        if(_phase >= 1.0f) _phase = -1.0f;
        return (float)Math.sin(2.0 * _phase * Math.PI);
    }

    static int onBuffer(XtStream stream, XtBuffer buffer, Object user) {
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        safe.lock(buffer);
        float[] output = (float[])safe.getOutput();
        for(int f = 0; f < buffer.frames; f++) output[f] = nextSample();
        safe.unlock(buffer);
        return 0;
    }

    public static void main() throws Exception {
        XtStreamParams streamParams;
        XtDeviceStreamParams deviceParams;

        try(XtPlatform platform = XtAudio.init(null, null)) {
            XtSystem system = platform.setupToSystem(XtSetup.CONSUMER_AUDIO);
            XtService service = platform.getService(system);
            if(service == null) return;

            String defaultOutput = service.getDefaultDeviceId(true);
            if(defaultOutput == null) return;
            try(XtDevice device = service.openDevice(defaultOutput)) {
                if(!device.supportsFormat(FORMAT)) return;

                XtBufferSize size = device.getBufferSize(FORMAT);
                streamParams = new XtStreamParams(true, RenderSimple::onBuffer, null, null);
                deviceParams = new XtDeviceStreamParams(streamParams, FORMAT, size.current);
                try(XtStream stream = device.openStream(deviceParams, null);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    stream.start();
                    Thread.sleep(2000);
                    stream.stop();
                }
            }
        }
    }
}
using System;
using System.Threading;

namespace Xt
{
    public class RenderSimple
    {
        static float _phase;
        const float Frequency = 440.0f;
        static readonly XtMix Mix = new XtMix(44100, XtSample.Float32);
        static readonly XtChannels Channels = new XtChannels(0, 0, 1, 0);
        static readonly XtFormat Format = new XtFormat(Mix, Channels);

        static float NextSample()
        {
            _phase += Frequency / Mix.rate;
            if (_phase >= 1.0f) _phase = -1.0f;
            return (float)Math.Sin(2.0 * _phase * Math.PI);
        }

        static int OnBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            safe.Lock(in buffer);
            float[] output = (float[])safe.GetOutput();
            for (int f = 0; f < buffer.frames; f++) output[f] = NextSample();
            safe.Unlock(in buffer);
            return 0;
        }

        [STAThread]
        public static void Main()
        {
            XtStreamParams streamParams;
            XtDeviceStreamParams deviceParams;

            using XtPlatform platform = XtAudio.Init(null, IntPtr.Zero);
            XtSystem system = platform.SetupToSystem(XtSetup.ConsumerAudio);
            XtService service = platform.GetService(system);
            if (service == null) return;

            string defaultOutput = service.GetDefaultDeviceId(true);
            if(defaultOutput == null) return;
            using XtDevice device = service.OpenDevice(defaultOutput);
            if (!device.SupportsFormat(Format)) return;

            XtBufferSize size = device.GetBufferSize(Format);
            streamParams = new XtStreamParams(true, OnBuffer, null, null);
            deviceParams = new XtDeviceStreamParams(in streamParams, in Format, size.current);
            using XtStream stream = device.OpenStream(in deviceParams, null);
            using XtSafeBuffer safe = XtSafeBuffer.Register(stream);
            stream.Start();
            Thread.Sleep(2000);
            stream.Stop();
        }
    }
}
#define _USE_MATH_DEFINES 1
#include <xt/XtAudio.hpp>

#include <cmath>
#include <chrono>
#include <thread>
#include <cstdint>

static float _phase = 0.0f;
static float const Frequency = 440.0f;
static Xt::Channels const Channels(0, 0, 1, 0);
static Xt::Mix const Mix(44100, Xt::Sample::Float32);
static Xt::Format const Format(Mix, Channels);

static float 
NextSample()
{
  _phase += Frequency / Mix.rate;
  if (_phase >= 1.0f) _phase = -1.0f;
  return sinf(2.0f * _phase * static_cast<float>(M_PI));
}

static uint32_t 
OnBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user)
{
  float* output = static_cast<float*>(buffer.output);
  for (int32_t f = 0; f < buffer.frames; f++) output[f] = NextSample();
  return 0;
}

int 
RenderSimpleMain()
{
  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  Xt::System system = platform->SetupToSystem(Xt::Setup::ConsumerAudio);
  std::unique_ptr<Xt::Service> service = platform->GetService(system);
  if (!service) return 0;

  std::optional<std::string> id = service->GetDefaultDeviceId(true);
  if(!id.has_value()) return 0;
  std::unique_ptr<Xt::Device> device = service->OpenDevice(id.value());
  if(!device->SupportsFormat(Format)) return 0;

  double bufferSize = device->GetBufferSize(Format).current;
  Xt::StreamParams streamParams(true, OnBuffer, nullptr, nullptr);
  Xt::DeviceStreamParams deviceParams(streamParams, Format, bufferSize);
  std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, nullptr);
  stream->Start();
  std::this_thread::sleep_for(std::chrono::seconds(2));
  stream->Stop();

  return 0;
}

• Record from the default input device to a raw audio file.
package xt.sample;

import xt.audio.Enums.XtSample;
import xt.audio.Enums.XtSetup;
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.XtPlatform;
import xt.audio.XtSafeBuffer;
import xt.audio.XtService;
import xt.audio.XtStream;
import java.io.FileOutputStream;

public class CaptureSimple {

    static final XtMix MIX = new XtMix(44100, XtSample.INT24);
    static final XtChannels CHANNELS = new XtChannels(1, 0, 0, 0);
    static final XtFormat FORMAT = new XtFormat(MIX, CHANNELS);

    // Normally don't do I/O in the callback.
    static int onBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        var output = (FileOutputStream)user;
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        safe.lock(buffer);
        var input = (byte[])safe.getInput();
        int size = XtAudio.getSampleAttributes(MIX.sample).size;
        output.write(input, 0, buffer.frames * size);
        safe.unlock(buffer);
        return 0;
    }

    public static void main() throws Exception {
        XtStreamParams streamParams;
        XtDeviceStreamParams deviceParams;

        try(XtPlatform platform = XtAudio.init(null, null)) {
            XtSystem system = platform.setupToSystem(XtSetup.CONSUMER_AUDIO);
            XtService service = platform.getService(system);
            if(service == null) return;

            String defaultInput = service.getDefaultDeviceId(false);
            if(defaultInput == null) return;
            try(XtDevice device = service.openDevice(defaultInput)) {
                if(!device.supportsFormat(FORMAT)) return;

                XtBufferSize size = device.getBufferSize(FORMAT);
                streamParams = new XtStreamParams(true, CaptureSimple::onBuffer, null, null);
                deviceParams = new XtDeviceStreamParams(streamParams, FORMAT, size.current);
                try(FileOutputStream recording = new FileOutputStream("xt-audio.raw");
                    XtStream stream = device.openStream(deviceParams, recording);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    stream.start();
                    Thread.sleep(2000);
                    stream.stop();
                }
            }
        }
    }
}
using System;
using System.IO;
using System.Threading;

namespace Xt
{
    public class CaptureSimple
    {
        static readonly XtMix Mix = new XtMix(44100, XtSample.Int24);
        static readonly XtChannels Channels = new XtChannels(1, 0, 0, 0);
        static readonly XtFormat Format = new XtFormat(Mix, Channels);

        // Normally don't do I/O in the callback.
        static int OnBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            var output = (FileStream)user;
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            safe.Lock(buffer);
            var input = (byte[])safe.GetInput();
            int size = XtAudio.GetSampleAttributes(Mix.sample).size;
            if (buffer.frames > 0) output.Write(input, 0, buffer.frames * size);
            safe.Unlock(buffer);
            return 0;
        }

        [STAThread]
        public static void Main()
        {
            XtStreamParams streamParams;
            XtDeviceStreamParams deviceParams;

            using XtPlatform platform = XtAudio.Init(null, IntPtr.Zero);
            XtSystem system = platform.SetupToSystem(XtSetup.ConsumerAudio);
            XtService service = platform.GetService(system);
            if (service == null) return;

            string defaultInput = service.GetDefaultDeviceId(false);
            if(defaultInput == null) return;
            using XtDevice device = service.OpenDevice(defaultInput);
            if (!device.SupportsFormat(Format)) return;

            XtBufferSize size = device.GetBufferSize(Format);
            streamParams = new XtStreamParams(true, OnBuffer, null, null);
            deviceParams = new XtDeviceStreamParams(in streamParams, in Format, size.current);
            using var recording = new FileStream("xt-audio.raw", FileMode.Create, FileAccess.Write);
            using XtStream stream = device.OpenStream(in deviceParams, recording);
            using XtSafeBuffer safe = XtSafeBuffer.Register(stream);
            stream.Start();
            Thread.Sleep(2000);
            stream.Stop();
        }
    }
}
#include <xt/XtAudio.hpp>

#include <thread>
#include <chrono>
#include <fstream>

static Xt::Channels const Channels(1, 0, 0, 0);
static Xt::Mix const Mix(44100, Xt::Sample::Int24);
static Xt::Format const Format(Mix, Channels);

// Normally don't do I/O in the callback.
static uint32_t 
OnBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user) 
{
  auto os = static_cast<std::ofstream*>(user);
  char const* input = static_cast<char const*>(buffer.input);
  int32_t bytes = Xt::Audio::GetSampleAttributes(Mix.sample).size * buffer.frames;
  os->write(input, bytes);
  return 0;
}

int 
CaptureSimpleMain() 
{
  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  Xt::System system = platform->SetupToSystem(Xt::Setup::ConsumerAudio);
  std::unique_ptr<Xt::Service> service = platform->GetService(system);
  if(!service) return 0;  

  std::optional<std::string> id = service->GetDefaultDeviceId(false);
  if(!id.has_value()) return 0;
  std::unique_ptr<Xt::Device> device = service->OpenDevice(id.value());
  if(!device->SupportsFormat(Format)) return 0;

  double bufferSize = device->GetBufferSize(Format).current;
  Xt::StreamParams streamParams(true, OnBuffer, nullptr, nullptr);
  Xt::DeviceStreamParams deviceParams(streamParams, Format, bufferSize);
  std::ofstream recording("xt-audio.raw", std::ios::out | std::ios::binary);
  std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, &recording);
  stream->Start();
  std::this_thread::sleep_for(std::chrono::seconds(2));
  stream->Stop();

  return 0;
}

• Get notified on stream under/overflow.
• Playback using interleaved and non-interleaved buffers.
• Get notified when the stream stops outside of application's control.
• Use channel masks to route a channel to a specific speaker position.
• (Java and .NET): use native buffers to prevent data copying or safe buffers to work with .NET/JVM arrays instead of raw pointers.
package xt.sample;

import com.sun.jna.Native;
import xt.audio.Enums.XtSample;
import xt.audio.Enums.XtSetup;
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.XtPlatform;
import xt.audio.XtSafeBuffer;
import xt.audio.XtService;
import xt.audio.XtStream;

public class RenderAdvanced {

    static float _phase = 0.0f;
    static final float FREQUENCY = 440.0f;
    static final XtMix MIX = new XtMix(44100, XtSample.FLOAT32);

    static float nextSample() {
        _phase += FREQUENCY / MIX.rate;
        if(_phase >= 1.0) _phase = -1.0f;
        return (float)Math.sin(2.0 * _phase * Math.PI);
    }

    // Normally don't do I/O in the callback.
    static void onXRun(XtStream stream, int index, Object user) {
        System.out.println("XRun on device " + index + ".");
    }

    static void onRunning(XtStream stream, boolean running, long error, Object user) {
        String evt = running? "Started": "Stopped";
        System.out.println("Stream event: " + evt + ", new state: " + stream.isRunning() + ".");
        if(error != 0) System.out.println(XtAudio.getErrorInfo(error).toString());
    }

    static void runStream(XtStream stream) throws Exception {
        stream.start();
        Thread.sleep(2000);
        stream.stop();
    }

    static int onInterleavedSafeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        int channels = stream.getFormat().channels.outputs;
        safe.lock(buffer);
        float[] output = (float[])safe.getOutput();
        for(int f = 0; f < buffer.frames; f++) {
            float sample = nextSample();
            for(int c = 0; c < channels; c++) output[f * channels + c] = sample;
        }
        safe.unlock(buffer);
        return 0;
    }

    static int onInterleavedNativeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        int channels = stream.getFormat().channels.outputs;
        int size = XtAudio.getSampleAttributes(MIX.sample).size;
        for(int f = 0; f < buffer.frames; f++) {
            float sample = nextSample();
            for(int c = 0; c < channels; c++)
                buffer.output.setFloat((f * channels + c) * size, sample);
        }
        return 0;
    }

    static int onNonInterleavedSafeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        int channels = stream.getFormat().channels.outputs;
        safe.lock(buffer);
        float[][] output = (float[][])safe.getOutput();
        for(int f = 0; f < buffer.frames; f++) {
            float sample = nextSample();
            for(int c = 0; c < channels; c++) output[c][f] = sample;
        }
        safe.unlock(buffer);
        return 0;
    }

    static int onNonInterleavedNativeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        int channels = stream.getFormat().channels.outputs;
        int size = XtAudio.getSampleAttributes(MIX.sample).size;
        for(int f = 0; f < buffer.frames; f++) {
            float sample = nextSample();
            for(int c = 0; c < channels; c++)
                buffer.output.getPointer(c * Native.POINTER_SIZE).setFloat(f * size, sample);
        }
        return 0;
    }

    public static void main() throws Exception {
        XtStreamParams streamParams;
        XtDeviceStreamParams deviceParams;

        try(XtPlatform platform = XtAudio.init(null, null)) {
            XtSystem system = platform.setupToSystem(XtSetup.CONSUMER_AUDIO);
            XtService service = platform.getService(system);
            if(service == null) return;

            String defaultOutput = service.getDefaultDeviceId(true);
            if(defaultOutput == null) return;
            XtFormat format = new XtFormat(MIX, new XtChannels(0, 0, 2, 0));
            try(XtDevice device = service.openDevice(defaultOutput)) {
                if(!device.supportsFormat(format)) return;
                XtBufferSize size = device.getBufferSize(format);

                System.out.println("Render interleaved, safe buffers...");
                streamParams = new XtStreamParams(true, RenderAdvanced::onInterleavedSafeBuffer, RenderAdvanced::onXRun, RenderAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, format, size.current);
                try(XtStream stream = device.openStream(deviceParams, null);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    runStream(stream);
                }

                System.out.println("Render interleaved, native buffers...");
                streamParams = new XtStreamParams(true, RenderAdvanced::onInterleavedNativeBuffer, RenderAdvanced::onXRun, RenderAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, format, size.current);
                try(XtStream stream = device.openStream(deviceParams, null)) {
                    runStream(stream);
                }

                System.out.println("Render non-interleaved, safe buffers...");
                streamParams = new XtStreamParams(false, RenderAdvanced::onNonInterleavedSafeBuffer, RenderAdvanced::onXRun, RenderAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, format, size.current);
                try(XtStream stream = device.openStream(deviceParams, null);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    runStream(stream);
                }

                System.out.println("Render non-interleaved, native buffers...");
                streamParams = new XtStreamParams(false, RenderAdvanced::onNonInterleavedNativeBuffer, RenderAdvanced::onXRun, RenderAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, format, size.current);
                try(XtStream stream = device.openStream(deviceParams, null)) {
                    runStream(stream);
                }

                System.out.println("Render interleaved, safe buffers (channel 0)...");
                XtFormat sendTo0 = new XtFormat(MIX, new XtChannels(0, 0, 1, 1L << 0));
                streamParams = new XtStreamParams(true, RenderAdvanced::onInterleavedSafeBuffer, RenderAdvanced::onXRun, RenderAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, sendTo0, size.current);
                try(XtStream stream = device.openStream(deviceParams, null);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    runStream(stream);
                }

                System.out.println("Render non-interleaved, native buffers (channel 1)...");
                XtFormat sendTo1 = new XtFormat(MIX, new XtChannels(0, 0, 1, 1L << 1));
                streamParams = new XtStreamParams(false, RenderAdvanced::onNonInterleavedNativeBuffer, RenderAdvanced::onXRun, RenderAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, sendTo1, size.current);
                try(XtStream stream = device.openStream(deviceParams, null)) {
                    runStream(stream);
                }
            }
        }
    }
}
using System;
using System.Threading;

namespace Xt
{
    public class RenderAdvanced
    {
        static float _phase = 0.0f;
        const float Frequency = 440.0f;
        static readonly XtMix Mix = new XtMix(44100, XtSample.Float32);

        static float NextSample()
        {
            _phase += Frequency / Mix.rate;
            if (_phase >= 1.0f) _phase = -1.0f;
            return (float)Math.Sin(2.0 * _phase * Math.PI);
        }

        // Normally don't do I/O in the callback.
        static void OnXRun(XtStream stream, int index, object user)
        => Console.WriteLine("XRun on device " + index + ".");

        static void OnRunning(XtStream stream, bool running, ulong error, object user)
        {
            string evt = running ? "Started" : "Stopped";
            Console.WriteLine("Stream event: " + evt + ", new state: " + stream.IsRunning() + ".");
            if (error != 0) Console.WriteLine(XtAudio.GetErrorInfo(error).ToString());
        }

        static void RunStream(XtStream stream)
        {
            stream.Start();
            Thread.Sleep(2000);
            stream.Stop();
        }

        static int OnInterleavedSafeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            int channels = stream.GetFormat().channels.outputs;
            safe.Lock(in buffer);
            float[] output = (float[])safe.GetOutput();
            for (int f = 0; f < buffer.frames; f++)
            {
                float sample = NextSample();
                for (int c = 0; c < channels; c++) output[f * channels + c] = sample;
            }
            safe.Unlock(buffer);
            return 0;
        }

        static unsafe int OnInterleavedNativeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            int channels = stream.GetFormat().channels.outputs;
            int size = XtAudio.GetSampleAttributes(Mix.sample).size;
            for (int f = 0; f < buffer.frames; f++)
            {
                float sample = NextSample();
                for (int c = 0; c < channels; c++) ((float*)buffer.output)[f * channels + c] = sample;
            }
            return 0;
        }

        static int OnNonInterleavedSafeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            int channels = stream.GetFormat().channels.outputs;
            safe.Lock(buffer);
            float[][] output = (float[][])safe.GetOutput();
            for (int f = 0; f < buffer.frames; f++)
            {
                float sample = NextSample();
                for (int c = 0; c < channels; c++) output[c][f] = sample;
            }
            safe.Unlock(buffer);
            return 0;
        }

        static unsafe int OnNonInterleavedNativeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            int channels = stream.GetFormat().channels.outputs;
            int size = XtAudio.GetSampleAttributes(Mix.sample).size;
            for (int f = 0; f < buffer.frames; f++)
            {
                float sample = NextSample();
                for (int c = 0; c < channels; c++) ((float**)buffer.output)[c][f] = sample;
            }
            return 0;
        }

        [STAThread]
        public static void Main()
        {
            XtStreamParams streamParams;
            XtDeviceStreamParams deviceParams;

            using XtPlatform platform = XtAudio.Init(null, IntPtr.Zero);
            XtSystem system = platform.SetupToSystem(XtSetup.ConsumerAudio);
            XtService service = platform.GetService(system);
            if (service == null) return;

            XtFormat format = new XtFormat(Mix, new XtChannels(0, 0, 2, 0));
            string defaultOutput = service.GetDefaultDeviceId(true);
            if (defaultOutput == null) return;
            using XtDevice device = service.OpenDevice(defaultOutput);
            if (!device.SupportsFormat(format)) return;
            XtBufferSize size = device.GetBufferSize(format);

            Console.WriteLine("Render interleaved, safe buffers...");
            streamParams = new XtStreamParams(true, OnInterleavedSafeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in format, size.current);
            using (XtStream stream = device.OpenStream(in deviceParams, null))
            using (XtSafeBuffer safe = XtSafeBuffer.Register(stream))
                RunStream(stream);

            Console.WriteLine("Render interleaved, native buffers...");
            streamParams = new XtStreamParams(true, OnInterleavedNativeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in format, size.current);
            using (XtStream stream = device.OpenStream(in deviceParams, null))
                RunStream(stream);

            Console.WriteLine("Render non-interleaved, safe buffers...");
            streamParams = new XtStreamParams(false, OnNonInterleavedSafeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in format, size.current);
            using (XtStream stream = device.OpenStream(in deviceParams, null))
            using (XtSafeBuffer safe = XtSafeBuffer.Register(stream))
                RunStream(stream);

            Console.WriteLine("Render non-interleaved, native buffers...");
            streamParams = new XtStreamParams(false, OnNonInterleavedNativeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in format, size.current);
            using (XtStream stream = device.OpenStream(in deviceParams, null))
                RunStream(stream);

            Console.WriteLine("Render interleaved, safe buffers (channel 0)...");
            XtFormat sendTo0 = new XtFormat(Mix, new XtChannels(0, 0, 1, 1L << 0));
            streamParams = new XtStreamParams(true, OnInterleavedSafeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in sendTo0, size.current);
            using (XtStream stream = device.OpenStream(in deviceParams, null))
            using (XtSafeBuffer safe = XtSafeBuffer.Register(stream))
                RunStream(stream);

            Console.WriteLine("Render non-interleaved, native buffers (channel 1)...");
            XtFormat sendTo1 = new XtFormat(Mix, new XtChannels(0, 0, 1, 1L << 1));
            streamParams = new XtStreamParams(false, OnNonInterleavedNativeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in sendTo1, size.current);
            using (XtStream stream = device.OpenStream(in deviceParams, null))
                RunStream(stream);
        }
    }
}
#define _USE_MATH_DEFINES 1
#include <xt/XtAudio.hpp>

#include <cmath>
#include <chrono>
#include <thread>
#include <cstdint>
#include <iostream>

static float _phase = 0.0f;
static float const Frequency = 440.0f;
static Xt::Mix const Mix(44100, Xt::Sample::Float32);

// Normally don't do I/O in the callback.
static void 
OnXRun(Xt::Stream const& stream, int32_t index, void* user) 
{ std::cout << "XRun on device " << index << ".\n"; }

static void
OnRunning(Xt::Stream const& stream, bool running, uint64_t error, void* user)
{ 
  char const* evt = running? "Started": "Stopped";
  std::cout << "Stream event: " << evt << ", new state: " << stream.IsRunning() << ".\n"; 
  if(error != 0) std::cout << Xt::Audio::GetErrorInfo(error) << ".\n";
}

static void 
RunStream(Xt::Stream* stream)
{
  stream->Start();
  std::this_thread::sleep_for(std::chrono::seconds(2));
  stream->Stop();
}

static float 
NextSample()
{
  _phase += Frequency / Mix.rate;
  if (_phase >= 1.0f) _phase = -1.0f;
  return sinf(2.0f * _phase * static_cast<float>(M_PI));
}

static uint32_t 
OnInterleavedBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user)
{
  float* output = static_cast<float*>(buffer.output);
  int32_t channels = stream.GetFormat().channels.outputs;
  int32_t size = Xt::Audio::GetSampleAttributes(Mix.sample).size;
  for(int32_t f = 0; f < buffer.frames; f++) 
  {
    float sample = NextSample();
    for (int32_t c = 0; c < channels; c++) output[f * channels + c] = sample;
  }
  return 0;
}

static uint32_t 
OnNonInterleavedBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user) 
{
  float** output = static_cast<float**>(buffer.output);
  int32_t channels = stream.GetFormat().channels.outputs;
  int32_t size = Xt::Audio::GetSampleAttributes(Mix.sample).size;
  for(int32_t f = 0; f < buffer.frames; f++) 
  {
    float sample = NextSample();
    for(int32_t c = 0; c < channels; c++) output[c][f] = sample;
  }
  return 0;
}

int 
RenderAdvancedMain() 
{
  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  Xt::Format format(Mix, Xt::Channels(0, 0, 2, 0));
  Xt::System system = platform->SetupToSystem(Xt::Setup::ConsumerAudio);
  std::unique_ptr<Xt::Service> service = platform->GetService(system);
  if(!service) return 0;

  std::optional<std::string> id = service->GetDefaultDeviceId(true);
  if(!id.has_value()) return 0;
  std::unique_ptr<Xt::Device> device = service->OpenDevice(id.value());
  if (!device->SupportsFormat(format)) return 0;
  Xt::BufferSize size = device->GetBufferSize(format);

  std::cout << "Render interleaved...\n";
  Xt::StreamParams streamParams(true, OnInterleavedBuffer, OnXRun, OnRunning);
  Xt::DeviceStreamParams deviceParams(streamParams, format, size.current);
  {
    std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, nullptr);
    RunStream(stream.get());
  }

  std::cout << "Render non-interleaved...\n";
  streamParams = Xt::StreamParams(false, OnNonInterleavedBuffer, OnXRun, OnRunning);
  deviceParams = Xt::DeviceStreamParams(streamParams, format, size.current);
  {
    std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, nullptr);
    RunStream(stream.get());
  }

  std::cout << "Render interleaved (channel 0)...\n";
  Xt::Format sendTo0(Mix, Xt::Channels(0, 0, 1, 1ULL << 0));
  streamParams = Xt::StreamParams(true, OnInterleavedBuffer, OnXRun, OnRunning);
  deviceParams = Xt::DeviceStreamParams(streamParams, sendTo0, size.current);
  {
    std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, nullptr);
    RunStream(stream.get());
  }

  std::cout << "Render non-interleaved (channel 1)...\n";
  Xt::Format sendTo1(Mix, Xt::Channels(0, 0, 1, 1ULL << 1));
  streamParams = Xt::StreamParams(false, OnNonInterleavedBuffer, OnXRun, OnRunning);
  deviceParams = Xt::DeviceStreamParams(streamParams, sendTo1, size.current);
  {
    std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, nullptr);
    RunStream(stream.get());
  }

  return 0;
}

• Get notified on stream under/overflow.
• Record using interleaved and non-interleaved buffers.
• Get notified when the stream stops outside of application's control.
• (Java and .NET): use native buffers to prevent data copying or safe buffers to work with .NET/JVM arrays instead of raw pointers.
package xt.sample;

import com.sun.jna.Native;
import com.sun.jna.Pointer;

import xt.audio.Enums.XtSample;
import xt.audio.Enums.XtSetup;
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.XtPlatform;
import xt.audio.XtSafeBuffer;
import xt.audio.XtService;
import xt.audio.XtStream;
import java.io.FileOutputStream;

public class CaptureAdvanced {

    static class Context {
        byte[] intermediate;
        FileOutputStream out;
    }

    static final XtMix MIX = new XtMix(44100, XtSample.INT24);
    static final XtChannels CHANNELS = new XtChannels(2, 0, 0, 0);
    static final XtFormat FORMAT = new XtFormat(MIX, CHANNELS);

    // Normally don't do I/O in the callback.
    static void onXRun(XtStream stream, int index, Object user) {
        System.out.println("XRun on device " + index + ".");
    }

    static void onRunning(XtStream stream, boolean running, long error, Object user) {
        String evt = running? "Started": "Stopped";
        System.out.println("Stream event: " + evt + ", new state: " + stream.isRunning() + ".");
        if(error != 0) System.out.println(XtAudio.getErrorInfo(error).toString());
    }

    static int getBufferSize(int channels, int frames) {
        int size = XtAudio.getSampleAttributes(MIX.sample).size;
        return channels * frames * size;
    }

    static void runStream(XtStream stream) throws Exception {
        stream.start();
        Thread.sleep(2000);
        stream.stop();
    }

    // Normally don't do I/O in the callback.
    static int onInterleavedSafeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        var out = (FileOutputStream)user;
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        int bytes = getBufferSize(CHANNELS.inputs, buffer.frames);
        safe.lock(buffer);
        out.write((byte[])safe.getInput(), 0, bytes);
        safe.unlock(buffer);
        return 0;
    }

    // Normally don't do I/O in the callback.
    static int onInterleavedNativeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        var ctx = (Context)user;
        int bytes = getBufferSize(CHANNELS.inputs, buffer.frames);
        buffer.input.read(0, ctx.intermediate, 0, bytes);
        ctx.out.write(ctx.intermediate, 0, bytes);
        return 0;
    }

    // Normally don't do I/O in the callback.
    static int onNonInterleavedSafeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        var out = (FileOutputStream)user;
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        int size = XtAudio.getSampleAttributes(MIX.sample).size;
        safe.lock(buffer);
        for(int f = 0; f < buffer.frames; f++)
            for(int c = 0; c < CHANNELS.inputs; c++)
                out.write(((byte[][])safe.getInput())[c], f * size, size);
        safe.unlock(buffer);
        return 0;
    }

    // Normally don't do I/O in the callback.
    static int onNonInterleavedNativeBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        var ctx = (Context)user;
        int size = XtAudio.getSampleAttributes(MIX.sample).size;
        for(int f = 0; f < buffer.frames; f++)
            for(int c = 0; c < CHANNELS.inputs; c++) {
                Pointer channel = buffer.input.getPointer(c * Native.POINTER_SIZE);
                channel.read(f * size, ctx.intermediate, 0, size);
                ctx.out.write(ctx.intermediate, 0, size);
            }
        return 0;
    }

    public static void main() throws Exception {
        XtStreamParams streamParams;
        XtDeviceStreamParams deviceParams;

        try(XtPlatform platform = XtAudio.init(null, null)) {
            XtSystem system = platform.setupToSystem(XtSetup.CONSUMER_AUDIO);
            XtService service = platform.getService(system);
            if(service == null) return;

            String defaultInput = service.getDefaultDeviceId(false);
            if(defaultInput == null) return;
            try(XtDevice device = service.openDevice(defaultInput)) {
                if(!device.supportsFormat(FORMAT)) return;
                XtBufferSize size = device.getBufferSize(FORMAT);

                System.out.println("Capture interleaved, safe buffers...");
                streamParams = new XtStreamParams(true, CaptureAdvanced::onInterleavedSafeBuffer, CaptureAdvanced::onXRun, CaptureAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, FORMAT, size.current);
                try(FileOutputStream recording = new FileOutputStream("xt-audio-interleaved-safe.raw");
                    XtStream stream = device.openStream(deviceParams, recording);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    runStream(stream);
                }

                System.out.println("Capture interleaved, native buffers...");
                streamParams = new XtStreamParams(true, CaptureAdvanced::onInterleavedNativeBuffer, CaptureAdvanced::onXRun, CaptureAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, FORMAT, size.current);
                Context context = new Context();
                try(FileOutputStream recording = new FileOutputStream("xt-audio-interleaved-native.raw");
                    XtStream stream = device.openStream(deviceParams, context)) {
                    context.out = recording;
                    context.intermediate = new byte[getBufferSize(CHANNELS.inputs, stream.getFrames())];
                    runStream(stream);
                }

                System.out.println("Capture non-interleaved, safe buffers...");
                streamParams = new XtStreamParams(false, CaptureAdvanced::onNonInterleavedSafeBuffer, CaptureAdvanced::onXRun, CaptureAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, FORMAT, size.current);
                try(FileOutputStream recording = new FileOutputStream("xt-audio-non-interleaved-safe.raw");
                    XtStream stream = device.openStream(deviceParams, recording);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    runStream(stream);
                }

                System.out.println("Capture non-interleaved, native buffers...");
                context = new Context();
                streamParams = new XtStreamParams(false, CaptureAdvanced::onNonInterleavedNativeBuffer, CaptureAdvanced::onXRun, CaptureAdvanced::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, FORMAT, size.current);
                try(FileOutputStream recording = new FileOutputStream("xt-audio-non-interleaved-native.raw");
                    XtStream stream = device.openStream(deviceParams, context)) {
                    context.out = recording;
                    context.intermediate = new byte[getBufferSize(1, stream.getFrames())];
                    runStream(stream);
                }
            }
        }
    }
}
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

namespace Xt
{
    public class CaptureAdvanced
    {
        class Context
        {
            internal byte[] intermediate;
            internal FileStream recording;
        }

        static readonly XtMix Mix = new XtMix(44100, XtSample.Int24);
        static readonly XtChannels Channels = new XtChannels(2, 0, 0, 0);
        static readonly XtFormat Format = new XtFormat(Mix, Channels);

        // Normally don't do I/O in the callback.
        static void OnXRun(XtStream stream, int index, object user)
        => Console.WriteLine("XRun on device " + index + ".");

        static void OnRunning(XtStream stream, bool running, ulong error, object user)
        {
            string evt = running ? "Started" : "Stopped";
            Console.WriteLine("Stream event: " + evt + ", new state: " + stream.IsRunning() + ".");
            if (error != 0) Console.WriteLine(XtAudio.GetErrorInfo(error).ToString());
        }

        static void RunStream(XtStream stream)
        {
            stream.Start();
            Thread.Sleep(2000);
            stream.Stop();
        }

        static int GetBufferSize(int channels, int frames)
        {
            int size = XtAudio.GetSampleAttributes(Mix.sample).size;
            return channels * frames * size;
        }

        // Normally don't do I/O in the callback.
        static int OnInterleavedSafeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            var output = (FileStream)user;
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            int bytes = GetBufferSize(Channels.inputs, buffer.frames);
            safe.Lock(in buffer);
            output.Write((byte[])safe.GetInput(), 0, bytes);
            safe.Unlock(in buffer);
            return 0;
        }

        // Normally don't do I/O in the callback.
        static int OnInterleavedNativeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            var ctx = (Context)user;
            int bytes = GetBufferSize(Channels.inputs, buffer.frames);
            Marshal.Copy(buffer.input, ctx.intermediate, 0, bytes);
            ctx.recording.Write(ctx.intermediate, 0, bytes);
            return 0;
        }

        // Normally don't do I/O in the callback.
        static int OnNonInterleavedSafeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            var output = (FileStream)user;
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            int size = XtAudio.GetSampleAttributes(Mix.sample).size;
            safe.Lock(in buffer);
            for (int f = 0; f < buffer.frames; f++)
                for (int c = 0; c < Channels.inputs; c++)
                    output.Write(((byte[][])safe.GetInput())[c], f * size, size);
            safe.Unlock(in buffer);
            return 0;
        }

        // Normally don't do I/O in the callback.
        static unsafe int OnNonInterleavedNativeBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            var ctx = (Context)user;
            int size = XtAudio.GetSampleAttributes(Mix.sample).size;
            for (int f = 0; f < buffer.frames; f++)
                for (int c = 0; c < Channels.inputs; c++)
                {
                    IntPtr source = new IntPtr(&(((byte**)buffer.input)[c][f * size]));
                    Marshal.Copy(source, ctx.intermediate, 0, size);
                    ctx.recording.Write(ctx.intermediate, 0, size);
                }
            return 0;
        }

        [STAThread]
        public static void Main()
        {
            XtStreamParams streamParams;
            XtDeviceStreamParams deviceParams;

            using XtPlatform platform = XtAudio.Init(null, IntPtr.Zero);
            XtSystem system = platform.SetupToSystem(XtSetup.ConsumerAudio);
            XtService service = platform.GetService(system);
            if (service == null) return;

            string defaultInput = service.GetDefaultDeviceId(false);
            if (defaultInput == null) return;
            using XtDevice device = service.OpenDevice(defaultInput);
            if (!device.SupportsFormat(Format)) return;
            XtBufferSize size = device.GetBufferSize(Format);

            Console.WriteLine("Capture interleaved, safe buffers...");
            streamParams = new XtStreamParams(true, OnInterleavedSafeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in Format, size.current);
            using (FileStream recording = new FileStream("xt-audio-interleaved-safe.raw", FileMode.Create, FileAccess.Write))
            using (XtStream stream = device.OpenStream(in deviceParams, recording))
            using (XtSafeBuffer safe = XtSafeBuffer.Register(stream))
                RunStream(stream);

            Console.WriteLine("Capture interleaved, native buffers...");
            var context = new Context();
            streamParams = new XtStreamParams(true, OnInterleavedNativeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in Format, size.current);
            using (FileStream recording = new FileStream("xt-audio-interleaved-native.raw", FileMode.Create, FileAccess.Write))
            using (XtStream stream = device.OpenStream(in deviceParams, context))
            {
                context.recording = recording;
                context.intermediate = new byte[GetBufferSize(Channels.inputs, stream.GetFrames())];
                RunStream(stream);
            }

            Console.WriteLine("Capture non-interleaved, safe buffers...");
            streamParams = new XtStreamParams(false, OnNonInterleavedSafeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in Format, size.current);
            using (FileStream recording = new FileStream("xt-audio-non-interleaved-safe.raw", FileMode.Create, FileAccess.Write))
            using (XtStream stream = device.OpenStream(in deviceParams, recording))
            using (XtSafeBuffer safe = XtSafeBuffer.Register(stream))
                RunStream(stream);

            Console.WriteLine("Capture non-interleaved, native buffers...");
            context = new Context();
            streamParams = new XtStreamParams(false, OnNonInterleavedNativeBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in Format, size.current);
            using (FileStream recording = new FileStream("xt-audio-non-interleaved-native.raw", FileMode.Create, FileAccess.Write))
            using (XtStream stream = device.OpenStream(in deviceParams, context))
            {
                context.recording = recording;
                context.intermediate = new byte[GetBufferSize(Channels.inputs, stream.GetFrames())];
                RunStream(stream);
            }
        }
    }
}
#include <xt/XtAudio.hpp>

#include <chrono>
#include <thread>
#include <fstream>
#include <iostream>

static Xt::Channels const Channels(2, 0, 0, 0);
static Xt::Mix const Mix(44100, Xt::Sample::Int24);
static Xt::Format const Format(Mix, Channels);

// Normally don't do I/O in the callback.
static void 
OnXRun(Xt::Stream const& stream, int32_t index, void* user) 
{ std::cout << "XRun on device " << index << ".\n"; }

static void
OnRunning(Xt::Stream const& stream, bool running, uint64_t error, void* user)
{ 
  char const* evt = running? "Started": "Stopped";
  std::cout << "Stream event: " << evt << ", new state: " << stream.IsRunning() << ".\n"; 
  if(error != 0) std::cout << Xt::Audio::GetErrorInfo(error) << ".\n";
}

static void 
RunStream(Xt::Stream* stream)
{
  stream->Start();
  std::this_thread::sleep_for(std::chrono::seconds(2));
  stream->Stop();
}

static int32_t 
GetBufferSize(int32_t channels, int32_t frames)
{
  int32_t size = Xt::Audio::GetSampleAttributes(Mix.sample).size;
  return channels * frames * size;
}

// Normally don't do I/O in the callback.
static uint32_t 
OnInterleavedBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user) 
{
  auto output = static_cast<std::ofstream*>(user);
  auto input = static_cast<char const*>(buffer.input);
  int32_t bytes = GetBufferSize(Channels.inputs, buffer.frames);
  output->write(input, bytes);
  return 0;
}

// Normally don't do I/O in the callback.
static uint32_t 
OnNonInterleavedBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user) 
{
  auto output = static_cast<std::ofstream*>(user);  
  auto input = static_cast<char const* const*>(buffer.input);
  int32_t size = Xt::Audio::GetSampleAttributes(Mix.sample).size;
  for(int32_t f = 0; f < buffer.frames; f++)
    for(int32_t c = 0; c < Channels.inputs; c++)
      output->write(&input[c][f * size], size);
  return 0;
}

int 
CaptureAdvancedMain() 
{
  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  Xt::System system = platform->SetupToSystem(Xt::Setup::ConsumerAudio);
  std::unique_ptr<Xt::Service> service = platform->GetService(system);
  if(!service) return 0; 

  std::optional<std::string> id = service->GetDefaultDeviceId(false);
  if(!id.has_value()) return 0;
  std::unique_ptr<Xt::Device> device = service->OpenDevice(id.value());
  if(!device->SupportsFormat(Format)) return 0;
  Xt::BufferSize size = device->GetBufferSize(Format);

  std::cout << "Capture interleaved...\n";
  Xt::StreamParams streamParams(true, OnInterleavedBuffer, OnXRun, OnRunning);
  Xt::DeviceStreamParams deviceParams(streamParams, Format, size.current);
  {
    std::ofstream interleaved("xt-audio-interleaved.raw", std::ios::out | std::ios::binary);
    std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, &interleaved);
    RunStream(stream.get());
  }

  std::cout << "Capture non-interleaved...\n";
  streamParams = Xt::StreamParams(false, OnNonInterleavedBuffer, OnXRun, OnRunning);
  deviceParams = Xt::DeviceStreamParams(streamParams, Format, size.current);
  {
    std::ofstream nonInterleaved("xt-audio-non-interleaved.raw", std::ios::out | std::ios::binary);
    std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, &nonInterleaved);
    RunStream(stream.get());
  }

  return 0;
}

• Use JACK or ASIO for full-duplex operation.
package xt.sample;

import xt.audio.Enums.XtSample;
import xt.audio.Enums.XtServiceCaps;
import xt.audio.Enums.XtSetup;
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.XtPlatform;
import xt.audio.XtSafeBuffer;
import xt.audio.XtService;
import xt.audio.XtStream;

public class FullDuplex {

    // Normally don't do I/O in the callback.
    static void onXRun(XtStream stream, int index, Object user) {
        System.out.println("XRun on device " + index + ".");
    }

    static void onRunning(XtStream stream, boolean running, long error, Object user) {
        String evt = running? "Started": "Stopped";
        System.out.println("Stream event: " + evt + ", new state: " + stream.isRunning() + ".");
        if(error != 0) System.out.println(XtAudio.getErrorInfo(error).toString());
    }

    static int onBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        safe.lock(buffer);
        System.arraycopy(safe.getInput(), 0, safe.getOutput(), 0, buffer.frames * 2);
        safe.unlock(buffer);
        return 0;
    }

    public static void main() throws Exception {
        XtFormat format;
        XtStreamParams streamParams;
        XtDeviceStreamParams deviceParams;
        XtFormat int44100 = new XtFormat(new XtMix(44100, XtSample.INT32), new XtChannels(2, 0, 2, 0));
        XtFormat int48000 = new XtFormat(new XtMix(48000, XtSample.INT32), new XtChannels(2, 0, 2, 0));
        XtFormat float44100 = new XtFormat(new XtMix(44100, XtSample.FLOAT32), new XtChannels(2, 0, 2, 0));
        XtFormat float48000 = new XtFormat(new XtMix(48000, XtSample.FLOAT32), new XtChannels(2, 0, 2, 0));

        try(XtPlatform platform = XtAudio.init(null, null)) {
            XtSystem system = platform.setupToSystem(XtSetup.PRO_AUDIO);
            XtService service = platform.getService(system);
            if(service == null || !service.getCapabilities().contains(XtServiceCaps.FULL_DUPLEX)) return;

            String defaultOutput = service.getDefaultDeviceId(true);
            if(defaultOutput == null) return;
            try(XtDevice device = service.openDevice(defaultOutput)) {
                if(device.supportsFormat(int44100)) format = int44100;
                else if(device.supportsFormat(int48000)) format = int48000;
                else if(device.supportsFormat(float44100)) format = float44100;
                else if(device.supportsFormat(float48000)) format = float48000;
                else return;

                XtBufferSize size = device.getBufferSize(format);
                streamParams = new XtStreamParams(true, FullDuplex::onBuffer, FullDuplex::onXRun, FullDuplex::onRunning);
                deviceParams = new XtDeviceStreamParams(streamParams, format, size.current);
                try(XtStream stream = device.openStream(deviceParams, null);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    stream.start();
                    Thread.sleep(2000);
                    stream.stop();
                }
            }
        }
    }
}
using System;
using System.Threading;

namespace Xt
{
    public class FullDuplex
    {
        // Normally don't do I/O in the callback.
        static void OnXRun(XtStream stream, int index, object user)
        => Console.WriteLine("XRun on device " + index + ".");

        static void OnRunning(XtStream stream, bool running, ulong error, object user)
        {
            string evt = running ? "Started" : "Stopped";
            Console.WriteLine("Stream event: " + evt + ", new state: " + stream.IsRunning() + ".");
            if (error != 0) Console.WriteLine(XtAudio.GetErrorInfo(error).ToString());
        }

        static int OnBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            safe.Lock(in buffer);
            Buffer.BlockCopy(safe.GetInput(), 0, safe.GetOutput(), 0, buffer.frames * 2 * 4);
            safe.Unlock(in buffer);
            return 0;
        }

        [STAThread]
        public static void Main()
        {
            XtFormat format;
            XtStreamParams streamParams;
            XtDeviceStreamParams deviceParams;
            XtFormat int44100 = new XtFormat(new XtMix(44100, XtSample.Int32), new XtChannels(2, 0, 2, 0));
            XtFormat int48000 = new XtFormat(new XtMix(48000, XtSample.Int32), new XtChannels(2, 0, 2, 0));
            XtFormat float44100 = new XtFormat(new XtMix(44100, XtSample.Float32), new XtChannels(2, 0, 2, 0));
            XtFormat float48000 = new XtFormat(new XtMix(48000, XtSample.Float32), new XtChannels(2, 0, 2, 0));

            using XtPlatform platform = XtAudio.Init(null, IntPtr.Zero);
            XtSystem system = platform.SetupToSystem(XtSetup.ProAudio);
            XtService service = platform.GetService(system);
            if (service == null || (service.GetCapabilities() & XtServiceCaps.FullDuplex) == 0) return;

            string defaultId = service.GetDefaultDeviceId(true);
            if (defaultId == null) return;
            using XtDevice device = service.OpenDevice(defaultId);
            if (device.SupportsFormat(int44100)) format = int44100;
            else if (device.SupportsFormat(int48000)) format = int48000;
            else if (device.SupportsFormat(float44100)) format = float44100;
            else if (device.SupportsFormat(float48000)) format = float48000;
            else return;

            XtBufferSize size = device.GetBufferSize(format);
            streamParams = new XtStreamParams(true, OnBuffer, OnXRun, OnRunning);
            deviceParams = new XtDeviceStreamParams(in streamParams, in format, size.current);
            using XtStream stream = device.OpenStream(in deviceParams, null);
            using XtSafeBuffer safe = XtSafeBuffer.Register(stream);
            stream.Start();
            Thread.Sleep(2000);
            stream.Stop();
        }
    }
}
#include <xt/XtAudio.hpp>

#include <thread>
#include <chrono>
#include <cstring>
#include <iostream>

// Normally don't do I/O in the callback.
static void 
OnXRun(Xt::Stream const& stream, int32_t index, void* user) 
{ std::cout << "XRun on device " << index << ".\n"; }

static uint32_t 
OnBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user) 
{
  int32_t bytes = buffer.frames * 2 * 4;
  std::memcpy(buffer.output, buffer.input, bytes);
  return 0;
}

static void
OnRunning(Xt::Stream const& stream, bool running, uint64_t error, void* user)
{ 
  char const* evt = running? "Started": "Stopped";
  std::cout << "Stream event: " << evt << ", new state: " << stream.IsRunning() << ".\n"; 
  if(error != 0) std::cout << Xt::Audio::GetErrorInfo(error) << ".\n";
}

int 
FullDuplexMain() 
{
  Xt::Format format;
  Xt::Format int44100(Xt::Mix(44100, Xt::Sample::Int32), Xt::Channels(2, 0, 2, 0));
  Xt::Format int48000(Xt::Mix(48000, Xt::Sample::Int32), Xt::Channels(2, 0, 2, 0));
  Xt::Format float44100(Xt::Mix(44100, Xt::Sample::Float32), Xt::Channels(2, 0, 2, 0));
  Xt::Format float48000(Xt::Mix(48000, Xt::Sample::Float32), Xt::Channels(2, 0, 2, 0));

  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  Xt::System system = platform->SetupToSystem(Xt::Setup::ProAudio);
  std::unique_ptr<Xt::Service> service = platform->GetService(system);
  if(!service || (service->GetCapabilities() & Xt::ServiceCapsFullDuplex) == 0) return 0;

  std::optional<std::string> id = service->GetDefaultDeviceId(true);
  if(!id.has_value()) return 0;
  std::unique_ptr<Xt::Device> device = service->OpenDevice(id.value());

  if(device->SupportsFormat(int44100)) format = int44100;
  else if(device->SupportsFormat(int48000)) format = int48000;
  else if(device->SupportsFormat(float44100)) format = float44100;
  else if(device->SupportsFormat(float48000)) format = float48000;
  else return 0;

  double bufferSize = device->GetBufferSize(format).current;
  Xt::StreamParams streamParams(true, OnBuffer, OnXRun, OnRunning);
  Xt::DeviceStreamParams deviceParams(streamParams, format, bufferSize);
  std::unique_ptr<Xt::Stream> stream = device->OpenStream(deviceParams, nullptr);
  stream->Start();
  std::this_thread::sleep_for(std::chrono::seconds(2));
  stream->Stop();

  return 0;
}

• Combine any number of input and output devices into a single stream.
package xt.sample;

import xt.audio.Enums.XtSample;
import xt.audio.Enums.XtServiceCaps;
import xt.audio.Enums.XtSetup;
import xt.audio.Enums.XtSystem;
import xt.audio.Structs.XtAggregateDeviceParams;
import xt.audio.Structs.XtAggregateStreamParams;
import xt.audio.Structs.XtBuffer;
import xt.audio.Structs.XtChannels;
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.XtPlatform;
import xt.audio.XtSafeBuffer;
import xt.audio.XtService;
import xt.audio.XtStream;

public class Aggregate {

    // Normally don't do I/O in the callback.
    static void onXRun(XtStream stream, int index, Object user) {
        System.out.println("XRun on device " + index + ".");
    }

    static void onRunning(XtStream stream, boolean running, long error, Object user) {
        String evt = running? "Started": "Stopped";
        System.out.println("Stream event: " + evt + ", new state: " + stream.isRunning() + ".");
        if(error != 0) System.out.println(XtAudio.getErrorInfo(error).toString());
    }

    static int onBuffer(XtStream stream, XtBuffer buffer, Object user) throws Exception {
        XtSafeBuffer safe = XtSafeBuffer.get(stream);
        safe.lock(buffer);
        int count = buffer.frames * stream.getFormat().channels.inputs;
        System.arraycopy(safe.getInput(), 0, safe.getOutput(), 0, count);
        safe.unlock(buffer);
        return 0;
    }

    public static void main() throws Exception {

        XtAggregateStreamParams aggregateParams;
        XtMix mix = new XtMix(48000, XtSample.INT16);
        XtFormat inputFormat = new XtFormat(mix, new XtChannels(2, 0, 0, 0));
        XtFormat outputFormat = new XtFormat(mix, new XtChannels(0, 0, 2, 0));

        try(XtPlatform platform = XtAudio.init(null, null)) {
            XtSystem system = platform.setupToSystem(XtSetup.SYSTEM_AUDIO);
            XtService service = platform.getService(system);
            if(service == null || !service.getCapabilities().contains(XtServiceCaps.AGGREGATION)) return;

            String defaultInput = service.getDefaultDeviceId(false);
            String defaultOutput = service.getDefaultDeviceId(true);
            if(defaultInput == null || defaultOutput == null) return;

            try(XtDevice input = service.openDevice(defaultInput);
                XtDevice output = service.openDevice(defaultOutput)) {
                if(!input.supportsFormat(inputFormat)) return;
                if(!output.supportsFormat(outputFormat)) return;

                XtAggregateDeviceParams[] deviceParams = new XtAggregateDeviceParams[2];
                deviceParams[0] = new XtAggregateDeviceParams(input, inputFormat.channels, 30.0);
                deviceParams[1] = new XtAggregateDeviceParams(output, outputFormat.channels, 30.0);
                XtStreamParams streamParams = new XtStreamParams(true, Aggregate::onBuffer, Aggregate::onXRun, Aggregate::onRunning);
                aggregateParams = new XtAggregateStreamParams(streamParams, deviceParams, 2, mix, output);
                try(XtStream stream = service.aggregateStream(aggregateParams, null);
                    XtSafeBuffer safe = XtSafeBuffer.register(stream)) {
                    stream.start();
                    Thread.sleep(2000);
                    stream.stop();
                }
            }
        }
    }
}
using System;
using System.Threading;

namespace Xt
{
    public class Aggregate
    {
        // Normally don't do I/O in the callback.
        static void OnXRun(XtStream stream, int index, object user)
        => Console.WriteLine("XRun on device " + index + ".");

        static void OnRunning(XtStream stream, bool running, ulong error, object user)
        {
            string evt = running ? "Started" : "Stopped";
            Console.WriteLine("Stream event: " + evt + ", new state: " + stream.IsRunning() + ".");
            if (error != 0) Console.WriteLine(XtAudio.GetErrorInfo(error).ToString());
        }

        static int OnBuffer(XtStream stream, in XtBuffer buffer, object user)
        {
            XtSafeBuffer safe = XtSafeBuffer.Get(stream);
            safe.Lock(buffer);
            XtFormat format = stream.GetFormat();
            XtAttributes attrs = XtAudio.GetSampleAttributes(format.mix.sample);
            int bytes = buffer.frames * stream.GetFormat().channels.inputs * attrs.size;
            Buffer.BlockCopy(safe.GetInput(), 0, safe.GetOutput(), 0, bytes);
            safe.Unlock(buffer);
            return 0;
        }

        [STAThread]
        public static void Main()
        {
            XtAggregateStreamParams aggregateParams;
            XtMix mix = new XtMix(48000, XtSample.Int16);
            XtFormat inputFormat = new XtFormat(mix, new XtChannels(2, 0, 0, 0));
            XtFormat outputFormat = new XtFormat(mix, new XtChannels(0, 0, 2, 0));

            using XtPlatform platform = XtAudio.Init(null, IntPtr.Zero);
            XtSystem system = platform.SetupToSystem(XtSetup.SystemAudio);
            XtService service = platform.GetService(system);
            if (service == null || (service.GetCapabilities() & XtServiceCaps.Aggregation) == 0) return;

            string defaultInput = service.GetDefaultDeviceId(false);
            if (defaultInput == null) return;
            using XtDevice input = service.OpenDevice(defaultInput);
            if (!input.SupportsFormat(inputFormat)) return;

            string defaultOutput = service.GetDefaultDeviceId(true);
            if (defaultOutput == null) return;
            using XtDevice output = service.OpenDevice(defaultOutput);
            if (!output.SupportsFormat(outputFormat)) return;

            XtAggregateDeviceParams[] deviceParams = new XtAggregateDeviceParams[2];
            deviceParams[0] = new XtAggregateDeviceParams(input, in inputFormat.channels, 30.0);
            deviceParams[1] = new XtAggregateDeviceParams(output, in outputFormat.channels, 30.0);
            XtStreamParams streamParams = new XtStreamParams(true, OnBuffer, OnXRun, OnRunning);
            aggregateParams = new XtAggregateStreamParams(in streamParams, deviceParams, 2, mix, output);
            using XtStream stream = service.AggregateStream(in aggregateParams, null);
            using XtSafeBuffer safe = XtSafeBuffer.Register(stream);
            stream.Start();
            Thread.Sleep(2000);
            stream.Stop();
        }
    }
}
#include <xt/XtAudio.hpp>

#include <chrono>
#include <thread>
#include <cstdint>
#include <cstring>
#include <iostream>

// Normally don't do I/O in the callback.
static void 
OnXRun(Xt::Stream const& stream, int32_t index, void* user) 
{ std::cout << "XRun on device " << index << ".\n"; }

static void
OnRunning(Xt::Stream const& stream, bool running, uint64_t error, void* user)
{ 
  char const* evt = running? "Started": "Stopped";
  std::cout << "Stream event: " << evt << ", new state: " << stream.IsRunning() << ".\n"; 
  if(error != 0) std::cout << Xt::Audio::GetErrorInfo(error) << ".\n";
}

static uint32_t 
OnBuffer(Xt::Stream const& stream, Xt::Buffer const& buffer, void* user) 
{
  Xt::Format const& format = stream.GetFormat();
  Xt::Attributes attrs = Xt::Audio::GetSampleAttributes(format.mix.sample);
  int32_t bytes = buffer.frames * format.channels.inputs * attrs.size;
  std::memcpy(buffer.output, buffer.input, bytes);
  return 0;
}

int 
AggregateMain()
{
  Xt::Mix mix(48000, Xt::Sample::Int16);
  Xt::Format inputFormat(mix, Xt::Channels(2, 0, 0, 0));
  Xt::Format outputFormat(mix, Xt::Channels(0, 0, 2, 0));
  
  std::unique_ptr<Xt::Platform> platform = Xt::Audio::Init("", nullptr);
  Xt::System system = platform->SetupToSystem(Xt::Setup::SystemAudio);
  std::unique_ptr<Xt::Service> service = platform->GetService(system);
  if(!service || (service->GetCapabilities() & Xt::ServiceCapsAggregation) == 0) return 0;

  std::optional<std::string> defaultInput = service->GetDefaultDeviceId(false);
  if(!defaultInput.has_value()) return 0;
  std::unique_ptr<Xt::Device> input = service->OpenDevice(defaultInput.value());
  if(!input->SupportsFormat(inputFormat)) return 0;

  std::optional<std::string> defaultOutput = service->GetDefaultDeviceId(true);
  if(!defaultOutput.has_value()) return 0;
  std::unique_ptr<Xt::Device> output = service->OpenDevice(defaultOutput.value());
  if(!output->SupportsFormat(outputFormat)) return 0;

  Xt::AggregateDeviceParams deviceParams[2];
  deviceParams[0] = Xt::AggregateDeviceParams(input.get(), inputFormat.channels, 30.0);
  deviceParams[1] = Xt::AggregateDeviceParams(output.get(), outputFormat.channels, 30.0);
  Xt::StreamParams streamParams(true, OnBuffer, OnXRun, OnRunning);
  Xt::AggregateStreamParams aggregateParams(streamParams, deviceParams, 2, mix, output.get());
  std::unique_ptr<Xt::Stream> stream = service->AggregateStream(aggregateParams, nullptr);
  stream->Start();
  std::this_thread::sleep_for(std::chrono::seconds(2));
  stream->Stop();
  return 0;
}