Bilgus Posted February 14, 2019 Share Posted February 14, 2019 (edited) 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 expandcollapse popup#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 expandcollapse popup;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 March 5, 2019 by Bilgus Attached Files Nine and argumentum 1 1 Link to comment Share on other sites More sharing options...
Bilgus Posted February 14, 2019 Author Share Posted February 14, 2019 Hey folks, I provided a broken example with the UDF. The wavsaver was writing junk for the wav header on the final save It is fixed NOW argumentum 1 Link to comment Share on other sites More sharing options...
Bilgus Posted February 15, 2019 Author Share Posted February 15, 2019 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 argumentum and Earthshine 2 Link to comment Share on other sites More sharing options...
Earthshine Posted February 15, 2019 Share Posted February 15, 2019 (edited) 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 February 15, 2019 by Earthshine My resources are limited. You must ask the right questions Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 (edited) @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 February 16, 2019 by Bilgus Earthshine 1 Link to comment Share on other sites More sharing options...
Earthshine Posted February 16, 2019 Share Posted February 16, 2019 (edited) Ok. My device is an external dac/amp so maybe it doesn’t. I’ll try it when in office again Edited February 16, 2019 by Earthshine My resources are limited. You must ask the right questions Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 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 Link to comment Share on other sites More sharing options...
Earthshine Posted February 16, 2019 Share Posted February 16, 2019 I have the Schiit Magni amp and Modi Multibit Dac at home. I’ll test with that and my internal sound device too. Bilgus 1 My resources are limited. You must ask the right questions Link to comment Share on other sites More sharing options...
argumentum Posted February 16, 2019 Share Posted February 16, 2019 (edited) ..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 expandcollapse popup>"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 expandcollapse popup;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 February 16, 2019 by argumentum more tries Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 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) Link to comment Share on other sites More sharing options...
argumentum Posted February 16, 2019 Share Posted February 16, 2019 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 expandcollapse popup>"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 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 yeah just comment it out Link to comment Share on other sites More sharing options...
argumentum Posted February 16, 2019 Share Posted February 16, 2019 did. No timeout but no filesize either =/ Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 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 Link to comment Share on other sites More sharing options...
argumentum Posted February 16, 2019 Share Posted February 16, 2019 removed FILE_ATTRIBUTE_TEMPORARY and same =/ Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 WTH the file should be at least 44bytes due to the Wav header so I'm not sure whats going on there Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 Can You uncomment this line (69) ;ConsoleWrite($iBytesWritten & @crlf) Link to comment Share on other sites More sharing options...
argumentum Posted February 16, 2019 Share Posted February 16, 2019 lol, it did work with the 1st try, so did with the 2nd. Sowy. and thanks =D Bilgus 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
argumentum Posted February 16, 2019 Share Posted February 16, 2019 ..I was going with the FileSize in the ConsoleWrite. My bad. It works =) Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Bilgus Posted February 16, 2019 Author Share Posted February 16, 2019 Ah I was about to say maybe Windows 10 is REALLY aggressive with file buffering Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now