Thin Audio Gateway

Introduction

Thin Audio Gateway is a virtual audio interface driver similar to Virtual Audio Cable (VAC). While VAC creates two-sided virtual audio transports called "virtual cables", to transfer audio data between any applications, Thin Audio Gateway creates independent pins/endpoints that are served by the "companion" application called host. The host is responsible for all audio processing for these pins.

Key differences between Virtual Audio Cable and Thin Audio Gateway:

Property Virtual Audio Cable Thin Audio Gateway
Supported OS Windows XP, 2003, Vista, 7, 8, 8.1, 10, 11 Windows 7, 8, 8.1, 10, 11
Can work without the host Yes No
KS protocol support Legacy (chained buffers), RT Audio (cyclic buffer) RT Audio (cyclic buffer)
Audio data transfer protocol for the host KS, WASAPI, DirectSound, MME, etc. Direct buffer access
Stream clock Internal (can be controlled via the API) Provided by the host
Typical latency Less than 10 ms Less than 3 ms

Thin Audio Gateway is a WDM/KS (Kernel Streaming) kernel-mode driver. It creates one or more virtual lines exposed as waveform audio pins (playback and/or recording). Windows Audio Subsystem maps these pins to high-level endpoints ("Microphone", "Line In", "Speakers", "Headphones" etc.). All endpoints are accessible via any high-level audio interface: WASAPI, MME, DirectSound, Media Foundation etc. KS pins can be directly accessed by KS-aware applications supporting RT Audio (WaveRT) protocol.

From the driver's point of view, host application acts as a hardware audio adapter. If the host application is not running, all pins exposed by the driver are marked as unplugged/unconnected. When the host application is started, it connects to the driver and establishes data pipes for the virtual lines, then the appropriate pins become plugged/connected and ready for the clients. For each pin, the driver creates a circular (ring) buffer and maps it into host application's address space.

When an audio client (Windows Audio Subsystem or KS-aware application) connects to a pin to play or record audio, the driver notifies host application, and then translates each client's request to the application through the appropriate "data pipe". When the client requests a circular data buffer, the driver maps the host buffer to the client, so both host and client buffers share the same part of RAM. Therefore, host application reads audio data from client's playback buffer or writes data to client's recording buffer directly, with no intermediate buffering and/or copying. After a portion of data is read or written, host application notifies the driver, and the driver reflects buffer position movement to the client.

Data transfer timing (clocking) is provided solely by the host application. The driver does not affect the data flow, nor the timing.

Therefore, the driver has nothing to do without its host, just being an interface between the host and the clients, so the product is called "thin". As in the case of a removable device, the driver itself does nothing if the device is not connected.

Basic terms

Virtual line

A virtual line consists of a standard Windows audio pin/endpoint and the supporting data structures. On the driver's level, a line is represented by a pair of Kernel Streaming filters (Wave and Topology) that expose some KS pins available for audio clients. Audio data transfer and stream control are performed through Wave pin, volume control (if any) is performed through Topology pin. For each Wave pin, System Audio Engine exposes the appropriate high-level endpoint.

Virtual line has the following parameters:

Audio client

An audio client is a process that connects to an audio endpoint. In a client-server model, such a process becomes a client of the entity serving the endpoint.

Low-level endpoints (Kernel Streaming pins) are served by WDM/KS drivers. When a process connects to a pin, it becomes a client of the driver serving the pin. Audio applications that can use KS pins directly, bypassing Windows Audio Subsystem, are called KS-aware.

High-level endpoints created by Windows Audio Subsystem, are served by System Audio Engine, so every process using such endpoints via WASAPI, MME and other high-level audio interfaces, becomes clients of the Engine. To serve a high-level endpoint, the Engine connects to the appropriate KS pin, becoming a client of the appropriate KS driver.

Audio format

An audio format is a combination of most important parameters of a PCM audio stream: sampling rate, bits per sample, number of channels, number of bytes per sample container and channel configuration/mapping mask. These parameters, among some additional ones, are part of the WAVEFORMATEXTENSIBLE descriptor defined in Windows SDK.

Samples in the container are left-aligned (towards the most significant bits).

For "template" format descriptors that describe allowed/supported formats, container size value equal to zero means a default one (a standard size for the appropriate bits per sample). For the actual stream, this value represents actual container size.

In the code and documentation of Thin Audio Gateway, audio formats are represented as "SR/BPS/NC/BPC/CC", where CC is a hexadecimal number, and other values are decimal numbers.

Format range

A format range is the range of audio format parameters, except for the channel configuration. Format ranges are used to narrow the wide range supported by the system (1000 to 384000 samples per second, 8 to 32 bits per sample, 1 to 8 channels, 1 to 4 bytes per sample container), to a range reasonable for particular tasks (for example, 44100-48000 samples per second, 8-16 bits per sample, 1-2 channels, 1-2 bytes per container).

In the code and documentation of Thin Audio Gateway, format ranges are represented as "MinSR-MaxSR/MinBPS-MaxBPS/MinNC-MaxNC/MinBPC-MaxBPC".

Default format

A default format is the audio format reported by the KS Wave filter as "proposed" for the appropriate Wave pin. Windows Audio Subsystem uses this format to choose its shared/default format for the appropriate high-level endpoint. The shared format can be viewed and changed in the system's Audio Properties Applet (mmsys.cpl).

The default format must always be inside the format range.

Driver installation/uninstallation

To install/uninstall the virtual device and Thin Audio Gateway driver, a simple WDM driver manager is provided ("wdmdrvmgr" subdirectory). It requires administrator rights (if started from a non-privileged account, privilege elevation will occur).

The bitness of the installer/uninstaller executable must match the system bitness. 32-bit executable will fail in 64-bit system.

The "instrmv.cmd" script (in the "tools" subdirectory) shows how to use the installer/uninstaller. The parameter is "install" or "remove".

Test driver builds may not have Microsoft attestation-signing, so Windows will not load them in the normal mode. Such drivers can be loaded only in Windows boot mode with integrity checks disabled. To boot Windows in this mode, open the Boot Menu, pressing F8 on the black screen immediately after BIOS/EFI starts OS boot, and choose "Disable driver signature enforcement" from the menu.

Programming interface

The host process (application) connects to the driver using Win32 API functions (CreateFile and DeviceIoControl).

Driver data formats and IOCTL function definitions are defined provided in "DriverApi.h" header (in the "driver" subdirectory).

User-mode helper library simplifies C++ applications working with the driver. The library is provided in the "apilib" project, as a static library. Interface classes are defined in the "userapi.h" header.

The "apidll" project incorporates these classes and their legacy (C language) wrappers. The wrappers are declared in the "legacyapi.h" header.

Buffer sizes and positions are represented in audio frames (blocks of audio data that hold all channel amplitudes sampled at the same time).

User-mode API functions interact with the driver using IOCTL functions (IoCtl_Driver_Xxx and IoCtl_Line_Xxx) passed to DeviceIoControl. 

Data structures

Common data block

The common data block is mapped to both driver's and host's address spaces at the same time, so the host can see most changes in driver and/or filter states directly, without calling API functions.

CommonDataDesc structure:

Drv - driver-side properties
    FI - filter information (render/capture pin's subdevice state)
        Flags - mode flags (FilterFlags namespace)
        FormatRange - format range supported by the Wave pin
        DefaultFormat - default format reported as "proposed" for the Wave pin
        NotificationCode - event notification code for the host
        WaitingForServer - driver is waiting for state change confirmation
        ReservedBufferSize - reserved audio buffer size (bytes)
        MinBufferMcs - minimum duration of the client circular buffer, in microseconds
        HasStream - the filter has a stream (audio client)
        DataOverflows - amount of data overflows summed from all streams
        DataUnderflows - amount of data underflows summed from all streams
    SI - stream information
        Flags - mode flags (StreamFlags namespace)
            Notifications - client uses audio buffer with notifications
            PacketMode - client uses packet mode
            PositionRegister - client uses (maps) driver's position register
        Fmt - current audio format (WAVEFORMATEXTENSIBLE structure)
        State - KS stream state (Stop, Acquire, Pause, Run)
        BufferFrames - circular buffer size in audio frames
        FramesBuffered - number of frames buffered by the client (accessible to the host for read/write)
        ClientPos - client's buffer position
        DataOverflows - number of audio data overflows
        DataUnderflows - number of audio data underflows
Host - host-side properties
    Buffer - circular audio buffer address in host address space
    DevicePos - device stream position in the buffer (in frames)
    DevicePosBytes - device stream position for the client (in bytes)
    Status - status code (STATUS_*, defined in ntstatus.h)
    Ready - host application is ready to accept a client connection

Independent functions

Create driver interface object

HRESULT CreateDriver (TagDriver * & Obj, GUID const & ClassId);

The ClassId parameter specifies driver interface class identifier. Unique Product GUID is used for this purpose.

In case of successful completion, the function returns a dynamically allocated interface object. To delete the object, use the appropriate method. Using the delete operator is dangerous because new and delete operators of the library and host application can have different implementation.

This function does not actually access the driver, it just creates a data structure for it.

Create host interface object

HRESULT CreateHost (
  TagHost * & Obj, TagDriver const * const Drv,
  UINT const LineId, bool const Capture,
  CallbackInterface * const CB
);

Creates a capture/render host object associated with the specified driver object.

Callback interface object class must be inherited from the CallbackInterface interface, and must implement virtual CopyData method (see EventLoop method for details).

In case of successful completion, the function returns dynamically allocated interface object. To delete the object, use the appropriate method. Using operator delete is dangerous because new and delete operators of the library and host application can have different implementation.

Set global debug print level

void SetGlobalDebugPrintLevel (UINT Level);

Sets global (common) level for debug messages sent to system's debug channel (OutputDebugString).

Only debug builds export this function.

TagDriver interface

This interface represents a driver connection and implements driver operations common for all virtual lines. Interface object should be created by CreateDriver. The interface has the following methods:

Find the driver

HRESULT Find ();

This function finds driver interface registered in the system, if any. Successful completion means that the driver is loaded by the system and is available for connection. Driver interface path is saved and can be retrieved. Otherwise, the appropriate error code is returned.

Open driver connection (connect to the driver)

HRESULT Open ();

This function opens (establishes) a connection to the driver, calling CreateFile with the driver interface. If success, subsequent communication is possible. 

Reques driver information

HRESULT GetInfo (DriverInfo & DI) const;

This function returns some information about the driver:

VerMajor - major version number
VerMinor - minor version number
VerRev - revision number
VerBuild - build number
FormatRange - driver's common format range
NumLines - number of virtual lines created
Flags - driver flags

Requesting a list of virtual lines

HRESULT GetLineList (VirtualLineDesc * Buffer, LocSize Size, UINT & NumLines) const;

This function fills the buffer with an array of VirtualLineDesc structures. Each structure describes a single virtual line. The buffer size must be at least the number of lines created by the driver, multiplied to sizeof (VirtualLineDesc). The number of lines is returned to NumLines.

Create a virtual line

HRESULT CreateLine (VirtualLineDesc const & Desc) const;

This function creates a new virtual line with the parameters described by Desc.

Id - identifier, must be nonzero and unique among the lines.
Capture - direction.
Type - line type (one of VLT_xxx constants). Currently the driver supports Microphone, Speakers, Headphones, Line Connector and S/PDIF Interface.
KsName - kernel streaming name.
EpName - high-level endpoint name. If an empty string is specified, the system names the endpoint in accordance with its type (Microphone, Headphones, Line etc.). For speaker-type lines (VLT_Speaker, KSNODETYPE_SPEAKER) endpoint name doesn't make sense because the system does not use the name provided by the driver, so the name string should be empty.

Therefore, it makes no sense to create more than a single speaker-type line because all of these endpoints will be automatically named "Speakers", so all these lines will have the same endpoint name. Users will not be be able to distinguish them, except by renaming them manually.

Delete a virtual line

HRESULT DeleteLine (UINT Id) const;

This function deletes the specified line. The line must have no host connection (no open pipe exists, so the line is unplugged/unconnected).

If the driver is configured to create pre-defined lines on startup, they can be deleted as well.

Check if driver interface is found (the driver is loaded)

bool IsFound () const;

Returns true if the driver is loaded and its interface is found.

Check if driver interface is open (driver connection is established)

bool IsOpen () const;

Returns true if driver connection is established.

Query driver interface path

PCTSTR GetInterface () const;

Returns driver interface path if found. Otherwise, returns an empty string. Driver interface path is passed to CreateFile to establish driver connection.

Query driver interface class ID

GUID const & GetClassId () const;

Returns driver interface class identifier (product identifier).

Create a pipe for the virtual line

HRESULT CreatePipe (TagPipe * & Pipe, UINT LineId, bool Capture);

This function creates a "pipe" interface used for host communication with the specified virtual line.

"LineId" specifies line identifier. "Capture" specifies line/pipe direction. Through a capture pipe, host application sends audio data to the Wave capture pin (for example, microphone). Through a render pipe, host application receives audio data from the Wave render pin (for example, speakers).

In case of successful completion, CreatePipe returns dynamically allocated interface object. To delete the object, use Delete method. Using operator delete is dangerous because new and delete operators of the library and host application can have different implementation.

Issue a control request to the driver

HRESULT ControlRequest (
  UINT Func,
  DriverReq & Req, LocSize ReqSize,
  void * Data = nullptr, LocSize DataSize = 0,
  LocSize * RetSize = nullptr
) const;

This function executes DeviceIoControl for the driver connection, passing the request (a primitive) to the driver, and possibly returning some data.

"Func" specifies driver's function code (defined in DriverApi.h), for example, IoCtl_Driver_GetInfo.

"Req" and "ReqSize" specify the request descriptor and its size. All driver request descriptors are derived from DriverReq.

"Data" and "DataSize" specify optional buffer for the data returned by the driver. For example, IoCtl_Driver_GetInfo function fills the buffer with DriverInfo structure.

"RetSize" specifies an optional pointer to a variable that receives actual size of the returned data (in bytes), if the actual amount is less than DataSize.

Most of defined driver functions can be accessed via TagDriver methods described here.

Close (release) driver interface

HRESULT Close ();

Closes driver connection, if established.

Forget driver interface

void Forget ();

Clears driver interface, if found.

Set debug output level offset

void SetDebugPrintLevelOffset (int Offset);

This function sets debug output level offset relative to the global one.

Only debug builds export this function.

Delete (destroy) driver interface object

void Delete ();

Deletes driver interface object. If the driver connection was established, it is closed.

TagPipe interface

This interface serves host-to-line communications (a "pipe").

Open the pipe

HRESULT Open ();

Establishes connection with the specified pipe.

Request common data block address

CommonDataDesc & GetCommonData ();

Returns the address of the common data block mapped to the kernel and user address space at the same time, and shared between the driver and the host.

Query if the pipe direction is capture

bool IsCapt () const;

Query if the pipe is open and ready

bool IsOpen () const;

Query pipe direction name

PCTSTR GetDir () const;

Returns "capture" or "render".

Set audio format range supported by the line's pin

HRESULT SetFormatRange (FormatRangeDesc const & FRD) const;

This function sets a format range supported by the Wave pin.

FormatRangeDesc contains minimum and maximum values of main format parameters: sampling rate, bits per sample, number of channels and bytes per container.

If the value of bytes per container is 0, it means a default size for the specified sample bitness.

Format ranges supported by the lines must be within global driver's range.

To change line/pin format range, the line/pin must be idle (have no client/stream).

If the current shared format of the appropriate audio endpoint (it can be set in endpoint properties) is outside of the range, Windows Audio Subsystem will not serve the endpoint until new shared format is explicitly set. This behavior is by the system's design.

Debug builds of driver's user-modeinterface code show audio formats in form "SR/BPS(BPC)/NC(CC)", where SR stands for Sampling Rate, BPS - for Bytes Per Sample, NC - for Number of Channels, BPC - for Bytes Per Container, and CC - for Channel Configuration (SPEAKER_* and KSAUDIO_SPEAKER_* constants).

Set default audio format for clients

HRESULT SetDefaultFormat (WaveFormatDesc const & FD) const;

This function sets a default format for the line (and for the appropriate Wave pin).

Set minimum buffer length/duration for clients

HRESULT SetMinimumBufferLength (UINT Mcs);

Mcs - minimum number of microseconds in client's circular buffer.

If circular buffer size requested by the client for the appropriate audio format, is smaller than the length/duration specified, the driver creates a buffer with the given minimum duration (actual size is calculated using format parameters). Actual buffer size is returned to the client.

Warning: some versions of Windows Audio Subsystem may fail high-level interface (WASAPI, MME etc.) operations if the underlying KS audio driver returns actual buffer size greater than requested. Usually, Windows Audio Subsystem requests 20 ms buffer (960 frames for 48000 samples per second). 

Pass notification event object handle to the driver

HRESULT SetNotificationEvent (HANDLE Event);

Event object must be created by CreateEvent function. It is recommended to set bManualReset parameter to false to avoid losing quickly arising events.

The driver notifies host application about client state changes:

A single event object is used to inform the host about all event types. Common Data Block provides actual state information.

Notifying the host about an event, the driver suspends client code execution until the host responds to the event. So there is only a single event at every moment, and all events are processed synchonously.

Notify the driver (confirm the event)

HRESULT Notify (NTSTATUS Result);

Notifies the driver that the host has finished event processing. The specified status code is returned to the client (values other than STATUS_SUCCESS mean an error). 

Advance current device/stream position in client's audio buffer

HRESULT AdvanceDevicePosition (UINT Frames);

A number of frames processed by the host is specified in the parameter. The interface updates Host.DevicePos and Host.DevicePosBytes fields in the Common Data Block.

Reset device/stream position

HRESULT ResetDevicePosition ();

Current buffer position is reset to zero.

Current position is also reset by the driver when the stream comes to the Stop state. 

Issue a control request to the pipe

HRESULT ControlRequest (
  UINT Func,
  DriverReq & Req, LocSize ReqSize,
  void * Data = nullptr, LocSize DataSize = 0,
  LocSize * RetSize = nullptr
) const;

Can be used to issue a control request (a primitive) to the pipe. The function is similar to TagDriver::ControlRequest, but is directed only to the appropriate pipe. Most defined pipe functions can be accessed via dedicated TagPipe methods described here.

Set debug output level offset

void SetDebugPrintLevelOffset (int Offset);

This function sets debug output level offset relative to the global one.

Only debug builds export this function.

Close the pipe

void Close ();

Delete pipe object

void Delete ();

Use this method instead of operator delete to avoid incorrect dynamic memory handling. 

TagHost interface

This class implements typical host-side operations, establishing a simple event loop. It is created primarily for demonstration purposes, to simplify incorporating user-mode driver interface into customer's application. Interface methods are described below.

Query if the host direction is capture

bool IsCapt () const;

Returns true for capture hosts, otherwise returns false. 

Query if the host is started

bool IsStarted () const;

Returns true if the host has successfully started with Start and is ready to run its event loop. 

Get host direction name

PCTSTR GetType () const;

Returns either "capture" or "render".

Request pipe's common data block address

CommonDataDesc const & GetCommonData () const;

Start the host

HRESULT Start (
  FormatRangeDesc const * FormatRange,
  WaveFormatDesc const * DefFormat,
  UINT const MinBufMcs
);

FormatRange, if nonzero (not equal to nullptr) describes a format range for the line and its Wave pin.

DefFormat, if nonzero, describes a default format.

MinBufMcs, if nonzero, specifies minimum buffer duration in microseconds. See TagPipe::SetMinimumBufferLength method for details.

Exit the event loop (if running) and stop the host:

HRESULT Stop ();

Requests the termination of the event loop and waits until it is terminated.

To avoid deadlocks, the function must be called from a thread other than the one executing the event loop.

Execute the event loop:

HRESULT EventLoop (UINT PollPeriodMs);

PollPeriodMs specifies the period to check driver/filter state changes.

This method should be called from a separate thread. It will not return until Stop () is called from the main thread(s).

Host's event loop processes all filter/stream event notifications sent by the driver/filter. When the capture host detects audio data available in client's render buffer, or when the render host detects that a room is available in client's capture buffer, it calls CallbackInterface::CopyData method:

LocSize CopyData (void * Addr, LocSize Bytes);

Addr specifies the address in client's circular audio buffer.

Bytes specifies a maximum amount of bytes to read/write.

The application should read a portion of data from or write it to the client's buffer, update its data pointer/counter, and return the amount of bytes actually copied.

If the buffer part represented by Addr:Bytes, is limited only by the upper limit of client's circular buffer, and the client has data/room immediately beyond this portion (i.e. at the beginning of the circular buffer), the host object immediately calls CopyData again with the appropriate parameters.

Delete host object

void Delete ();

Use this method instead of operator delete to avoid incorrect dynamic memory handling. 

Set debug output level offset

void SetDebugPrintLevelOffset (int Offset);

This function sets debug output level offset relative to the global one.

Only debug builds export this function.

Callback interface

This interface is used only by the reference host interface that is provided for demonstration purposes.

Copying audio data

LocSize CopyData (void * Addr, LocSize Bytes);

Addr - address of the data block that represents the next fragment of audio data stream.

Bytes - size of the data block in bytes.

Copies audio data between the circular buffer of the audio client connected to the KS filter, and host process memory. The function is called by the host in response to a change in the position of the client's ring audio buffer. For the capture host connected to the capture virtual line, the data block should be read by the callee. For the render host connected to the render virtual line, the data block should be written by the callee.

Typical scenarios

 Using driver interface (TagDriver)

Using pipe interface (TagPipe)

 Typical implementation of these operations is demonstrated in the reference host interface.

Using host interface (TagHost)

The host interface is provided as a reference, to demonstrate host-to-pipe interactions. It can be used in simple tasks, but you may need to implement your own to achieve the highest possible performance.

Debug output support

Debug builds (_DEBUG macro is defined, _Debug macro value is nonzero) produce some debug message with Win32 OutputDebugString function. These messages can be captured by a debugger or by a special software like Debug View (DbgView).

Every message is associated with an importance level from 0 (less significant) and 9 (severe error). If message level is equal or greater to the actual level, it is printed, otherwise it is suppressed.

Independent functions use the global (basic) message level as the actual one. Each interface object has its own signed print level offset that is added to the global (basic) level to get the actual one, allowing increase/decrease levels of all messages produced by the interface. By default, these offsets are zero.

Global basic debug message level can be set by the SetGlobalDebugPrintLevel function. Offsets for interface objects can be set by their SetDebugPrintLevelOffset methods.

Debug output is controlled by DebugContextC class based on DebugContext structure. These objects are organized to a tree. The root structure is defined statically and represents the global print level and common name of the message source. All child objects are linked to the root as a parent, and represent print level offsets and individual object names.

Please note that root debug context objects located in the interface DLL are independent from the objects located in the main process. If you create all interface objects using DLL calls, they will be linked to the root debug context located in the DLL. If you create the objects, importing them from the static library, they will be linked to the root context located in main process memory (you must place a definition explicitly, see Test Application code how to do it).

Interface DLL

If you don't want to link to the interface library statically, you can use interface DLL (tagapi.dll). It exports TagDriver, TagPipe and TagHost classes, and exports pure C wrappers with stdcall calling convention, for their methods. The wrappers are declared in the "legacyapi.h" header.

The names of method wrappers are the same as original method names, prefixed with Driver_, Pipe_ and Host_. Non-static method wrappers accept class object pointer as a first parameter. Reference-type parameters are replaced by pointer types.

Data copy function for the host should comply with CopyDataFunc type. The "Context" parameter of Host_Create is passed to the function when it is called by the host.

To change global debug print level of the debug build of the DLL, use SetGlobalDebugPrintLevel function.

Best practices

Creating/deleting virtual lines, keep in mind that most audio applications enumerate audio endpoints only on startup and/or when the settings dialog is open. So if you run an application then create/delete virtual lines, the application most likely will use the previous endpoint configuration, and you need to restart it or force it to re-enumerate audio endpoints.

Don't create more virtual lines than really needed. Windows Endpoint Builder and associated services are very slow, so even 10-15 audio endpoints may be processed over a minute, significantly increasing system load.

Test application

The "testapp" subdirectory contains the test application project that implements a simple host application for the driver.

Test application window simulates a simplest MDI interface, creating a child window (a "worker") for every line exposed by the driver.

The "Load from" field specifies a source WAV file containing audio data for the capture/recording line's pin/endpoint (for example, Microphone). The host reads audio data from the file and sends them directly to driver's client process via the common audio buffer. The audio format is read from the file and set using TagPipe::SetFormatRange method.

The "Save to" field specifies a target WAV file to save audio data received from the render/playback line's pin/endpoint (for example, Speakers). Audio format range is specified as a single format by the SR, BPS, NC, BPS and CC fields and set for the render line. Audio data received from the pipe are stored in a secondary circular buffer ("SecondsToRecord" parameter in "params.h" header specifies buffer duration). The buffer is written to the file on application exit.

The "Start" button establishes host-to-driver connection that enables the appropriate pin/endpoint, allows audio clients to connect to them and begins audio data transfer. Each line is served independently.

The "Buffer size" field shows client's circular buffer size, in frames.

The "Frames buf" field shows how many frames are buffered by the client (available for read or write for the host).

The "State" field shows current stream state (Stop, Acquire, Pause, Run). These four states are used in all KS communications.

On the first run of the capture pipe (associated with capture/microphone endpoint), open Audio Properties Applet (press Win-R to open the "Run" dialog and type "mmsys.cpl"), select "Recording" tab, double-click the appropriate endpoint (for example, "Microphone (Thin Audio Gateway)", select "Levels" tab and check the volume level slider. If it is located near the right border, slide it to the left approximately to the 1/5 position. This avoids signal over-amplification by the System Audio Engine.

Building binaries

All projects are created in MS Visual Studio 2008 (9.00). Separate solution/project files are provided for MS VS 2017/2019/2022.

The sources are compatible with MS VC++ Compilers 15.xx-19.xx.

To build binaries outside MS VS, use "build.cmd" files in each project's subdirectory. Edit "tools\setlocalenv.cmd" to set the appropriate paths/versions of MS VC++ (it is a part of MS VS directory tree) and WIndows SDK 10.x.

Customization parameters

Each custom version has its own names and parameters. Before ordering a custom version, please specify them:

Developer's copyright information (Eugene V. Muzychenko) will always be present inside driver binary modules. This information can be viewed only by special utilities; Windows itself does not show it anywhere.

Main changes between versions 1.x and 2.x: