#include ;GUID Conversion #include ;_WinAPI_CreateEvent #include ;_WinAPI_CoTaskMemFree #include ;_WinAPI_StrLen #include ;$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 header 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) 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 $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. @extended contains the last WFX pointer stored ; =============================================================================================================================== Func _WFx_Struct($pWFX) Local $iErr = 0 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) $iErr = @error If (Not $iErr) And DllStructGetData($tWFX, "cbSize") > 0 Then $tWFX = DllStructCreate($tagWAVEFORMATEXTENSIBLE, $pWFX) EndIf EndIf EndIf Return SetError($iErr, $pWFX_Last, $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 = 0 ; 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 = 0) ;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 ###