Jump to content
Bilgus

WASAPI Audio Capture w/ loopback (WHAT U HEAR) UDF

Recommended Posts

Allows recording from eCapture(recording) and eRender (playback) devices

Audio capture UDf in autoit allows you to capture 'what you hear' to a wav file using a loopback

Event driven capturing allows your GUI to stay responsive and doesn't use a lot of your cpu

 

Loosely based on https://github.com/mvaneerde/blog/blob/master/loopback-capture/loopback-capture/loopback-capture.cpp

Wav saving inspired by Eukalyptus https://www.autoitscript.com/forum/topic/164700-directsound-udf/

 

UDF WasApi_Capture.au3

Spoiler

UDF

#include <WinAPIConv.au3> ;GUID Conversion
#include <WinAPIProc.au3> ;_WinAPI_CreateEvent
#include <WinAPICom.au3> ;_WinAPI_CoTaskMemFree
#include <WinAPIMisc.au3> ;_WinAPI_StrLen
#include <WinAPIConstants.au3> ;$S_OK, $E_INVALIDARG
; #INDEX# =======================================================================================================================
; Title .........: WASAPI Capture UDF Library for AutoIt3
; AutoIt Version : 3.3.14.5
; Description ...: Audio Client capture library with Loopback using WASAPI COM objects
;                  Windows Vista+
; Author(s) .....: Bilgus 2019
; ===============================================================================================================================

#Region Global Variables and Constants
; #CONSTANTS# ===================================================================================================================
Global Enum $eStream_Close = 0, $eStream_Open, $eStream_StartCapture, $eStream_StopCapture

Global Enum $eSC_iState = 0, $eSC_oIAudioClient, $eSC_oIAudioClient_Loopback, _
        $eSC_oICaptureClient, $eSC_pWFX, $eSC_eDataFlow, $eSC_hEvent_BufferReady, $eSC_sID, $eSC_iElems

Global Enum $eWASInfo_Name = 0, $eWASinfo_Desc, $eWASinfo_Interface, _
        $eWASinfo_SystemName, $eWASInfo_ID, $eWASinfo_GUID, $eWASINFO_ELEMS

Global Enum $eRender, $eCapture, $eAll, $EDataFlow_enum_count

Global Const $WASAPI_EVENT_WAIT_TIMEOUT = 0x102

Global Const $WAVE_FORMAT_PCM = 1
Global Const $WAVE_FORMAT_IEEE_FLOAT = 0x0003
Global Const $WAVE_FORMAT_EXTENSIBLE = 0xFFFE
Global Const $KSDATAFORMAT_SUBTYPE_PCM = "{00000001-0000-0010-8000-00AA00389B71}"
Global Const $KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = "{00000003-0000-0010-8000-00aa00389b71}"

Global Enum $AUDCLNT_SHAREMODE_SHARED = 0, $AUDCLNT_SHAREMODE_EXCLUSIVE
Global $AUDCLNT_STREAMFLAGS_LOOPBACK = 0x020000
Global $AUDCLNT_STREAMFLAGS_EVENTCALLBACK = 0x040000

Global Const $CLSCTX_INPROC_SERVER = 1
Global Const $CLSCTX_INPROC_HANDLER = 2
Global Const $CLSCTX_LOCAL_SERVER = 4
Global Const $CLSCTX_ALL = BitOR($CLSCTX_INPROC_SERVER, $CLSCTX_INPROC_HANDLER, $CLSCTX_LOCAL_SERVER)

Global Const $PKEY_AUDIO_ENDPOINT_SUPPORTS_EVENT_DRIVEN_MODE = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E}"
Global Const $PKEY_DEVICE_INTERFACE_FRIENDLY_NAME = "{026E516E-B814-414B-83CD-856D6FEF4822}, 2"
Global Const $PKEY_DEVICE_FRIENDLY_NAME = "{A45C254E-DF1C-4EFD-8020-67D146A850E0}, 14"
Global Const $PKEY_AUDIO_ENDPOINT_GUID = "{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E}, 4"
Global Const $PKEY_DEVICE_DESCRIPTION = "{A45C254E-DF1C-4EFD-8020-67D146A850E0}, 2"
Global Const $PKEY_SYSTEM_NAME = "{B3F8FA53-0004-438E-9003-51A46E139BFC}, 6"

Global Const $STGM_READ = 0
Global Const $DEVICE_STATE_ACTIVE = 0x00000001

;Global Const $S_OK = 0
;Global Const $E_INVALIDARG = 0x80070057
Global Const $S_FALSE = 1
Global Const $OLE_E_BLANK = 0x80040007

; #CONSTANTS# = COM INTERFACES ==================================================================================================
Global Const $CLSID_MMDeviceEnumerator = "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
Global Const $IID_IMMDeviceEnumerator = "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
;https://docs.microsoft.com/en-us/windows/desktop/api/mmdeviceapi/nn-mmdeviceapi-immdeviceenumerator
Global Const $tagIMMDeviceEnumerator = _
        "EnumAudioEndpoints                     hresult(int;dword;ptr*);" & _
        "GetDefaultAudioEndpoint                hresult(int;int;ptr*);" & _
        "GetDevice                              hresult(wstr;ptr*);" & _
        "RegisterEndpointNotificationCallback   hresult(ptr);" & _
        "UnregisterEndpointNotificationCallback hresult(ptr)"

Global Const $IID_IMMDeviceCollection = "{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}"
;https://docs.microsoft.com/en-us/windows/desktop/api/mmdeviceapi/nn-mmdeviceapi-immdevicecollection
Global Const $tagIMMDeviceCollection = "GetCount hresult(uint*);Item hresult(uint;ptr*)"

Global Const $IID_IMMDevice = "{D666063F-1587-4E43-81F1-B948E807363F}"
;https://docs.microsoft.com/en-us/windows/desktop/api/mmdeviceapi/nn-mmdeviceapi-immdevice
Global Const $tagIMMDevice = _
        "Activate          hresult(struct*;dword;ptr;ptr*);" & _
        "OpenPropertyStore hresult(dword;ptr*);" & _
        "GetId             hresult(ptr*);" & _
        "GetState          hresult(dword*)"

Global Const $IID_IMMEndpoint = "{1BE09788-6894-4089-8586-9A2A6C265AC5}"
;https://docs.microsoft.com/en-us/windows/desktop/api/Mmdeviceapi/nf-mmdeviceapi-immendpoint-getdataflow
Global Const $tagIMMEndpoint = "GetDataFlow hresult(ptr*)"

Global Const $IID_IAudioClient = "{1CB9AD4C-DBFA-4c32-B178-C2F568A703B2}"
;https://docs.microsoft.com/en-us/windows/desktop/api/audioclient/nn-audioclient-iaudioclient
Global Const $tagIAudioClient = _
        "Initialize        hresult(int;uint;int64;int64;ptr;struct*);" & _
        "GetBufferSize     hresult(uint*);" & _
        "GetStreamLatency  hresult(int64*);" & _
        "GetCurrentPadding hresult(uint*);" & _
        "IsFormatSupported hresult(int;ptr;ptr*);" & _
        "GetMixFormat      hresult(uint*);" & _
        "GetDevicePeriod   hresult(int64*;int64*);" & _
        "Start             hresult();" & _
        "Stop              hresult();" & _
        "Reset             hresult();" & _
        "SetEventHandle    hresult(uint);" & _
        "GetService        hresult(struct*;ptr*)"

Global Enum Step *2 $AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 1, _
        $AUDCLNT_BUFFERFLAGS_SILENT, _
        $AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR

Global Const $IID_IAudioCaptureClient = "{C8ADBD64-E71E-48A0-A4DE-185C395CD317}"
;https://docs.microsoft.com/en-us/windows/desktop/api/Audioclient/nn-audioclient-iaudiocaptureclient
Global Const $tagIAudioCaptureClient = _
        "GetBuffer         hresult(ptr*;uint*;uint*;int64*;int64*);" & _
        "ReleaseBuffer     hresult(uint);" & _
        "GetNextPacketSize hresult(uint*);"

Global Const $IID_IAudioClock = "{CD63314F-3FBA-4A1B-812C-EF96358728E7}"
;https://docs.microsoft.com/en-us/windows/desktop/api/audioclient/nn-audioclient-iaudioclock
Global Const $tagIAudioClock = _
        "GetFrequency       hresult(int64*);" & _
        "GetPosition        hresult(int64*;int64*);" & _
        "GetCharacteristics hresult(ptr*);"

Global Const $IID_IPropertyStore = "{886d8eeb-8cf2-4446-8d02-cdba1dbdcf99}"
;https://msdn.microsoft.com/en-us/library/windows/desktop/bb761474(v=vs.85).aspx
Global Const $tagIPropertyStore = _
        "GetCount hresult(dword*);" & _
        "GetAt    hresult(dword;ptr*);" & _
        "GetValue hresult(struct*;variant*);" & _
        "SetValue hresult(struct*;variant*);" & _
        "Commit   hresult();"

; ===============================================================================================================================

Global Const $tagWAVEFORMATEX = "align 2;" & _
        "word wFormatTag;word nChannels;" & _
        "dword nSamplesPerSec;dword nAvgBytesPerSec;" & _
        "word nBlockAlign;word wBitsPerSample;" & _
        "word cbSize;"

Global Const $tagWAVEFORMATEXTENSIBLE = _
        "struct;" & $tagWAVEFORMATEX & _
        "word wUnionReserved;" & _ ;wValidBitsPerSample;wSamplesPerBlock;wReserved;
        "dword dwChannelMask;" & _
        "byte SubFormat[16];" & _ ;DllStructGetSize(DllStructCreate($tagGUID))
        "endstruct;"

;WAV file headers
Global Const $tagWAVHEADER = "char RIFF[4]; uint FileSize; char WAVE[4];"

Global Const $tagWAVFMT = "char FMT[4]; uint FMTLen; word wFormatTag;" & _
        "word nChannels; uint nSamplesPerSec; uint nAvgBytesPerSec; word nBlockAlign; word wBitsPerSample;"

Global Const $tagWAVFACT = "char FACT[4]; uint FACTLen; dword dwSampleLength;"

Global Const $tagWAVEXTENSIBLE = "word cbsize; word wValidBitsPerSample; dword dwChannelMask; " & _
        "byte SubFormat[16]; " & $tagWAVFACT

Global Const $tagWAVDATA = "char DATA[4]; uint DATALen;"

#EndRegion Global Variables and Constants

#Region Functions list

; #CURRENT# =====================================================================================================================
; _WASAPI_GetDefaultAudioEndpoint
; _WASAPI_EnumAudioEndpoints
; _WASAPI_EndpointInfo
; _WASAPI_Stream_Open
; _WASAPI_Stream_Close
; _WASAPI_Stream_StartCapture
; _WASAPI_Stream_Dataflow
; _WASAPI_Stream_DeviceID
; _WASAPI_Stream_StopCapture
; _WASAPI_Stream_Status
; _WASAPI_Stream_CaptureEventHandle
; _WASAPI_Stream_WaveFormat
; _WASAPI_Stream_DefaultDevicePeriod
; _WASAPI_Stream_MinimumDevicePeriod
; _WASAPI_Stream_BufferSize
; _WASAPI_Stream_BufferSizeBytes
; _WASAPI_Stream_GetPosition
; _WASAPI_Stream_PaddingFrames
; _WASAPI_Stream_Latency
; _WASAPI_Stream_Reset
; _WASAPI_RIFF_Header
; _WFx_Struct
; _WFx_GetPtr
; _WFx_Get
; _WFx_Set
; _WFx_GetDbg
; #CURRENT# = INTERNAL ==========================================================================================================
; __Interface
; __DeviceGetId
; __GetDeviceFromID
; __Stream_AudioClient_Init
; __Stream_AudioClient_Setup
; __Stream_CaptureClient_Setup
; __Stream_SetWaveFormat
; __PropertyStore_GetValue
; __WinAPI_PKEYFromString
; ===============================================================================================================================
#EndRegion Functions list

#Region ### WASAPI INFO ###
; #FUNCTION _WASAPI_GetDefaultAudioEndpoint# ====================================================================================
; Author.......: Bilgus
; Description..: Returns DeviceID of default endpoint device that meets the specified criteria
; Parameters...: $eDataFlow, $iState = $DEVICE_STATE_ACTIVE
; Returns......: DeviceID(String) on success
; ===============================================================================================================================

Func _WASAPI_GetDefaultAudioEndpoint($eDataFlow, $iState = $DEVICE_STATE_ACTIVE)
    Local $iErr = 0, $hRes = $OLE_E_BLANK
    Local $oIMMDeviceEnumerator
    Local $pDefaultEndpoint = 0
    Local $sID

    If __Interface($CLSID_MMDeviceEnumerator, $IID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator, $oIMMDeviceEnumerator) Then
        $iErr = 1
    Else
        $hRes = $oIMMDeviceEnumerator.GetDefaultAudioEndpoint($eDataFlow, $iState, $pDefaultEndpoint)
        If $hRes Then $iErr = 2
    EndIf
    $oIMMDeviceEnumerator = 0
    If __DeviceGetId($pDefaultEndpoint, $sID) Then $iErr = @error
    Return SetError($iErr, $hRes, $sID)
EndFunc   ;==>_WASAPI_GetDefaultAudioEndpoint

; #FUNCTION _WASAPI_EnumAudioEndpoints# =========================================================================================
; Author.......: Bilgus ;;;Thanks trancexx, kafu
; Description..: Returns array of audio endpoint device IDs that meet the specified criteria
; Parameters...: $eDataFlow, $iState = $DEVICE_STATE_ACTIVE
; Returns......: array containing DeviceIDs(String)
; ===============================================================================================================================

Func _WASAPI_EnumAudioEndpoints($eDataFlow, $iState = $DEVICE_STATE_ACTIVE)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $oIMMDeviceEnumerator

    Local $pDeviceCollection = 0
    Local $oIMMDeviceCollection

    Local $iCount, $pEndpoint

    While Not __Interface($CLSID_MMDeviceEnumerator, $IID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator, $oIMMDeviceEnumerator)
        $iErr = 0
        $oIMMDeviceEnumerator.EnumAudioEndpoints($eDataFlow, $iState, $pDeviceCollection)
        $oIMMDeviceEnumerator = 0
        If __Interface($pDeviceCollection, $IID_IMMDeviceCollection, $tagIMMDeviceCollection, $oIMMDeviceCollection) Then
            $iErr = 2
            ExitLoop
        EndIf
        $hRes = $oIMMDeviceCollection.GetCount($iCount)
        If $hRes Then
            $iErr = 3
            ExitLoop
        EndIf

        Local $aEndpoints[$iCount + 1]
        $aEndpoints[0] = $iCount
        For $i = 0 To $iCount - 1
            $pEndpoint = 0
            $hRes = $oIMMDeviceCollection.Item($i, $pEndpoint)
            If $hRes Then
                $iErr = 4
                ExitLoop
            EndIf
            If __DeviceGetId($pEndpoint, $aEndpoints[$i + 1]) Then
                $iErr = @error
                ExitLoop
            EndIf
        Next
        ExitLoop
    WEnd
    $oIMMDeviceCollection = 0
    If $iErr Then
        ConsoleWriteError("Could not enumerate endpoints, Err: " & $iErr & " : " & $hRes & @CRLF)
        Local $aEndpoints_None[1] = [0]
        Return SetError($iErr, $hRes, $aEndpoints_None)
    Else
        Return $aEndpoints
    EndIf
EndFunc   ;==>_WASAPI_EnumAudioEndpoints

; #FUNCTION _WASAPI_EndpointInfo# ===============================================================================================
; Author.......: Bilgus ;;;Thanks trancexx, kafu
; Description..: Returns Array Containing Name, Description, Interface, SystemName, DeviceId, Guid for specified endpoint
; Parameters...: $sDeviceID
; Returns......: Array containing information for specified endpoint on success
; ===============================================================================================================================

Func _WASAPI_EndpointInfo($sDeviceID)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $pEndpoint = __GetDeviceFromID($sDeviceID)
    Local $oIMMDevice
    Local $pPropertyStore = 0
    Local $oIPropertyStore

    Local $aInfo[$eWASINFO_ELEMS]
    If Not __Interface($pEndpoint, $IID_IMMDevice, $tagIMMDevice, $oIMMDevice) Then
        $iErr = 0
        $aInfo[$eWASInfo_ID] = $sDeviceID
        $hRes = $oIMMDevice.OpenPropertyStore($STGM_READ, $pPropertyStore)
        If Not ($hRes Or __Interface($pPropertyStore, $IID_IPropertyStore, $tagIPropertyStore, $oIPropertyStore)) Then
            $aInfo[$eWASInfo_Name] = __PropertyStore_GetValue($oIPropertyStore, $PKEY_DEVICE_FRIENDLY_NAME)

            $aInfo[$eWASinfo_GUID] = __PropertyStore_GetValue($oIPropertyStore, $PKEY_AUDIO_ENDPOINT_GUID)

            $aInfo[$eWASinfo_Desc] = __PropertyStore_GetValue($oIPropertyStore, $PKEY_DEVICE_DESCRIPTION)

            $aInfo[$eWASinfo_Interface] = __PropertyStore_GetValue($oIPropertyStore, $PKEY_DEVICE_INTERFACE_FRIENDLY_NAME)

            $aInfo[$eWASinfo_SystemName] = __PropertyStore_GetValue($oIPropertyStore, $PKEY_SYSTEM_NAME)
        Else
            $iErr = 2
        EndIf
    EndIf
    $oIPropertyStore = 0
    $oIMMDevice = 0
    Return SetError($iErr, $hRes, $aInfo)
EndFunc   ;==>_WASAPI_EndpointInfo
#EndRegion ### WASAPI INFO ###

#Region ### WASAPI CAPTURE ###

; #FUNCTION _WASAPI_Stream_Open# ================================================================================================
; Author.......: Bilgus
; Description..: opens an audio client for given device pointer if eRender device opens as loopback
; Parameters...: $sDeviceID, $iBufferMs = 200, $iStreamFlags = Default, $iShareMode = Default, $iPeriodicity = 0
; Returns......: (opaque) array containing handles for other _WASAPI_Stream_ functions on success
; ===============================================================================================================================

Func _WASAPI_Stream_Open($sDeviceID, $iBufferMs = 200, $iStreamFlags = Default, $iShareMode = Default, $iPeriodicity = 0)
    Local $hRes = 0
    Local $pWFX, $hEvent_BufferReady
    Local $oIAudioClient, $oIAudioClient_Loopback, $oICaptureClient
    Local $eDataFlow
    Local $aSC[$eSC_iElems] ;
    $aSC[$eSC_iState] = $eStream_Close
    $aSC[$eSC_oIAudioClient] = 0
    $aSC[$eSC_oIAudioClient_Loopback] = 0
    $aSC[$eSC_oICaptureClient] = 0
    $aSC[$eSC_pWFX] = 0
    $aSC[$eSC_eDataFlow] = -1
    $aSC[$eSC_hEvent_BufferReady] = 0
    $aSC[$eSC_sID] = $sDeviceID

    ;Create the IAudioClient Interface
    Local $pDevice = __GetDeviceFromID($sDeviceID)
    If __Stream_AudioClient_Setup($pWFX, $pDevice, $eDataFlow, $oIAudioClient, $oIAudioClient_Loopback, 0) Then
        $hRes = @extended
        MsgBox(16, @ScriptName, "Error retrieving IAudioClient: " & @error & " 0x" & Hex($hRes))
        Return SetError(1, $hRes, 0)
    EndIf

    ;;;If @OSBuild >= 10000 Then $oIAudioClient_Loopback = 0
    ;;PRETTY SURE THIS IS A LIE BUT MAYBE SOME VERSIONS OF W10 work.
    ;On Windows versions prior to Windows 10, a pull-mode capture client will not receive
    ;any events when a stream is initialized with event-driven buffering
    ;(AUDCLNT_STREAMFLAGS_EVENTCALLBACK) and is loopback-enabled (AUDCLNT_STREAMFLAGS_LOOPBACK)

    $hEvent_BufferReady = _WinAPI_CreateEvent(0, False, False)
    If __Stream_AudioClient_Init($pWFX, $eDataFlow, $oIAudioClient, $oIAudioClient_Loopback, _
            $hEvent_BufferReady, $iBufferMs, $iStreamFlags, $iShareMode, $iPeriodicity) Then
        $hRes = @extended
        MsgBox(16, @ScriptName, "Error initializing IAudioClient: " & @error & " 0x" & Hex($hRes))
        $oIAudioClient = 0
        $oIAudioClient_Loopback = 0
        _WinAPI_CloseHandle($hEvent_BufferReady)
        Return SetError(2, $hRes, 0)
    EndIf

    ;Create the ICaptureClient Interface
    If __Stream_CaptureClient_Setup($oIAudioClient, $oICaptureClient) Then
        $hRes = @extended
        MsgBox(16, @ScriptName, "Error retrieving IAudioCaptureClient: " & @error & " 0x" & Hex($hRes))
        _WinAPI_CloseHandle($hEvent_BufferReady)
    Else
        $aSC[$eSC_iState] = $eStream_Open
        $aSC[$eSC_oIAudioClient] = $oIAudioClient
        $aSC[$eSC_oIAudioClient_Loopback] = $oIAudioClient_Loopback
        $aSC[$eSC_oICaptureClient] = $oICaptureClient
        $aSC[$eSC_pWFX] = $pWFX
        $aSC[$eSC_eDataFlow] = $eDataFlow
        $aSC[$eSC_hEvent_BufferReady] = $hEvent_BufferReady
        Return $aSC
    EndIf
    Return SetError(3, $hRes, 0)
EndFunc   ;==>_WASAPI_Stream_Open

; #FUNCTION _WASAPI_Stream_Close# ===============================================================================================
; Author.......: Bilgus
; Description..: Closes loopback audio client frees handles and playback ready event, stops stream capture if active
; Parameters...: ByRef $aSC
; Returns......: 0
; ===============================================================================================================================

Func _WASAPI_Stream_Close(ByRef $aSC)
    If $aSC[$eSC_iState] = $eStream_StartCapture Then _WASAPI_Stream_StopCapture($aSC)
    _WinAPI_CloseHandle($aSC[$eSC_hEvent_BufferReady])
    _WinAPI_CoTaskMemFree($aSC[$eSC_pWFX])
    _WFx_Struct(0) ;Clear static data
    $aSC[$eSC_iState] = $eStream_Close
    $aSC[$eSC_oIAudioClient] = 0
    $aSC[$eSC_oIAudioClient_Loopback] = 0
    $aSC[$eSC_oICaptureClient] = 0
    $aSC[$eSC_pWFX] = 0
    $aSC[$eSC_hEvent_BufferReady] = 0
    Return 0
EndFunc   ;==>_WASAPI_Stream_Close

; #FUNCTION _WASAPI_Stream_StartCapture# ========================================================================================
; Author.......: Bilgus
; Description..: Starts capture for audio client, if bReset = True and Capture is stopped stream is reset
; Parameters...: ByRef $aSC, $bReset = False
; Returns......: Interface object oICaptureClient on success
; ===============================================================================================================================

Func _WASAPI_Stream_StartCapture(ByRef $aSC, $bReset = False)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $iState = $aSC[$eSC_iState]
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    Local $oIAudioClient_Loopback = $aSC[$eSC_oIAudioClient_Loopback]
    Local $oICaptureClient = 0 ;

    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = 0
        If $iState = $eStream_Open Or $iState = $eStream_StopCapture Then
            If $bReset And $iState = $eStream_StopCapture Then $hRes = _WASAPI_Stream_Reset($aSC)
            If Not $hRes And IsObj($oIAudioClient_Loopback) Then $hRes = $oIAudioClient_Loopback.Start()
            If Not $hRes Then
                $hRes = $oIAudioClient.Start()
                If Not $hRes Then
                    $aSC[$eSC_iState] = $eStream_StartCapture
                    $oICaptureClient = $aSC[$eSC_oICaptureClient]
                EndIf
            EndIf
            If $hRes Or Not IsObj($oICaptureClient) Then
                ConsoleWriteError("Unable to start capture" & @CRLF)
                $iErr = 3
            EndIf
        Else
            ConsoleWriteError("Capture is not opened" & @CRLF)
            $iErr = 2
        EndIf
    Else
        ConsoleWriteError("AudioClient object does not exist" & @CRLF)
    EndIf
    Return SetError($iErr, $hRes, $oICaptureClient)
EndFunc   ;==>_WASAPI_Stream_StartCapture

; #FUNCTION _WASAPI_Stream_Dataflow# ============================================================================================
; Author.......: Bilgus
; Description..: Returns the dataflow direction of the stream device
; Parameters...: $aSC
; Returns......: $eRender(playback), $eCapture(recording), eAll on success
; ===============================================================================================================================

Func _WASAPI_Stream_Dataflow($aSC)
    Local $iErr = 1, $iDataFlow = -1
    If IsArray($aSC) Then
        $iErr = 0
        If $aSC[$eSC_eDataFlow] = -1 Then
            $iErr = 2
        EndIf
        $iDataFlow = $aSC[$eSC_eDataFlow]
    EndIf
    Return SetError($iErr, 0, $iDataFlow)
EndFunc   ;==>_WASAPI_Stream_Dataflow

; #FUNCTION _WASAPI_Stream_DeviceID# ============================================================================================
; Author.......: Bilgus
; Description..: Returns DeviceID(String) of stream capture
; Parameters...: $aSC
; Returns......: DeviceID(String) on success
; ===============================================================================================================================

Func _WASAPI_Stream_DeviceID($aSC)
    If IsArray($aSC) Then
        Return $aSC[$eSC_sID]
    Else
        Return -1
    EndIf
EndFunc   ;==>_WASAPI_Stream_DeviceID

; #FUNCTION _WASAPI_Stream_StopCapture# =========================================================================================
; Author.......: Bilgus
; Description..: Stops capture for audio client, resets stream by default
; Parameters...: ByRef $aSC, $bReset = True
; Returns......: 0 on success
; ===============================================================================================================================

Func _WASAPI_Stream_StopCapture(ByRef $aSC, $bReset = True)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    Local $oIAudioClient_Loopback = $aSC[$eSC_oIAudioClient_Loopback]
    If IsObj($oIAudioClient) Then
        $iErr = 0
        If IsObj($oIAudioClient_Loopback) Then
            $hRes = $oIAudioClient_Loopback.Stop()
            If $hRes Then $iErr = 2
        EndIf
        ;Attempt to stop IAudioClient even if above failed
        $hRes = $oIAudioClient.Stop()
        If $hRes Then $iErr += 3
        $aSC[$eSC_iState] = $eStream_StopCapture
    EndIf
    If $bReset Then $hRes = _WASAPI_Stream_Reset($aSC)
    Return SetError($iErr, $hRes, $iErr)
EndFunc   ;==>_WASAPI_Stream_StopCapture

; #FUNCTION _WASAPI_Stream_Status# ==============================================================================================
; Author.......: Bilgus
; Description..: Returns the state of stream capture
; Parameters...: $aSC
; Returns......: $eStream_Close, $eStream_Open, $eStream_StartCapture, $eStream_StopCapture on success
; ===============================================================================================================================

Func _WASAPI_Stream_Status($aSC)
    If IsArray($aSC) Then
        Return $aSC[$eSC_iState]
    Else
        Return -1
    EndIf
EndFunc   ;==>_WASAPI_Stream_Status

; #FUNCTION _WASAPI_Stream_CaptureEventHandle# ==================================================================================
; Author.......: Bilgus
; Description..: Retrieves handle to buffer ready event
; Parameters...: $aSC
; Returns......: handle to Event_BufferReady on success
; ===============================================================================================================================

Func _WASAPI_Stream_CaptureEventHandle($aSC)
    If IsArray($aSC) Then
        Return $aSC[$eSC_hEvent_BufferReady]
    Else
        Return -1
    EndIf
EndFunc   ;==>_WASAPI_Stream_CaptureEventHandle

; #FUNCTION _WASAPI_Stream_WaveFormat# ==========================================================================================
; Author.......: Bilgus
; Description..: Retrieves current pointer to format descriptor of type WAVEFORMATEX (or WAVEFORMATEXTENSIBLE)
; Parameters...: $aSC
; Returns......: pointer to format descriptor on success
; ===============================================================================================================================

Func _WASAPI_Stream_WaveFormat($aSC)
    If IsArray($aSC) Then
        Return $aSC[$eSC_pWFX]
    Else
        Return -1
    EndIf
EndFunc   ;==>_WASAPI_Stream_WaveFormat

; #FUNCTION _WASAPI_Stream_DefaultDevicePeriod# =================================================================================
; Author.......: Bilgus
; Description..: retrieves the maximum length of the periodic interval separating successive processing passes by
;                the audio engine on the data in the endpoint buffer
; Parameters...: $aSC
; Returns......: Maximum device period in 100 nanosecond increments on success
; ===============================================================================================================================

Func _WASAPI_Stream_DefaultDevicePeriod($aSC)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $hnsDefaultDevicePeriod = 0
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.GetDevicePeriod($hnsDefaultDevicePeriod, Null)
        If $hRes Then $iErr = 2
    EndIf
    Return SetError($iErr, $hRes, $hnsDefaultDevicePeriod)
EndFunc   ;==>_WASAPI_Stream_DefaultDevicePeriod

; #FUNCTION _WASAPI_Stream_MinimumDevicePeriod# ==================================================================================
; Author.......: Bilgus
; Description..: retrieves the minimum length of the periodic interval separating successive processing passes by
;                the audio engine on the data in the endpoint buffer
; Parameters...: $aSC
; Returns......: Minimum device period in 100 nanosecond increments on success
;; ===============================================================================================================================

Func _WASAPI_Stream_MinimumDevicePeriod($aSC)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $hnsMinimumDevicePeriod = 0
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.GetDevicePeriod(Null, $hnsMinimumDevicePeriod)
        If $hRes Then $iErr = 2
    EndIf
    Return SetError($iErr, $hRes, $hnsMinimumDevicePeriod)
EndFunc   ;==>_WASAPI_Stream_MinimumDevicePeriod

; #FUNCTION _WASAPI_Stream_BufferSize# ==========================================================================================
; Author.......: Bilgus
; Description..: retrieves the size (maximum capacity) of the endpoint buffer in audio frames
; Parameters...: $aSC
; Returns......: size of audio buffer in audio frames on success
; ===============================================================================================================================

Func _WASAPI_Stream_BufferSize($aSC)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $iBufferFrameCount = 0
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.GetBufferSize($iBufferFrameCount)
        If $hRes Then $iErr = 2
    EndIf
    Return SetError($iErr, $hRes, $iBufferFrameCount)
EndFunc   ;==>_WASAPI_Stream_BufferSize

; #FUNCTION _WASAPI_Stream_BufferSizeBytes# =====================================================================================
; Author.......: Bilgus
; Description..: retrieves the size (maximum capacity) of the endpoint buffer in bytes
; Parameters...: $aSC
; Returns......: size of audio buffer in bytes on success
; ===============================================================================================================================

Func _WASAPI_Stream_BufferSizeBytes($aSC)
    Local $iBufferBytes = 0
    Local $iBufferFrameCount = _WASAPI_Stream_BufferSize($aSC)
    Local $iErr = @error
    Local $hRes = @extended
    If Not ($iErr Or $hRes) Then
        Local $pWFX = $aSC[$eSC_pWFX]
        $iBufferBytes = _WFx_Get($pWFX, "nBlockAlign") * $iBufferFrameCount
    EndIf
    Return SetError($iErr, $hRes, $iBufferBytes)
EndFunc   ;==>_WASAPI_Stream_BufferSizeBytes

; #FUNCTION _WASAPI_Stream_GetPosition# =====================================================================================
; Author.......: Bilgus
; Description..: retrieves the time in seconds elapsed for the audio stream (Stream Reset will reset pos to 0)
;                set $bPeerfCount = True to instead get the performance counter value (see MSDN)
; Parameters...: $aSC, $bPerfCount = False
; Returns......: time elapsed for the audio buffer on success, @extended will be $S_FALSE(1) if time is inaccurate
; ===============================================================================================================================
Func _WASAPI_Stream_GetPosition($aSC, $bPerfCount = False)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $pAudioClock, $oIAudioClock
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    Local $iFreq = 1, $iPos, $iPerf
    Local $iTime = -1

    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.GetService(_WinAPI_GUIDFromString($IID_IAudioClock), $pAudioClock)

        If $hRes Or __Interface($pAudioClock, $IID_IAudioClock, $tagIAudioClock, $oIAudioClock) Then
            $iErr = @error
        Else

            $hRes = $oIAudioClock.GetPosition($iPos, $iPerf)
            If $hRes = $S_FALSE Then ;Attempt another call to get a more accurate count
                $hRes = $oIAudioClock.GetPosition($iPos, $iPerf)
                If $hRes = $S_FALSE Then $iErr = 3
            ElseIf $hRes Then
                $iErr = 2
            EndIf

            If $bPerfCount Then
                $iTime = $iPerf
            ElseIf $hRes = $S_OK Or $hRes = $S_FALSE Then
                $hRes = $oIAudioClock.GetFrequency($iFreq)
                If Not $hRes Then
                    $iTime = ($iPos / $iFreq)
                Else
                    $iErr = 4
                EndIf
            EndIf
        EndIf
    EndIf
    Return SetError($iErr, $hRes, $iTime)
EndFunc   ;==>_WASAPI_Stream_GetPosition

; #FUNCTION _WASAPI_Stream_PaddingFrames# =======================================================================================
; Author.......: Bilgus
; Description..: retrieves the number of frames of padding in the endpoint buffer
; Parameters...: $aSC
; Returns......: PaddingFrames on success
; ===============================================================================================================================

Func _WASAPI_Stream_PaddingFrames($aSC)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $iNumPaddingFrames = 0
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.GetCurrentPadding($iNumPaddingFrames)
        If $hRes Then $iErr = 2
    EndIf
    Return SetError($iErr, $hRes, $iNumPaddingFrames)
EndFunc   ;==>_WASAPI_Stream_PaddingFrames

; #FUNCTION _WASAPI_Stream_Latency# =============================================================================================
; Author.......: Bilgus
; Description..: retrieves the maximum latency for the current stream
; Parameters...: $aSC
; Returns......: Latency in 100 nanosecond increments on success
; ===============================================================================================================================

Func _WASAPI_Stream_Latency($aSC)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $ihnsLatency = 0
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.GetStreamLatency($ihnsLatency)
        If $hRes Then $iErr = 2
    EndIf
    Return SetError($iErr, $hRes, $ihnsLatency)
EndFunc   ;==>_WASAPI_Stream_Latency

; #FUNCTION _WASAPI_Stream_Reset# ===============================================================================================
; Author.......: Bilgus
; Description..: Reset a stopped audio stream, flushes all pending data and resets the audio clock stream position to 0
; Parameters...: $aSC
; Returns......: 0 on success, fails if it is called on a stream that is not stopped
; ===============================================================================================================================

Func _WASAPI_Stream_Reset($aSC)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $oIAudioClient = $aSC[$eSC_oIAudioClient]
    Local $oIAudioClient_Loopback = $aSC[$eSC_oIAudioClient_Loopback]
    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.Reset()
        If $hRes = $S_FALSE Then $hRes = 0
        If IsObj($oIAudioClient_Loopback) And Not $hRes Then
            $hRes = $oIAudioClient_Loopback.Reset()
        EndIf
        If $hRes And $hRes <> $S_FALSE Then $iErr = 2
    EndIf
    Return SetError($iErr, $hRes, $iErr)
EndFunc   ;==>_WASAPI_Stream_Reset

; #FUNCTION _WASAPI_RIFF_Header# ========================================================================================================
; Author.......: Bilgus
; Description..: Returns a RIFF wav header to the stream format passed in $aSC
;                Passing 0 for $aSC once initialized returns the same structure
;                set $bReinit = True if you change the format as it is only created once
;                For format spec See: http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
; Parameters...: $aSC, $bReinit = False
; Returns......: (DllStruct)$tRIFF on success
; ===============================================================================================================================

Func _WASAPI_RIFF_Header($aSC, $bReinit = False)
    Static Local $pWFX_Last = 0
    Static Local $tRIFF = 0

    Local $pWFX = 0
    If IsArray($aSC) Then $pWFX = $aSC[$eSC_pWFX]

    If $bReinit Or $tRIFF = 0 Then
        If _WFx_Get($pWFX, "cbSize") Then
            $tRIFF = DllStructCreate("Struct;" & $tagWAVHEADER & $tagWAVFMT & $tagWAVEXTENSIBLE & $tagWAVDATA & "EndStruct;") ;Wave Extensible Format
        Else
            $tRIFF = DllStructCreate("Struct;" & $tagWAVHEADER & $tagWAVFMT & $tagWAVDATA & "EndStruct;")
        EndIf
    EndIf

    If $bReinit Or ($pWFX <> 0 And $pWFX <> $pWFX_Last) Then
        $pWFX_Last = $pWFX
        DllStructSetData($tRIFF, "RIFF", "RIFF")
        DllStructSetData($tRIFF, "FileSize", 0)
        DllStructSetData($tRIFF, "WAVE", "WAVE")
        DllStructSetData($tRIFF, "FMT", "fmt ")
        DllStructSetData($tRIFF, "FMTLen", 16)
        DllStructSetData($tRIFF, "wFormatTag", _WFx_Get($pWFX, "wFormatTag"))
        DllStructSetData($tRIFF, "nChannels", _WFx_Get($pWFX, "nChannels"))
        DllStructSetData($tRIFF, "nSamplesPerSec", _WFx_Get($pWFX, "nSamplesPerSec"))
        DllStructSetData($tRIFF, "nAvgBytesPerSec", _WFx_Get($pWFX, "nAvgBytesPerSec"))
        DllStructSetData($tRIFF, "nBlockAlign", _WFx_Get($pWFX, "nBlockAlign"))
        DllStructSetData($tRIFF, "wBitsPerSample", _WFx_Get($pWFX, "wBitsPerSample"))
        If _WFx_Get($pWFX, "cbSize") > 0 Then ;Wave Extensible Format
            DllStructSetData($tRIFF, "FMTLen", 40)
            DllStructSetData($tRIFF, "cbSize", _WFx_Get($pWFX, "cbSize"))
            DllStructSetData($tRIFF, "wValidBitsPerSample", _WFx_Get($pWFX, "wUnionReserved"))
            DllStructSetData($tRIFF, "dwChannelMask", _WFx_Get($pWFX, "dwChannelMask"))
            DllStructSetData($tRIFF, "SubFormat", _WFx_Get($pWFX, "SubFormat"))
            DllStructSetData($tRIFF, "FACT", "fact")
            DllStructSetData($tRIFF, "FACTLen", 4)
            DllStructSetData($tRIFF, "dwSampleLength", 0)
        EndIf
        DllStructSetData($tRIFF, "DATA", "data")
        DllStructSetData($tRIFF, "DATALen", 0)
    EndIf
    Return $tRIFF
EndFunc   ;==>_WASAPI_RIFF_Header

#EndRegion ### WASAPI CAPTURE ###

#Region ### WFX ###
; #FUNCTION _WFx_Struct# ========================================================================================================
; Author.......: Bilgus
; Description..: Takes a pointer to the WaveFormatEx/tensible structure first as a WAVEFORMATEX structure and then as a
;                WAVEFORMATEXTENSIBLE struct if applicable. The resulting dllstruct is then saved between calls with the
;                same $pWFX(pointer).. to remove the struct pass 0 for $pWFX
; Parameters...: $pWFX
; Returns......: (DllStruct)$tWFX on success
; ===============================================================================================================================

Func _WFx_Struct($pWFX)
    Static Local $pWFX_Last = 0
    Static Local $tWFX = ""
    If $pWFX <> $pWFX_Last Then
        $pWFX_Last = $pWFX
        If $pWFX = 0 Then
            $tWFX = ""
            Return SetExtended(1, 0)
        Else
            $tWFX = DllStructCreate($tagWAVEFORMATEX, $pWFX)
            If DllStructGetData($tWFX, "cbSize") > 0 Then
                $tWFX = DllStructCreate($tagWAVEFORMATEXTENSIBLE, $pWFX)
            EndIf
        EndIf
    EndIf
    Return $tWFX
EndFunc   ;==>_WFx_Struct

; #FUNCTION _WFx_GetPtr# ========================================================================================================
; Author.......: Bilgus
; Description..: Retrieves the pointer element within the WaveFormatEx/tensible structure
; Parameters...: $pWFX, $vElem
; Returns......: pointer to element on success
; ===============================================================================================================================

Func _WFx_GetPtr($pWFX, $vElem)
    Return _WFx_Get($pWFX, $vElem, Default, True)
EndFunc   ;==>_WFx_GetPtr

; #FUNCTION _WFx_Get# ===========================================================================================================
; Author.......: Bilgus
; Description..: Retrieves the value @ element within the WaveFormatEx/tensible structure
; Parameters...: $pWFX, $vElem, $iIndex = Default, $bReturnPtr = False
; Returns......: $vValue on success
; ===============================================================================================================================

Func _WFx_Get($pWFX, $vElem, $iIndex = Default, $bReturnPtr = False)
    Local $tWFX = _WFx_Struct($pWFX)
    If IsDllStruct($tWFX) Then
        If $bReturnPtr Then Return DllStructGetPtr($tWFX, $vElem)
        Return DllStructGetData($tWFX, $vElem, $iIndex)
    Else
        Return SetError(1, 0, 0)
    EndIf
EndFunc   ;==>_WFx_Get

; #FUNCTION _WFx_Set# ===========================================================================================================
; Author.......: Bilgus
; Description..: Sets the value @ element within the WaveFormatEx/tensible structure
; Parameters...: $pWFX, $vElem, $vValue, $iIndex = Default
; Returns......: $vValue on success
; ===============================================================================================================================

Func _WFx_Set($pWFX, $vElem, $vValue, $iIndex = Default)
    Local $tWFX = _WFx_Struct($pWFX)
    If IsDllStruct($tWFX) Then
        Return DllStructSetData($tWFX, $vElem, $vValue, $iIndex)
    Else
        Return SetError(1, 0, 0)
    EndIf
EndFunc   ;==>_WFx_Set

; #FUNCTION _WFx_GetDbg# ========================================================================================================
; Author.......: Bilgus
; Description..: Returns element name: value for debugging
; Parameters...: $pWFX, $vElem, $iIndex = Default
; Returns......: name: value on success
; ===============================================================================================================================

Func _WFx_GetDbg($pWFX, $vElem, $iIndex = Default)
    Local $vValue = _WFx_Get($pWFX, $vElem, $iIndex)
    If @error Then
        Return $vElem & ": " & $vValue & " Error: " & @error
    Else
        Return $vElem & ": " & $vValue
    EndIf
EndFunc   ;==>_WFx_GetDbg
#EndRegion ### WFX ###

#Region ### INTERNAL ###

; #FUNCTION __Interface# ======================================================================================================
; Author.......: Bilgus
; Description..: Retrieves COM object from ObjCreateInterface
;                Allows COM like error checking throughout the code
; Parameters...: $pInterface_sCLSID, $sIID, $sTag, ByRef $oReturned
; Returns......: 0 on Success
; ===============================================================================================================================
Func __Interface($pInterface_sCLSID, $sIID, $sTag, ByRef $oReturned)
    Local $iErr = 10
    $oReturned = 0
    If $pInterface_sCLSID Then
        $iErr = 0
        $oReturned = ObjCreateInterface($pInterface_sCLSID, $sIID, $sTag)
    EndIf
    If @error Or Not IsObj($oReturned) Then
        $oReturned = 0
        $iErr = 20
    EndIf
    Return SetError($iErr, 0, $iErr)
EndFunc   ;==>__Interface

; #FUNCTION __DeviceGetId# ======================================================================================================
; Author.......: Bilgus ;;thanks Trancexx
; Description..: Retrieves DeviceID from device pointer
; Parameters...: $oIMMDevice, ByRef $sDeviceID
; Returns......: 0 on Success
; ===============================================================================================================================
Func __DeviceGetId($pDevice, ByRef $sDeviceID)
    Local $iErr = 10, $hRes = $OLE_E_BLANK
    Local $pID = 0, $oIMMDevice
    $sDeviceID = ""

    If Not __Interface($pDevice, $IID_IMMDevice, $tagIMMDevice, $oIMMDevice) Then
        $hRes = $oIMMDevice.GetId($pID)
        $iErr = $hRes
        If Not $hRes Then
            $sDeviceID = DllStructGetData(DllStructCreate("wchar ID[" & _WinAPI_StrLen($pID) & "]", $pID), "ID")
            _WinAPI_CoTaskMemFree($pID)
        EndIf
    EndIf
    $oIMMDevice = 0
    Return SetError($iErr, $hRes, $iErr)
EndFunc   ;==>__DeviceGetId

; #FUNCTION __GetDeviceFromID# ==================================================================================================
; Author.......: Bilgus
; Description..: Retrieves Device pointer from DeviceID
; Parameters...: $sDeviceID
; Returns......: pointer to specified device on success
; ===============================================================================================================================
Func __GetDeviceFromID($sDeviceID)
    Local $iErr = 10, $hRes = $OLE_E_BLANK
    Local $pDevice = 0
    Local $oIMMDeviceEnumerator
    If Not __Interface($CLSID_MMDeviceEnumerator, $IID_IMMDeviceEnumerator, $tagIMMDeviceEnumerator, $oIMMDeviceEnumerator) Then
        $hRes = $oIMMDeviceEnumerator.GetDevice($sDeviceID, $pDevice)
        $iErr = $hRes
    EndIf
    $oIMMDeviceEnumerator = 0
    Return SetError($iErr, $hRes, $pDevice)
EndFunc   ;==>__GetDeviceFromID

; #FUNCTION __Stream_AudioClient_Init# ==========================================================================================
; Author.......: Bilgus
; Description..: Initializes IAudioClient and ( < Win10) IAudioClient_Loopback with specified buffer,
;                streamflags, sharemode and periodicity
; Parameters...: $pWFX, $eDataFlow $oIAudioClient, $oIAudioClient_Loopback, $hEvent_BufferReady,
;                $iBufferMs = 200, $iStreamFlags = Default, $iShareMode = Default, $iPeriodicity = 0
; Returns......: 0 on success
; ===============================================================================================================================

Func __Stream_AudioClient_Init($pWFX, $eDataFlow, $oIAudioClient, $oIAudioClient_Loopback, $hEvent_BufferReady, _
        $iBufferMs = 200, $iStreamFlags = Default, $iShareMode = Default, $iPeriodicity = 0)
    If $iStreamFlags = Default Then $iStreamFlags = BitOR($AUDCLNT_STREAMFLAGS_LOOPBACK, $AUDCLNT_STREAMFLAGS_EVENTCALLBACK)
    If $iShareMode = Default Then $iShareMode = $AUDCLNT_SHAREMODE_SHARED

    Local Const $iREFTIMES_PER_MSEC = 10000
    Local $iHnsRequestedDuration = $iREFTIMES_PER_MSEC * $iBufferMs
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    If IsObj($oIAudioClient) Then
        $iErr = 0
        If IsObj($oIAudioClient_Loopback) And BitAND($iStreamFlags, $AUDCLNT_STREAMFLAGS_EVENTCALLBACK) Then
            $iStreamFlags = BitAND($iStreamFlags, BitNOT($AUDCLNT_STREAMFLAGS_EVENTCALLBACK))
        EndIf

        If $eDataFlow = $eCapture Then ;AUDCLNT_STREAMFLAGS_LOOPBACK is not valid for capture devices
            $iStreamFlags = BitAND($iStreamFlags, BitNOT($AUDCLNT_STREAMFLAGS_LOOPBACK))
        EndIf

        If $iShareMode = $AUDCLNT_SHAREMODE_SHARED Then
            $iPeriodicity = 0
        ElseIf BitAND($iStreamFlags, $AUDCLNT_STREAMFLAGS_EVENTCALLBACK) Then
            $iPeriodicity = $iHnsRequestedDuration
        EndIf

        $hRes = $oIAudioClient.Initialize($iShareMode, $iStreamFlags, $iHnsRequestedDuration, $iPeriodicity, $pWFX, Null)

        If Not $hRes Then
            If IsObj($oIAudioClient_Loopback) And $hEvent_BufferReady Then
                ; In loopback mode AUDCLNT_STREAMFLAGS_EVENTCALLBACK doesn't work, so we create a second audio client to get notifications (< Win10)
                $hRes = $oIAudioClient_Loopback.Initialize($iShareMode, $AUDCLNT_STREAMFLAGS_EVENTCALLBACK, $iHnsRequestedDuration, 0, $pWFX, Null)
                If Not $hRes Then
                    $oIAudioClient_Loopback.SetEventHandle($hEvent_BufferReady)
                Else
                    ConsoleWriteError("Error Unable to set event mode for loopback " & $hRes & @CRLF)
                    $iErr = 3
                EndIf
            ElseIf BitAND($iStreamFlags, $AUDCLNT_STREAMFLAGS_EVENTCALLBACK) And $hEvent_BufferReady Then
                $oIAudioClient.SetEventHandle($hEvent_BufferReady)
            EndIf
        Else
            ConsoleWriteError("Error Unable to initialize AudioClient " & $hRes & @CRLF)
            $iErr = 2
        EndIf
    Else
        ConsoleWriteError("Error oIAudioClient is not an object" & @CRLF)
    EndIf

    Return SetError($iErr, $hRes, $iErr)
EndFunc   ;==>__Stream_AudioClient_Init

; #FUNCTION __Stream_AudioClient_Setup# =========================================================================================
; Author.......: Bilgus
; Description..: retrieves and Activates Audio Client / Audio Client Loopback with a wave file compatible format
; Parameters...: ByRef $pWFX, $pDevice, ByRef $eDataFlow, ByRef $oIAudioClient, ByRef $oIAudioClient_Loopback, $iBps = 16
; If $iBps = 0 then the current BitsPerSample of the device is used
; Returns......: 0 on success
; ===============================================================================================================================

Func __Stream_AudioClient_Setup(ByRef $pWFX, $pDevice, ByRef $eDataFlow, ByRef $oIAudioClient, ByRef $oIAudioClient_Loopback, $iBps = 16)
    ;If $iBps = 0 then the current BitsPerSample of the device is used
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $oIMMDevice

    Local $pEndpoint = 0
    Local $oIMMEndPoint = 0
    $eDataFlow = -1

    Local $pAudioClient = 0
    $oIAudioClient = 0

    Local $pAudioClient_Loopback = 0
    $oIAudioClient_Loopback = 0

    $pWFX = 0

    While Not __Interface($pDevice, $IID_IMMDevice, $tagIMMDevice, $oIMMDevice)
        $iErr = 0
        $hRes = $oIMMDevice.Activate(_WinAPI_GUIDFromString($IID_IAudioClient), $CLSCTX_INPROC_SERVER, 0, $pAudioClient)

        If $hRes Or __Interface($pAudioClient, $IID_IAudioClient, $tagIAudioClient, $oIAudioClient) Then
            $iErr = 2
            ExitLoop
        EndIf

        $hRes = $oIAudioClient.GetMixFormat($pWFX)
        If $hRes Then
            $iErr = $hRes
            ExitLoop
        EndIf
        __Stream_SetWaveFormat($oIAudioClient, $pWFX, $iBps)
        $iErr = @error
        $hRes = @extended
        If $iErr Then ExitLoop
        ; In loopback mode AUDCLNT_STREAMFLAGS_EVENTCALLBACK doesn't work, so we create a second audio client to get notifications
        $hRes = $oIMMDevice.Activate(_WinAPI_GUIDFromString($IID_IAudioClient), $CLSCTX_INPROC_SERVER, 0, $pAudioClient_Loopback)
        If $hRes Then
            $iErr = 3
            ExitLoop
        EndIf

        $hRes = $oIMMDevice.QueryInterface(_WinAPI_GUIDFromString($IID_IMMEndpoint), $pEndpoint)
        If $hRes Or __Interface($pEndpoint, $IID_IMMEndpoint, $tagIMMEndpoint, $oIMMEndPoint) Then
            $iErr = 4
            ExitLoop
        EndIf
        $oIMMEndPoint.GetDataFlow($eDataFlow)
        $oIMMEndPoint = 0

        If $eDataFlow <> $eCapture Then
            If __Interface($pAudioClient_Loopback, $IID_IAudioClient, $tagIAudioClient, $oIAudioClient_Loopback) Then
                $iErr = 5
            EndIf
        EndIf
        ExitLoop
    WEnd

    $oIMMDevice = 0
    _WFx_Struct(0) ;Clear static data
    Return SetError($iErr, $hRes, $iErr)
EndFunc   ;==>__Stream_AudioClient_Setup

; #FUNCTION __Stream_CaptureClient_Setup# =======================================================================================
; Author.......: Bilgus
; Description..: retrieves ICaptureClient object
; Parameters...: $oIAudioClient, ByRef $oICaptureClient
; Returns......: 0 on success
; ===============================================================================================================================

Func __Stream_CaptureClient_Setup($oIAudioClient, ByRef $oICaptureClient)
    Local $iErr = 1, $hRes = $OLE_E_BLANK
    Local $pCaptureClient = 0
    $oICaptureClient = 0
    If IsObj($oIAudioClient) Then
        $iErr = 0
        $hRes = $oIAudioClient.GetService(_WinAPI_GUIDFromString($IID_IAudioCaptureClient), $pCaptureClient)

        If $hRes Or __Interface($pCaptureClient, $IID_IAudioCaptureClient, $tagIAudioCaptureClient, $oICaptureClient) Then
            $iErr = 2
        EndIf
    EndIf
    Return SetError($iErr, $hRes, $iErr)
EndFunc   ;==>__Stream_CaptureClient_Setup

; #FUNCTION __Stream_SetWaveFormat# =============================================================================================
; Author.......: Bilgus
; Description..: Attempts to set AudioClient to a wave compatible format
; Parameters...: $oIAudioClient, $pWFX, $iBps = 16; If $iBps = 0 then the current BitsPerSample of the device is used
; Returns......: pointer to format descriptor on success
; ===============================================================================================================================

Func __Stream_SetWaveFormat($oIAudioClient, $pWFX, $iBps = 16)
    Local $iErr = 0
    ;https://github.com/mvaneerde/blog/tree/master/loopback-capture/loopback-capture
    ;coerce int - wave format
    ;can Do this In - place since we're not changing the size of the format
    ;also, the engine will auto - convert from float To int For us

    Switch _WFx_Get($pWFX, "wFormatTag")
        Case $WAVE_FORMAT_IEEE_FLOAT
            _WFx_Set($pWFX, "wFormatTag", $WAVE_FORMAT_PCM)
            If $iBps Then _WFx_Set($pWFX, "wBitsPerSample", $iBps)
            _WFx_Set($pWFX, "nBlockAlign", _WFx_Get($pWFX, "nChannels") * _WFx_Get($pWFX, "wBitsPerSample") / 8)
            _WFx_Set($pWFX, "nAvgBytesPerSec", _WFx_Get($pWFX, "nBlockAlign") * _WFx_Get($pWFX, "nSamplesPerSec"))
        Case $WAVE_FORMAT_EXTENSIBLE
            Local $pGUID = _WFx_GetPtr($pWFX, "SubFormat")
            If _WinAPI_StringFromGUID($pGUID) = $KSDATAFORMAT_SUBTYPE_IEEE_FLOAT Then
                _WinAPI_GUIDFromStringEx($KSDATAFORMAT_SUBTYPE_PCM, _WFx_GetPtr($pWFX, "SubFormat"))
                If $iBps Then
                    _WFx_Set($pWFX, "wUnionReserved", $iBps) ;wValidBitsPerSample
                    _WFx_Set($pWFX, "wBitsPerSample", $iBps)
                EndIf
                _WFx_Set($pWFX, "nBlockAlign", _WFx_Get($pWFX, "nChannels") * _WFx_Get($pWFX, "wBitsPerSample") / 8)
                _WFx_Set($pWFX, "nAvgBytesPerSec", _WFx_Get($pWFX, "nBlockAlign") * _WFx_Get($pWFX, "nSamplesPerSec"))
            Else
                $iErr = 1
                ConsoleWriteError("Error SubFormat " & _WinAPI_StringFromGUID($pGUID) & " is not recognized" & @CRLF)
            EndIf
        Case Else
            $iErr = 2
            ConsoleWriteError("Error: FormatTag " & _WFx_Get($pWFX, "wFormatTag") & " is not recognized" & @CRLF)
    EndSwitch

    If IsObj($oIAudioClient) Then
        Local $pWFX_Dbg = $pWFX
        Local $pWFX_Closest = 0

        Local $hRes = $oIAudioClient.IsFormatSupported($AUDCLNT_SHAREMODE_SHARED, $pWFX, $pWFX_Closest)

        If $hRes <> 0 Then
            $iErr = 3
            ConsoleWriteError("Error: Format is not valid for this device" & @CRLF)
            ConsoleWriteError("hResult: " & Hex($hRes) & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "wFormatTag") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "nChannels") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "nSamplesPerSec") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "nAvgBytesPerSec") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "nBlockAlign") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "wBitsPerSample") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "cbSize") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "wUnionReserved") & @CRLF)
            ConsoleWriteError(_WFx_GetDbg($pWFX_Dbg, "dwChannelMask") & @CRLF)
            ConsoleWriteError("SubFormat: " & _WinAPI_StringFromGUID(_WFx_GetPtr($pWFX_Dbg, "SubFormat")) & @CRLF)
            _WinAPI_CoTaskMemFree($pWFX_Closest)
        EndIf
    Else
        $iErr = 4
        $hRes = $OLE_E_BLANK
        ConsoleWriteError("Error: Unable to set format $oIAudioClient is not an object" & @CRLF)
    EndIf

    _WFx_Struct(0) ;Clear static data
    If $iErr Then $pWFX = 0
    Return SetError($iErr, $hRes, $pWFX)
EndFunc   ;==>__Stream_SetWaveFormat

; #FUNCTION __PropertyStore_GetValue# ===========================================================================================
; Author.......: Bilgus
; Description..: Gets data for a specified property
; Parameters...: $oIPropertyStore, $sPKey_GUID
; Returns......: PropertyVariant on success
; ===============================================================================================================================

Func __PropertyStore_GetValue($oIPropertyStore, $sPKey_GUID)
    Local $iErr = 0, $hRes = 0
    Local $tPKey, $vProp = ""
    If IsObj($oIPropertyStore) Then
        $tPKey = __WinAPI_PKEYFromString($sPKey_GUID)
        $hRes = $oIPropertyStore.GetValue($tPKey, $vProp)
        If $hRes Then $iErr = 2
    Else
        $iErr = 1
        $hRes = $OLE_E_BLANK
    EndIf
    Return SetError($iErr, $hRes, $vProp)
EndFunc   ;==>__PropertyStore_GetValue

; #FUNCTION __WinAPI_PKEYFromString# ============================================================================================
; Author.......: Trancexx
; Description..: Converts a string to a PROPERTYKEY structure
; Parameters...: $sPKEY, $pID = Default
; Returns......: PROPERTYKEY DllStruct on success
; ===============================================================================================================================

Func __WinAPI_PKEYFromString($sPKEY, $pID = Default)
    Local $tPKey = DllStructCreate("byte GUID[16]; dword PID;")
    DllCall("propsys.dll", "long", "PSPropertyKeyFromString", "wstr", $sPKEY, "struct*", $tPKey)
    If $pID <> Default Then DllStructSetData($tPKey, "PID", $pID)
    Return $tPKey
EndFunc   ;==>__WinAPI_PKEYFromString

#EndRegion ### INTERNAL ###

 

Example

Spoiler
;Bilgus 2019 ; https://www.autoitscript.com/forum/topic/197769-wasapi-audio-capture-w-loopback-udf/

#include 'WasApi_capture.au3'
#include <Misc.au3> ;_IsPressed
#include <WinAPIFiles.au3>

Global Const $g_sTMPFILE = @ScriptDir & "\wavecap.raw"
Global $g_hUser32 = DllOpen("user32.dll")
OnAutoItExitRegister("_Exit")

ListAvailableEndpoints()

Global $g_sID_Endpoint = _WASAPI_GetDefaultAudioEndpoint($eRender)
Global $aEpInfo = _WASAPI_EndpointInfo($g_sID_Endpoint)

ConsoleWrite("Capturing From: " & $aEpInfo[$eWASInfo_Name] & @CRLF & "ESC key ends capture" & @CRLF)
ListDeviceProperties($g_sID_Endpoint)
_Main($g_sID_Endpoint)

Func _Exit()
    DllClose($g_hUser32)
EndFunc   ;==>_Exit

Func Seconds2Time($fSec)
    ;Eliminates Float Rounding errors
    Local $iSec, $iH = 0, $iM = 0, $iS = 0, $iMs = 0
    Local $aTime = StringRegExp(Round($fSec, 3), "(\d*)(\.?\d*)", 1)
    If IsArray($aTime) Then
        $iSec = Int($aTime[0])
        $iS = Mod($iSec, 60)
        $iSec -= $iS
        $iSec /= 60
        $iM = Mod($iSec, 60)
        $iSec -= $iM
        $iH = $iSec / 60
        $iMs = $aTime[1] * 1000
    EndIf

    Return StringFormat("%02d:%02d:%02d.%03d", $iH, $iM, $iS, $iMs)
EndFunc   ;==>Seconds2Time

Func _Main($sDeviceID)
    Local Const $iBUFFER_MS = 500
    Local $hFile = _WinAPI_CreateFileEx($g_sTMPFILE, $CREATE_ALWAYS, $GENERIC_WRITE)

    Local $aSC = _WASAPI_Stream_Open($sDeviceID, $iBUFFER_MS) ;Setup stream capture from default render device with a 500Ms buffer

    Local $pWFX = _WASAPI_Stream_WaveFormat($aSC)
    Local $nBlockAlign = _WFx_Get($pWFX, "nBlockAlign")
    Local $iAvgBytesPerSec = _WFx_Get($pWFX, "nAvgBytesPerSec")

    Local $hEvent = _WASAPI_Stream_CaptureEventHandle($aSC)

    ConsoleWrite("Buffer Sz: " & _WASAPI_Stream_BufferSizeBytes($aSC) & " Bytes" & @CRLF)
    Local $iFrameCapThresh = Int(_WASAPI_Stream_BufferSize($aSC) / ($iBUFFER_MS)) ;capture ~1ms of audio data
    If $iFrameCapThresh < 1 Then $iFrameCapThresh = 1
    ConsoleWrite("Capture Every " & $iFrameCapThresh & " audio frame(s) (" & $iFrameCapThresh * $nBlockAlign & " bytes)" & @CRLF)

    Local $nBytes, $tWave = _WASAPI_RIFF_Header($aSC)
    _WinAPI_WriteFile($hFile, DllStructGetPtr($tWave), DllStructGetSize($tWave), $nBytes)
    $tWave = 0 ;No longer needed

    Local $oICaptureClient = _WASAPI_Stream_StartCapture($aSC)
    If @error Then
        ConsoleWrite("Unable to start Capture")
        Return
    EndIf

    Local $iBytesWritten = 0, $iGlitchCount = 0, $iFlags = 0, $iRes = 0
    Local $bPaused, $iSilence = 10 * 15 ;15 seconds of silence before quit

    While $iSilence > 0 And Not _IsPressed("1B", $g_hUser32)
        $iRes = _WinAPI_WaitForSingleObject($hEvent, 100)
        If $iRes = 0 Then
            $iFlags = CapFunc($oICaptureClient, $hFile, $nBlockAlign, $iBytesWritten, $iGlitchCount, $iFrameCapThresh)
            $bPaused = False
            If $iFlags Then
                If BitAND($iFlags, $AUDCLNT_BUFFERFLAGS_SILENT) Then
                    ConsoleWrite("Paused ( waiting for sound play ) - " & $iSilence & @CRLF)
                    $iSilence -= 1
                    $bPaused = True
                EndIf
                If BitAND($iFlags, $AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) Then
                    ConsoleWrite("Time Stamp Error" & @CRLF)
                EndIf
                If $iGlitchCount > 0 Then
                    ConsoleWrite("Audio Glitch Detected" & @CRLF)
                    $iGlitchCount = 0
                EndIf
            EndIf
            ;ConsoleWrite($iBytesWritten & @crlf)
            Sleep(100)
        ElseIf $iRes = $WASAPI_EVENT_WAIT_TIMEOUT Then
            ConsoleWrite("Timeout" & @CRLF)
        Else
            ConsoleWrite("Error Waiting for event" & @CRLF)
            ExitLoop
        EndIf

        If $bPaused Then ;Thanks Argumentum
            ToolTip("[ESC] Exits, Paused ( waiting for sound play ) - " & $iSilence / 10 & "s")
        Else
            ToolTip("[ESC] Exits, " & Seconds2Time($iBytesWritten / $iAvgBytesPerSec))
        EndIf

    WEnd
    ConsoleWrite("Exit or Silence" & @CRLF)
    ToolTip("")

    _WASAPI_Stream_StopCapture($aSC, False)

    _WinAPI_FlushFileBuffers($hFile)
    ;Finalize the wav file
    WavSaver($hFile) ;Write the header data to beginning of capture file

    _WinAPI_CloseHandle($hFile)
    _WASAPI_Stream_Close($aSC) ;

    Local $sOutfile = @ScriptDir & "\wavecap" & @YDAY & "_" & @HOUR & @MIN & @SEC & ".wav"

    If FileMove($g_sTMPFILE, $sOutfile) Then
        Local $iFileSz = FileGetSize($sOutfile)
        ConsoleWrite($sOutfile & " - " & $iFileSz & " bytes - " & _
                Seconds2Time(($iFileSz - DllStructGetSize(_WASAPI_RIFF_Header(0))) / $iAvgBytesPerSec) & @CRLF)
    Else
        MsgBox($MB_ICONERROR, @ScriptName & " Error", "Error renaming tmpfile " & $g_sTMPFILE)
    EndIf

EndFunc   ;==>_Main

Func CapFunc($oICaptureClient, $hFileOpen, $nBlockAlign, ByRef $iBytesWritten, ByRef $iGlitchCount, $iFrameCapThresh = 1, $bCapture = True)
    ;Capture function for the stream, saves data directly to open file
    ;returns flags from the audioclient
    ;$AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY,
    ;$AUDCLNT_BUFFERFLAGS_SILENT
    ;$AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR
    ;
    ;If $AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY occurs $iGlitchCount is incremented and packet is skipped
    ;$iBytesWritten is incremented by number of bytes written
    ;If $bCapture = False all audio frames are discarded

    Local $iPacketLength = 0, $iNumFramesAvailable = 0, $iFlags = 0
    Local $iFlags_Session = 0 ;Flags for any packet during this capture session
    Local $pData, $nBytes

    ;Check if there are any packets to retrieve
    While (Not $oICaptureClient.GetNextPacketSize($iPacketLength)) And $iPacketLength >= $iFrameCapThresh
        $iFlags = 0
        $iFrameCapThresh = 1 ;get remaining frames

        ;Get the available data in the shared buffer.
        $oICaptureClient.GetBuffer($pData, $iNumFramesAvailable, $iFlags, 0, 0)
        $iFlags_Session = BitOR($iFlags_Session, $iFlags)

        If $iFlags = $AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY And $iBytesWritten <> 0 Then
            ;Win 7 Signals Glitch on first capture, Ignore it
            $iGlitchCount += 1
        ElseIf $bCapture And BitAND($iFlags, $AUDCLNT_BUFFERFLAGS_SILENT) = 0 Then
            _WinAPI_WriteFile($hFileOpen, $pData, $iNumFramesAvailable * $nBlockAlign, $nBytes)
            $iBytesWritten += $nBytes
        EndIf

        $oICaptureClient.ReleaseBuffer($iNumFramesAvailable) ;We are done with the buffer release it back to client
    WEnd

    Return $iFlags_Session
EndFunc   ;==>CapFunc

Func WavSaver($hFileOpen)
    ;Updates FileSize, DATALen, dwSampleLength(if applicable) fields in the wav header

    Local $tWaveHeader = _WASAPI_RIFF_Header(0)
    Local $iWaveHeaderLen = DllStructGetSize($tWaveHeader)
    Local $iFileSz = _WinAPI_SetFilePointer($hFileOpen, 0, 2)
    Local $nBytes

    If BitAND($iFileSz, 1) Then ;Padding byte needed
        Local $tPad = DllStructCreate("byte pad")
        _WinAPI_SetFilePointer($hFileOpen, 0, 2) ;write padding byte at eof
        _WinAPI_WriteFile($hFileOpen, DllStructGetPtr($tPad), 1, $nBytes)
        $iFileSz += $nBytes
        ConsoleWrite("Adding padding byte" & @CRLF)
    EndIf

    DllStructSetData($tWaveHeader, "FileSize", $iFileSz - 8)
    Local $iDataLen = $iFileSz - $iWaveHeaderLen
    DllStructSetData($tWaveHeader, "DATALen", $iDataLen)
    Local $wBitsPerSample = DllStructGetData($tWaveHeader, "wBitsPerSample")
    DllStructSetData($tWaveHeader, "dwSampleLength", $iDataLen / ($wBitsPerSample / 8))

    _WinAPI_SetFilePointer($hFileOpen, 0, 0) ;Rewrite the wavheader at the beginning of the file
    _WinAPI_WriteFile($hFileOpen, DllStructGetPtr($tWaveHeader), $iWaveHeaderLen, $nBytes)

    If $nBytes <> $iWaveHeaderLen Then MsgBox($MB_ICONERROR, @ScriptName & " Error", "Error updating wav header")
    Return $iFileSz
EndFunc   ;==>WavSaver

Func ListAvailableEndpoints()
    Local $aEP
    Local $aAvEPs = _WASAPI_EnumAudioEndpoints($eRender)
    ConsoleWrite("(" & $aAvEPs[0] & ") Available Render Endpoints:" & @CRLF)
    For $i = 1 To $aAvEPs[0]
        $aEP = _WASAPI_EndpointInfo($aAvEPs[$i])
        ConsoleWrite($aEP[$eWASInfo_Name] & @CRLF)
    Next
    ConsoleWrite(@CRLF)

    $aAvEPs = _WASAPI_EnumAudioEndpoints($eCapture)
    ConsoleWrite("(" & $aAvEPs[0] & ") Available Capture Endpoints:" & @CRLF)
    For $i = 1 To $aAvEPs[0]
        $aEP = _WASAPI_EndpointInfo($aAvEPs[$i])
        ConsoleWrite($aEP[$eWASInfo_Name] & @CRLF)
    Next
    ConsoleWrite(@CRLF & @CRLF)
EndFunc   ;==>ListAvailableEndpoints

Func ListDeviceProperties($sDeviceID)
    Local $aInfo = _WASAPI_EndpointInfo($sDeviceID)
    ConsoleWrite("Device Name: " & $aInfo[$eWASInfo_Name] & @CRLF)
    ConsoleWrite("Description: " & $aInfo[$eWASinfo_Desc] & @CRLF)
    ConsoleWrite("Interface: " & $aInfo[$eWASinfo_Interface] & @CRLF)
    ConsoleWrite("System Name: " & $aInfo[$eWASinfo_SystemName] & @CRLF)
    ConsoleWrite("DeviceID: " & $aInfo[$eWASinfo_ID] & @CRLF)
    ConsoleWrite("GUID: " & $aInfo[$eWASinfo_GUID] & @CRLF)
    ConsoleWrite(@CRLF & @CRLF)
EndFunc   ;==>ListDeviceProperties

 

Edit: added IMMEndpoint.GetDataFlow to allow the loopback flag to be removed from eCapture Devices so you can record eRender and eCapture Devices now

        added _WASAPI_Stream_Dataflow to get device direction eRender (playback), eCapture (recording), eAll

        Cleaned up error handling in the UDF should be more comprehensive (At least CLEANER!)

        Fixed Windows 10 event mode

        Proper handling of Wave Extensible Format, Cleaned up example code a bit

        FIxed Error with Non Wave Extensible Formats and added paused code from Argumentum

        Cleaned up code added the ability to only capture every n number of frames 1Ms in the example

Capture_Example.au3 WasApi_capture.au3

Edited by Bilgus
Attached Files

Share this post


Link to post
Share on other sites

I added functionality to enumerate the available devices on the system

In doing so I discovered that you get in to a use after free situation if you try to create an interface more than once from the same device pointer.

The device pointers are actually pointers to pointers (ptr**) so even though the underlying pointer is still valid you no longer have access to it.

To remedy this The UDF now passes DeviceIds from the user facing functions and only converts to device pointers as they are ready to be used

 

Share this post


Link to post
Share on other sites

I get an error code brah...

Available Render Endpoints:
Digital Audio (S/PDIF) (High Definition Audio Device)
Digital Audio (S/PDIF) (High Definition Audio Device)
Speakers (I'm Fulla Schiit)

Available Capture Endpoints:


Capturing From: Speakers (I'm Fulla Schiit)
!>13:39:50 AutoIt3.exe ended.rc:-1073741819
+>13:39:50 AutoIt3Wrapper Finished.
>Exit code: 3221225477    Time: 2.695

am i doing something wrong? ps, i love the name of my speakers

i was hoping i could do this to make my own ring tones... for now anyway

Edited by Earthshine

My resources are limited. You must ask the right questions

 

Share this post


Link to post
Share on other sites

@Earthshine

I Updated the UDF and Example to add some more info Try that and post the console output

It might be that your device doesn't support event mode

I still need to flesh out the error handling 

Error handling should be in a much better state now

Edited by Bilgus

Share this post


Link to post
Share on other sites

Ok. My device is an external dac/amp so maybe it doesn’t. I’ll try it when in office again 

Edited by Earthshine

My resources are limited. You must ask the right questions

 

Share this post


Link to post
Share on other sites

I haven't tried anything but the built-ins on my pc I probably have a usb sound card around here somewhere I just need to find a patch cable so I can test it

I'm kinda surprised that it doesn't throw any errors through the UDF

Share this post


Link to post
Share on other sites

..added some more ConsoleWrite's to better show what's up.

Spoiler
>Running:(3.3.14.5):C:\ProgFilesSelf\AutoIt3\autoit3.exe "C:\AU3code\WasApi_capture\WasApi_capture_Example.au3"    
+>Setting Hotkeys...--> Press Ctrl+Alt+Break to Restart or Ctrl+Break to Stop
- Func ListAvailableEndpoints()
Available Render Endpoints:
--- --- --- --- --- --- --- --- --- --- --- 
Available Capture Endpoints:
--- --- --- --- --- --- --- --- --- --- --- 
Capturing From: Speakers (High Definition Audio Device)
ESC key ends capture
- Func ListDeviceProperties("{0.0.0.00000000}.{34d90035-0679-4b97-b868-ddba418b989b}")
Device Name: Speakers (High Definition Audio Device)
Description: Speakers
Interface: High Definition Audio Device
System Name: High Definition Audio Device
DeviceID: {0.0.0.00000000}.{34d90035-0679-4b97-b868-ddba418b989b}
GUID: {34D90035-0679-4B97-B868-DDBA418B989B}
--- --- --- --- --- --- --- --- --- --- --- 


- Func _Main("{0.0.0.00000000}.{34d90035-0679-4b97-b868-ddba418b989b}")
Buffer Sz: 176400 Bytes
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Exit or silence
C:\AU3code\WasApi_capture\wavecap133852.wav     0 bytes
+>13:38:52 AutoIt3.exe ended.rc:0

playing mp3 on VLC media player

it did not record  =/

Edit 1:
 

Spoiler
>"C:\ProgFilesSelf\AutoIt3\SciTE\..\AutoIt3.exe" "C:\ProgFilesSelf\AutoIt3\SciTE\AutoIt3Wrapper\AutoIt3Wrapper.au3" /run /prod /ErrorStdOut /in "C:\AU3code\WasApi_capture\WasApi_capture_Example.au3" /UserParams    
+>13:46:16 Starting AutoIt3Wrapper v.19.102.1901.0 SciTE v.4.1.2.0   Keyboard:00000409  OS:WIN_10/  CPU:X64 OS:X64  Environment(Language:0409)  CodePage:0  utf8.auto.check:4
+>         SciTEDir => C:\ProgFilesSelf\AutoIt3\SciTE   UserDir => C:\Users\Tester\AppData\Local\AutoIt v3\SciTE\AutoIt3Wrapper   SCITE_USERHOME => C:\Users\Tester\AppData\Local\AutoIt v3\SciTE 
>Running AU3Check (3.3.14.5)  from:C:\ProgFilesSelf\AutoIt3  input:C:\AU3code\WasApi_capture\WasApi_capture_Example.au3
+>13:46:16 AU3Check ended.rc:0
>Running:(3.3.14.5):C:\ProgFilesSelf\AutoIt3\autoit3.exe "C:\AU3code\WasApi_capture\WasApi_capture_Example.au3"    
+>Setting Hotkeys...--> Press Ctrl+Alt+Break to Restart or Ctrl+Break to Stop
- Func ListAvailableEndpoints()
Available Render Endpoints:
--- --- --- --- --- --- --- --- --- --- --- 
Available Capture Endpoints:
--- --- --- --- --- --- --- --- --- --- --- 
Capturing From: Headset Earphone (2- Arctis Pro Wireless Chat)
ESC key ends capture
- Func ListDeviceProperties("{0.0.0.00000000}.{43d508ee-19c5-4513-bd52-e1b7ba57ee82}")
Device Name: Headset Earphone (2- Arctis Pro Wireless Chat)
Description: Headset Earphone
Interface: 2- Arctis Pro Wireless Chat
System Name: Arctis Pro Wireless Chat
DeviceID: {0.0.0.00000000}.{43d508ee-19c5-4513-bd52-e1b7ba57ee82}
GUID: {43D508EE-19C5-4513-BD52-E1B7BA57EE82}
--- --- --- --- --- --- --- --- --- --- --- 


- Func _Main("{0.0.0.00000000}.{43d508ee-19c5-4513-bd52-e1b7ba57ee82}")
Buffer Sz: 96000 Bytes
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
1 s
Timeout
Timeout
Timeout
Timeout
Timeout
Timeout
Exit or silence
C:\AU3code\WasApi_capture\wavecap134618.wav     0 bytes
+>13:46:18 AutoIt3.exe ended.rc:0
+>13:46:18 AutoIt3Wrapper Finished.
>Exit code: 0    Time: 2.259

 

same with a headset

Edit 2:

Spoiler
;BILGUS 2019

#include 'WasApi_capture.au3'
#include <Misc.au3> ;_IsPressed
#include <WinAPIFiles.au3>

Global Const $sTmpFile = @ScriptDir & "\wavecap.raw"
Global $g_hUser32 = DllOpen("user32.dll")
OnAutoItExitRegister("_Exit")

ListAvailableEndpoints()

Global $g_sID_Endpoint = _WASAPI_GetDefaultAudioEndpoint($eRender)
Global $aEpInfo = _WASAPI_EndpointInfo($g_sID_Endpoint)

ConsoleWrite("Capturing From: " & $aEpInfo[$eWASInfo_Name] & @CRlf & "ESC key ends capture" & @crlf)
ListDeviceProperties($g_sID_Endpoint)
_Main($g_sID_Endpoint)

Func _Exit()
    DllClose($g_hUser32)
EndFunc   ;==>_Exit

Func _Main($sDeviceID)
    ConsoleWrite('- Func _Main("' & $sDeviceID & '")' & @CRLF)
    Local $hFile = _WinAPI_CreateFileEx($sTmpFile, $CREATE_ALWAYS, $GENERIC_WRITE, 0, $FILE_ATTRIBUTE_TEMPORARY)

    Local $aSC = _WASAPI_Stream_Open($sDeviceID, 500) ;Setup stream capture from default render device with a 500Ms buffer

    ConsoleWrite("Buffer Sz: " & _WASAPI_Stream_BufferSizeBytes($aSC) & " Bytes" & @CRLF)

    Local $hEvent = _WASAPI_Stream_CaptureEventHandle($aSC)
    Local Const $WAIT_TIMEOUT = 0x102

    Local $pWFX = _WASAPI_Stream_WaveFormat($aSC)
    Local $nBlockAlign = _WFx_Get($pWFX, "nBlockAlign")
    Local $tWave = WavHeader_Init($pWFX)

    Local $iBytesWritten = 0, $iGlitchCount = 0
    Local $nBytes, $iFlags
    Local $iS = 0, $res = 0

    _WinAPI_WriteFile($hFile, DllStructGetPtr($tWave), DllStructGetSize($tWave), $nBytes)

    Local $oICaptureClient = _WASAPI_Stream_StartCapture($aSC)
    If @error Then
        ConSoleWrite("Unable to start Capture")
        Return
    EndIf

    While Not _IsPressed("1B", $g_hUser32)
        $res = _WinAPI_WaitForSingleObject($hEvent, 100)
        If $res <> $WAIT_TIMEOUT Then
            $iFlags = CapFunc($oICaptureClient, $hFile, $nBlockAlign, $iBytesWritten, $iGlitchCount)
            _WinAPI_ResetEvent($hEvent)
            If $iFlags Then
                If BitAND($iFlags, $AUDCLNT_BUFFERFLAGS_SILENT) Then
                    ConsoleWrite("Silent" & @CRLF)
                EndIf
                If BitAND($iFlags, $AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) Then
                    ConsoleWrite("Time Stamp Error" & @CRLF)
                EndIf
                If $iGlitchCount > 0 Then
                    ConsoleWrite("Audio Glitch Detected" & @CRLF)
                    $iGlitchCount = 0
                EndIf
            EndIf
            ;ConsoleWrite($iBytesWritten & @crlf)
            Sleep(100)
        Else
            ConsoleWrite("Timeout" & @CRLF)
        EndIf

        $iS += 100
        If Mod($iS, 1000) == 0 Then
            ConsoleWrite($iS / 1000 & " s" & @CRLF)
        EndIf
    WEnd
    ConsoleWrite("Exit or silence" & @CRLF)

    _WASAPI_Stream_StopCapture($aSC)

    _WinAPI_CloseHandle($hFile)

    Local $hFileOpen = _WinAPI_CreateFileEx($sTmpFile, $OPEN_EXISTING, $GENERIC_WRITE, 0)
    If $hFileOpen = -1 Then
        MsgBox($MB_SYSTEMMODAL, "", "An error occurred when opening the file: " & $sTmpFile)
        Exit
    EndIf

    WavSaver($hFileOpen) ;Write the header data to beginning of capture file
    _WinAPI_CloseHandle($hFileOpen)

    Local $sOutfile = @ScriptDir & "\wavecap" & @HOUR & @MIN & @SEC & ".wav"
    ConsoleWrite($sOutfile & '  ' & @TAB & FileGetSize($sOutfile) & ' bytes' & @CRLF)
    FileMove($sTmpFile, $sOutfile)
    _WASAPI_Stream_Close($aSC) ;
EndFunc   ;==>_Main

Func CapFunc($oICaptureClient, $hFile, $nBlockAlign, ByRef $iBytesWritten, ByRef $iGlitchCount)
    Local $hRes = 0
    ;Capture function for the stream, saves data directly to open file
    ;returns flags from the audioclient
    ;$AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY,
    ;$AUDCLNT_BUFFERFLAGS_SILENT
    ;$AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR
    ;
    ;If $AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY occurs $iGlitchCount is incremented and packet is skipped
    ;$iBytesWritten is incremented by number of bytes written
    Local $iPacketLength = 0, $iNumFramesAvailable = 0, $iFlags = 0
    Local $pData, $nBytes, $iFlags_Persist = 0

    $hRes = $oICaptureClient.GetNextPacketSize($iPacketLength) ;Check if there are any packets to retrieve
    While $iPacketLength <> 0 And Not $hRes
        $iFlags = 0
        ;Get the available data in the shared buffer.
        $oICaptureClient.GetBuffer($pData, $iNumFramesAvailable, $iFlags, 0, 0)
        $iFlags_Persist = BitOR($iFlags_Persist, $iFlags)
        If $iFlags = $AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY And $iBytesWritten <> 0 Then
            $iGlitchCount += 1
        Else
            _WinAPI_WriteFile($hFile, $pData, $iNumFramesAvailable * $nBlockAlign, $nBytes)
            $iBytesWritten += $nBytes
        EndIf
        $oICaptureClient.ReleaseBuffer($iNumFramesAvailable) ;We are done with the buffer release it back to client

        $oICaptureClient.GetNextPacketSize($iPacketLength) ;Check if there are more packets to retrieve
    WEnd

    Return $iFlags_Persist
EndFunc   ;==>CapFunc

#Region ### WAVE ###
Func WavHeader_Init($pWFX, $bReinit = False)
    ;Initializes the wav header to the stream format passed in $pWFX
    ;set $bReinit = True if you change the format as its only created once
    Local Const $WAVE_FORMAT_PCM = 1
    Local Const $tagDSWAVEHEADER = "struct; char RIFF[4]; uint FileSize; char WAVE[4]; char FMT[4]; uint FMTLen; " & _
            "word Format; word Channels; uint SampleRate; uint BytesPerSec; word BlockAlign; " & _
            "word BitsPerSample; char DATA[4]; uint DATALen; endstruct;"

    Static Local $tWave = DllStructCreate($tagDSWAVEHEADER)
    Static Local $pWFX_Last = 0

    If $bReinit Or ($pWFX <> 0 And $pWFX <> $pWFX_Last) Then
        $pWFX_Last = $pWFX
        DllStructSetData($tWave, "RIFF", "RIFF")
        DllStructSetData($tWave, "FileSize", 0)
        DllStructSetData($tWave, "WAVE", "WAVE")
        DllStructSetData($tWave, "FMT", "fmt ")
        DllStructSetData($tWave, "FMTLen", 16)
        DllStructSetData($tWave, "Format", $WAVE_FORMAT_PCM) ;Must be WAVE_FORMAT_PCM
        DllStructSetData($tWave, "Channels", _WFx_Get($pWFX, "nChannels"))
        DllStructSetData($tWave, "SampleRate", _WFx_Get($pWFX, "nSamplesPerSec"))
        DllStructSetData($tWave, "BytesPerSec", _WFx_Get($pWFX, "nAvgBytesPerSec"))
        DllStructSetData($tWave, "BlockAlign", _WFx_Get($pWFX, "nBlockAlign"))
        DllStructSetData($tWave, "BitsPerSample", _WFx_Get($pWFX, "wBitsPerSample"))
        DllStructSetData($tWave, "DATA", "data")
    EndIf
    Return $tWave
EndFunc   ;==>WavHeader_Init

Func WavSaver($hFileOpen)
    ;Updates FileSize and DATALen fields in the wav header
    Local $tWaveHeader = WavHeader_Init(0)
    Local $iWaveHeaderLen = DllStructGetSize($tWaveHeader)

    Local $nBytes

    Local $iFileSz = _WinAPI_GetFileSizeEx($hFileOpen)
    DllStructSetData($tWaveHeader, "FileSize", $iFileSz - 8)
    DllStructSetData($tWaveHeader, "DATALen", $iFileSz - $iWaveHeaderLen)

    _WinAPI_SetFilePointer($hFileOpen, 0, 0) ;Rewrite the wavheader at the beginning of the file
    _WinAPI_WriteFile($hFileOpen, DllStructGetPtr($tWaveHeader), $iWaveHeaderLen, $nBytes)

    Return $iFileSz
EndFunc   ;==>WavSaver
#EndRegion ### WAVE ###

Func ListAvailableEndpoints()
    ConsoleWrite('- Func ListAvailableEndpoints()' & @CRLF)
    Local $aEP
    Local $aAvEPs = _WASAPI_EnumAudioEndpoints($eRender)
    ConsoleWrite("Available Render Endpoints:" & @crlf)
    for $i = 1 to $aAvEPs[0]
        $aEP = _WASAPI_EndpointInfo($aAvEPs[$i])
        ConsoleWrite($aEP[$eWASInfo_Name] & @CRlf)
    Next
    ConsoleWrite('--- --- --- --- --- --- --- --- --- --- --- ' & @Crlf)
    $aAvEPs = _WASAPI_EnumAudioEndpoints($eCapture)
    ConsoleWrite("Available Capture Endpoints:" & @crlf)
    for $i = 1 to $aAvEPs[0]
        $aEP = _WASAPI_EndpointInfo($aAvEPs[$i])
        ConsoleWrite($aEP[$eWASInfo_Name] & @CRlf)
    Next
    ConsoleWrite('--- --- --- --- --- --- --- --- --- --- --- ' & @Crlf)
EndFunc

Func ListDeviceProperties($sDeviceID)
    ConsoleWrite('- Func ListDeviceProperties("' & $sDeviceID & '")' & @CRLF)
    Local $aInfo = _WASAPI_EndpointInfo($sDeviceID)
    ConsoleWrite("Device Name: " & $aInfo[$eWASInfo_Name] & @crlf)
    ConsoleWrite("Description: " & $aInfo[$eWASinfo_Desc] & @crlf)
    ConsoleWrite("Interface: " & $aInfo[$eWASinfo_Interface] & @crlf)
    ConsoleWrite("System Name: " & $aInfo[$eWASinfo_SystemName] & @crlf)
    ConsoleWrite("DeviceID: " & $aInfo[$eWASinfo_ID] & @crlf)
    ConsoleWrite("GUID: " & $aInfo[$eWASinfo_GUID] & @crlf)
    ConsoleWrite('--- --- --- --- --- --- --- --- --- --- --- ' & @Crlf)
    ConsoleWrite(@crlf & @crlf)
EndFunc

the example with the added "ConsoleWrite()"'s

 

Edited by argumentum
more tries

Share this post


Link to post
Share on other sites

AH, I see you are using windows 10 there.

I read that windows 10 now allows a loopback interface to co exist with even driven mode but maybe thats a LIE

@argumentum can you try editing this line in the UDF?

Around Line 317

    If @OSBuild >= 10000 Then $oIAudioClient_Loopback = 0
    ;On Windows versions prior to Windows 10, a pull-mode capture client will not receive
    ;any events when a stream is initialized with event-driven buffering
    ;(AUDCLNT_STREAMFLAGS_EVENTCALLBACK) and is loopback-enabled (AUDCLNT_STREAMFLAGS_LOOPBACK)

Share this post


Link to post
Share on other sites
2 minutes ago, Bilgus said:

@argumentum can you try editing this line in the UDF?

I removed the line as I don't know what else to do with it.

Spoiler
>"C:\ProgFilesSelf\AutoIt3\SciTE\..\AutoIt3.exe" "C:\ProgFilesSelf\AutoIt3\SciTE\AutoIt3Wrapper\AutoIt3Wrapper.au3" /run /prod /ErrorStdOut /in "C:\AU3code\WasApi_capture\WasApi_capture_Example.au3" /UserParams    
+>14:51:40 Starting AutoIt3Wrapper v.19.102.1901.0 SciTE v.4.1.2.0   Keyboard:00000409  OS:WIN_10/  CPU:X64 OS:X64  Environment(Language:0409)  CodePage:0  utf8.auto.check:4
+>         SciTEDir => C:\ProgFilesSelf\AutoIt3\SciTE   UserDir => C:\Users\Tester\AppData\Local\AutoIt v3\SciTE\AutoIt3Wrapper   SCITE_USERHOME => C:\Users\Tester\AppData\Local\AutoIt v3\SciTE 
>Running AU3Check (3.3.14.5)  from:C:\ProgFilesSelf\AutoIt3  input:C:\AU3code\WasApi_capture\WasApi_capture_Example.au3
+>14:51:40 AU3Check ended.rc:0
>Running:(3.3.14.5):C:\ProgFilesSelf\AutoIt3\autoit3.exe "C:\AU3code\WasApi_capture\WasApi_capture_Example.au3"    
+>Setting Hotkeys...--> Press Ctrl+Alt+Break to Restart or Ctrl+Break to Stop
- Func ListAvailableEndpoints()
Available Render Endpoints:
--- --- --- --- --- --- --- --- --- --- --- 
Available Capture Endpoints:
--- --- --- --- --- --- --- --- --- --- --- 
Capturing From: Headset Earphone (2- Arctis Pro Wireless Chat)
ESC key ends capture
- Func ListDeviceProperties("{0.0.0.00000000}.{43d508ee-19c5-4513-bd52-e1b7ba57ee82}")
Device Name: Headset Earphone (2- Arctis Pro Wireless Chat)
Description: Headset Earphone
Interface: 2- Arctis Pro Wireless Chat
System Name: Arctis Pro Wireless Chat
DeviceID: {0.0.0.00000000}.{43d508ee-19c5-4513-bd52-e1b7ba57ee82}
GUID: {43D508EE-19C5-4513-BD52-E1B7BA57EE82}
--- --- --- --- --- --- --- --- --- --- --- 


- Func _Main("{0.0.0.00000000}.{43d508ee-19c5-4513-bd52-e1b7ba57ee82}")
Buffer Sz: 96000 Bytes
1 s
2 s
3 s
4 s
5 s
6 s
7 s
8 s
9 s
Exit or silence
C:\AU3code\WasApi_capture\wavecap145150.wav     0 bytes
+>14:51:50 AutoIt3.exe ended.rc:0
+>14:51:50 AutoIt3Wrapper Finished.
>Exit code: 0    Time: 10.09

 

 

Share this post


Link to post
Share on other sites

yeah just comment it out ok so we have events happening now but the files is still 0 Bytes?

That might be an issue in the example try removing this flag

Line 27

    Local $hFile = _WinAPI_CreateFileEx($sTmpFile, $CREATE_ALWAYS, $GENERIC_WRITE, 0, $FILE_ATTRIBUTE_TEMPORARY)

 

Sorry I don't have Windows 10 to test on

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...