Jump to content

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


Bilgus
 Share

Recommended Posts

Thanks for testing, I'll update the UDF

https://docs.microsoft.com/en-us/windows/desktop/api/Audioclient/nf-audioclient-iaudioclient-initialize

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). If the stream is opened with this configuration, the Initialize call succeeds, but relevant events are not raised to notify the capture client each time a buffer becomes ready for processing. To work around this, initialize a render stream in event-driven mode. Each time the client receives an event for the render stream, it must signal the capture client to run the capture thread that reads the next set of samples from the capture endpoint buffer. As of Windows 10 the relevant event handles are now set for loopback-enabled streams that are active.

Note that all streams must be opened in share mode because exclusive-mode streams cannot operate in loopback mode. For more information about audio loopback, see Loopback Recording.

LIES!

Edited by Bilgus
Link to comment
Share on other sites

It writes directly to disk so length should only depend on harddisk space

I found with the data in memory at very high bitrates as the memory filled up it started gathering a lot of glitches.

An Improvement might be a small buffer to decrease disk writes but I didn't bother for the example

Thanks for testing :)

Link to comment
Share on other sites

I've finally found a decent spec for the Wave Extensible Format:

http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html

turns out that Windows Media Player needs a 'fact' field added for Wave Extensible Formats

 

In light of the new info I've added a new function to the udf _WASAPI_RIFF_Header

this function replaces the previous wave header function in the example script

(WavHeader_Init) that I've left it commented out in the example script for now just in case an issue arises.

 

The generated WAV files do play in VLC and Windows Media Player so are probably fine.

I've also added padding to the WavSaver per the above spec but not sure how important it really is...

 

Bilgus

Link to comment
Share on other sites

added a "pause" ( since is there to code ) to the example  :)

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

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

Global Const $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)
    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 $pWFX = _WASAPI_Stream_WaveFormat($aSC)
    Local $nBlockAlign = _WFx_Get($pWFX, "nBlockAlign")
    Local $iAvgBytesPerSec = _WFx_Get($pWFX, "nAvgBytesPerSec")
    Local $tWave = _WASAPI_RIFF_Header($aSC) ;;WavHeader_Init($pWFX)

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

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

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

    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)
            $iPaused = @extended
            If $iFlags Then
                If BitAND($iFlags, $AUDCLNT_BUFFERFLAGS_SILENT) Then
                    ConsoleWrite("Paused ( waiting for sound play ) - " & $iSilence &  @CRLF) ; "Capturing Silence" & @CRLF)
                    $iSilence -= 1
                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 $iPaused Then
            ToolTip("Paused ( waiting for sound play ) - " & $iSilence)
        Else
            ToolTip(($iBytesWritten / $iAvgBytesPerSec) & "s")
        EndIf

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

    _WASAPI_Stream_StopCapture($aSC)
    _WinAPI_FlushFileBuffers($hFile)
    _WinAPI_CloseHandle($hFile)
    _WASAPI_Stream_Close($aSC) ;

    Local $hFileOpen = _WinAPI_CreateFileEx($sTmpFile, $OPEN_EXISTING, $GENERIC_WRITE, 0)
    If $hFileOpen = -1 Then
        MsgBox($MB_ICONERROR, @ScriptName & " Error", "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" & @YDAY & "_" & @HOUR & @MIN & @SEC & ".wav"

    If FileMove($sTmpFile, $sOutfile) Then
        ConsoleWrite($sOutfile & " - " & FileGetSize($sOutfile) & " bytes" & @CRLF)
    Else
        MsgBox($MB_ICONERROR, @ScriptName & " Error", "Error renaming tmpfile " & $sTmpFile)
    EndIf

EndFunc   ;==>_Main

Func CapFunc($oICaptureClient, $hFile, $nBlockAlign, ByRef $iBytesWritten, ByRef $iGlitchCount)
    ;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 $iFlags_Session = 0 ;Flags for any packet during this capture session
    Local $pData, $nBytes, $iPaused = 0

    ;Check if there are any packets to retrieve
    While (Not $oICaptureClient.GetNextPacketSize($iPacketLength)) And $iPacketLength <> 0
        $iFlags = 0
        ;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
            $iGlitchCount += 1
        ElseIf BitAND($iFlags, $AUDCLNT_BUFFERFLAGS_SILENT) Then
            $iPaused = 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
    WEnd

    Return SetError(0, $iPaused, $iFlags_Session)
EndFunc   ;==>CapFunc

#Region ### WAVE ###

Func WavSaver($hFileOpen)
    ;Updates FileSize, DATALen, dwSampleLength(if applicable) fields in the wav header
    Local $tWaveHeader = _WASAPI_RIFF_Header(0) ;;WavHeader_Init(0)
    Local $iWaveHeaderLen = DllStructGetSize($tWaveHeader)
    Local $iFileSz = _WinAPI_GetFileSizeEx($hFileOpen)

    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

#cs ;See -> _WASAPI_RIFF_Header
    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
#ce ;See -> _WASAPI_RIFF_Header
#EndRegion ### WAVE ###

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

 

 

Follow the link to my code contribution ( and other things too ).
FAQ - Please Read Before Posting.
autoit_scripter_blue_userbar.png

Link to comment
Share on other sites

I Fixed an error with Non Extensible wave formats that I introduced with the last change

 

I also incorporated the changes argumentum posted above

-- just be aware that not all devices pass back the silence flag and if the sound just recently

stopped you probably won't receive the notification even on devices that do pass the flag..

Link to comment
Share on other sites

Updated the UDF and example code No longer a WIP, I think it is pretty complete now

I tried playback as well but unfortunately it will only play wav files in the same format as the soundcard

READ same sample rate (bitrate, bits/sample)  otherwise you need to do format conversion

which is possible but I mainly was interested in this for the loopback interface and is thus beyond the scope I need it for

 

Link to comment
Share on other sites

  • Bilgus changed the title to WASAPI Audio Capture w/ loopback (WHAT U HEAR) UDF

it does not even compile now.

plus, could you please just attach the au3 files, this highlighting code isn't working for me, too much effort to test it.

Edited by Earthshine

My resources are limited. You must ask the right questions

 

Link to comment
Share on other sites

@Earthshine I attached the files to the first post

You know I attributed not being able to compile a script I copy and pasted to test

last night to a user being an arse but perhaps its the forum software placing

non-breaking white spaces into the code blocks...

 

I suppose I owe him/her an apology.

Link to comment
Share on other sites

  • 4 months later...
Link to comment
Share on other sites

  • 6 months later...

Windows version?

did you download or copy paste?

any errors?

the raw file is pcm at whatever sampling rate your sound card is set to

what does this mean 'when listen a file .raw  i listen it  bad  1 second listen good 2 second stay mute'?

 

also please post output of console..

Edited by Bilgus
Link to comment
Share on other sites

Create an account or sign in to comment

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

Create an account

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

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...