This commit is contained in:
恍兮惚兮 2024-05-19 02:43:10 +08:00
parent aadc0befd9
commit ff5e706b78
10 changed files with 628 additions and 8 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "plugins/libs/wil"]
path = plugins/libs/wil
url = https://github.com/microsoft/wil.git

View File

@ -19,7 +19,7 @@ from traceback import print_exc
import requests, json, subprocess, time import requests, json, subprocess, time
from PyQt5.QtCore import pyqtSignal, Qt, QUrl from PyQt5.QtCore import pyqtSignal, Qt, QUrl
import qtawesome, functools, os, base64 import qtawesome, functools, os, base64
import gobject, uuid, signal import gobject, uuid, windows, platform
from myutils.config import globalconfig, _TR, static_data from myutils.config import globalconfig, _TR, static_data
import myutils.ankiconnect as anki import myutils.ankiconnect as anki
from gui.usefulwidget import ( from gui.usefulwidget import (
@ -33,6 +33,8 @@ from gui.usefulwidget import (
getcolorbutton, getcolorbutton,
tabadd_lazy, tabadd_lazy,
) )
from myutils.subproc import subproc_w, autoproc
from myutils.wrapper import threader from myutils.wrapper import threader
from myutils.ocrutil import imageCut, ocr_run from myutils.ocrutil import imageCut, ocr_run
from gui.rangeselect import rangeselct_function from gui.rangeselect import rangeselct_function
@ -63,6 +65,35 @@ class ffmpeg_virtual_audio_capturer:
pass pass
class loopbackrecorder:
def __init__(self):
os.makedirs("./cache/tts", exist_ok=True)
self.file = os.path.abspath(
os.path.join("./cache/tts", str(time.time()) + ".wav")
)
try:
if platform.architecture()[0] == "64bit":
_6432 = "64"
elif platform.architecture()[0] == "32bit":
_6432 = "32"
self.waitsignal = str(time.time())
self.engine = autoproc(
subproc_w(
'./files/plugins/shareddllproxy{}.exe recordaudio "{}" "{}"'.format(
_6432, self.file, self.waitsignal
),
name="recordaudio",
)
)
except:
print_exc()
def end(self):
windows.SetEvent(
windows.AutoHandle(windows.CreateEvent(False, False, self.waitsignal))
)
class statusbutton(QPushButton): class statusbutton(QPushButton):
statuschanged1 = pyqtSignal(int) statuschanged1 = pyqtSignal(int)
statuschanged2 = pyqtSignal(int) statuschanged2 = pyqtSignal(int)
@ -395,7 +426,10 @@ class AnkiWindow(QWidget):
def startorendrecord(self, target: QLineEdit, idx): def startorendrecord(self, target: QLineEdit, idx):
if idx == 1: if idx == 1:
if len(globalconfig["ffmpeg"]) and os.path.exists(globalconfig["ffmpeg"]):
self.recorder = ffmpeg_virtual_audio_capturer() self.recorder = ffmpeg_virtual_audio_capturer()
else:
self.recorder = loopbackrecorder()
else: else:
self.recorder.end() self.recorder.end()
target.setText(self.recorder.file) target.setText(self.recorder.file)

View File

@ -7,6 +7,7 @@ endif()
include_directories(${CMAKE_CURRENT_LIST_DIR}) include_directories(${CMAKE_CURRENT_LIST_DIR})
include_directories(${CMAKE_CURRENT_LIST_DIR}/Detours-4.0.1/include) include_directories(${CMAKE_CURRENT_LIST_DIR}/Detours-4.0.1/include)
include_directories(${CMAKE_CURRENT_LIST_DIR}/wil/include)
if(${CMAKE_SIZEOF_VOID_P} EQUAL 4) if(${CMAKE_SIZEOF_VOID_P} EQUAL 4)
set(LTLPlatform "Win32") set(LTLPlatform "Win32")

1
plugins/libs/wil Submodule

@ -0,0 +1 @@
Subproject commit 963263543179642aa69addd13697a609c7b838d9

View File

@ -12,19 +12,17 @@ generate_product_version(
) )
add_executable(shareddllproxy shareddllproxy.cpp dllinject.cpp ntleas.cpp aspatch.cpp update.cpp ${versioninfo}) add_executable(shareddllproxy applicationloopbackaudio/LoopbackCapture.cpp applicationloopbackaudio/runer.cpp shareddllproxy.cpp dllinject.cpp ntleas.cpp aspatch.cpp update.cpp ${versioninfo})
target_precompile_headers(shareddllproxy REUSE_FROM pch) target_precompile_headers(shareddllproxy REUSE_FROM pch)
if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) if(${CMAKE_SIZEOF_VOID_P} EQUAL 8)
target_link_libraries(shareddllproxy ${Detours}) target_link_libraries(shareddllproxy Mfplat mfuuid ${Detours})
set_target_properties(shareddllproxy PROPERTIES OUTPUT_NAME "shareddllproxy64") set_target_properties(shareddllproxy PROPERTIES OUTPUT_NAME "shareddllproxy64")
else() else()
add_subdirectory(voiceroid2) add_subdirectory(voiceroid2)
add_library(x86lib dreye.cpp jbj7.cpp kingsoft.cpp le.cpp neospeech.cpp ../implsapi.cpp LR.cpp) add_library(x86lib dreye.cpp jbj7.cpp kingsoft.cpp le.cpp neospeech.cpp ../implsapi.cpp LR.cpp)
target_precompile_headers(voiceroid2 REUSE_FROM pch) target_precompile_headers(voiceroid2 REUSE_FROM pch)
target_precompile_headers(x86lib REUSE_FROM pch) target_precompile_headers(x86lib REUSE_FROM pch)
target_link_libraries(shareddllproxy x86lib voiceroid2 ${Detours}) target_link_libraries(shareddllproxy Mfplat mfuuid x86lib voiceroid2 ${Detours})
set_target_properties(shareddllproxy PROPERTIES OUTPUT_NAME "shareddllproxy32") set_target_properties(shareddllproxy PROPERTIES OUTPUT_NAME "shareddllproxy32")
endif() endif()

View File

@ -0,0 +1,58 @@
#pragma once
#include <mfidl.h>
#include <mfapi.h>
#include <mfobjects.h>
#ifndef METHODASYNCCALLBACK
#define METHODASYNCCALLBACK(Parent, AsyncCallback, pfnCallback) \
class Callback##AsyncCallback :\
public IMFAsyncCallback \
{ \
public: \
Callback##AsyncCallback() : \
_parent(((Parent*)((BYTE*)this - offsetof(Parent, m_x##AsyncCallback)))), \
_dwQueueID( MFASYNC_CALLBACK_QUEUE_MULTITHREADED ) \
{ \
} \
\
STDMETHOD_( ULONG, AddRef )() \
{ \
return _parent->AddRef(); \
} \
STDMETHOD_( ULONG, Release )() \
{ \
return _parent->Release(); \
} \
STDMETHOD( QueryInterface )( REFIID riid, void **ppvObject ) \
{ \
if (riid == IID_IMFAsyncCallback || riid == IID_IUnknown) \
{ \
(*ppvObject) = this; \
AddRef(); \
return S_OK; \
} \
*ppvObject = NULL; \
return E_NOINTERFACE; \
} \
STDMETHOD( GetParameters )( \
/* [out] */ __RPC__out DWORD *pdwFlags, \
/* [out] */ __RPC__out DWORD *pdwQueue) \
{ \
*pdwFlags = 0; \
*pdwQueue = _dwQueueID; \
return S_OK; \
} \
STDMETHOD( Invoke )( /* [out] */ __RPC__out IMFAsyncResult * pResult ) \
{ \
_parent->pfnCallback( pResult ); \
return S_OK; \
} \
void SetQueueID( DWORD dwQueueID ) { _dwQueueID = dwQueueID; } \
\
protected: \
Parent* _parent; \
DWORD _dwQueueID; \
\
} m_x##AsyncCallback;
#endif

View File

@ -0,0 +1,425 @@
#include <shlobj.h>
#include <wchar.h>
#include <iostream>
#include <audioclientactivationparams.h>
#include "LoopbackCapture.h"
#define BITS_PER_BYTE 8
HRESULT CLoopbackCapture::SetDeviceStateErrorIfFailed(HRESULT hr)
{
if (FAILED(hr))
{
m_DeviceState = DeviceState::Error;
}
return hr;
}
HRESULT CLoopbackCapture::InitializeLoopbackCapture()
{
// Create events for sample ready or user stop
RETURN_IF_FAILED(m_SampleReadyEvent.create(wil::EventOptions::None));
// Initialize MF
RETURN_IF_FAILED(MFStartup(MF_VERSION, MFSTARTUP_LITE));
// Register MMCSS work queue
DWORD dwTaskID = 0;
RETURN_IF_FAILED(MFLockSharedWorkQueue(L"Capture", 0, &dwTaskID, &m_dwQueueID));
// Set the capture event work queue to use the MMCSS queue
m_xSampleReady.SetQueueID(m_dwQueueID);
// Create the completion event as auto-reset
RETURN_IF_FAILED(m_hActivateCompleted.create(wil::EventOptions::None));
// Create the capture-stopped event as auto-reset
RETURN_IF_FAILED(m_hCaptureStopped.create(wil::EventOptions::None));
return S_OK;
}
CLoopbackCapture::~CLoopbackCapture()
{
if (m_dwQueueID != 0)
{
MFUnlockWorkQueue(m_dwQueueID);
}
}
typedef HRESULT (STDAPICALLTYPE *ActivateAudioInterfaceAsync_t)(
_In_ LPCWSTR deviceInterfacePath,
_In_ REFIID riid,
_In_opt_ PROPVARIANT *activationParams,
_In_ IActivateAudioInterfaceCompletionHandler *completionHandler,
_COM_Outptr_ IActivateAudioInterfaceAsyncOperation **activationOperation
);
HRESULT CLoopbackCapture::ActivateAudioInterface(DWORD processId, bool includeProcessTree)
{
return SetDeviceStateErrorIfFailed([&]() -> HRESULT
{
AUDIOCLIENT_ACTIVATION_PARAMS audioclientActivationParams = {};
audioclientActivationParams.ActivationType = AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK;
audioclientActivationParams.ProcessLoopbackParams.ProcessLoopbackMode = includeProcessTree ?
PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE : PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE;
audioclientActivationParams.ProcessLoopbackParams.TargetProcessId = processId;
PROPVARIANT activateParams = {};
activateParams.vt = VT_BLOB;
activateParams.blob.cbSize = sizeof(audioclientActivationParams);
activateParams.blob.pBlobData = (BYTE*)&audioclientActivationParams;
wil::com_ptr_nothrow<IActivateAudioInterfaceAsyncOperation> asyncOp;
auto pActivateAudioInterfaceAsync=(ActivateAudioInterfaceAsync_t)GetProcAddress(LoadLibrary(L"Mmdevapi.dll"),"ActivateAudioInterfaceAsync");
if(pActivateAudioInterfaceAsync==0)return S_FALSE;
RETURN_IF_FAILED(pActivateAudioInterfaceAsync(VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK, __uuidof(IAudioClient), &activateParams, this, &asyncOp));
// Wait for activation completion
m_hActivateCompleted.wait();
return m_activateResult;
}());
}
//
// ActivateCompleted()
//
// Callback implementation of ActivateAudioInterfaceAsync function. This will be called on MTA thread
// when results of the activation are available.
//
HRESULT CLoopbackCapture::ActivateCompleted(IActivateAudioInterfaceAsyncOperation* operation)
{
m_activateResult = SetDeviceStateErrorIfFailed([&]()->HRESULT
{
// Check for a successful activation result
HRESULT hrActivateResult = E_UNEXPECTED;
wil::com_ptr_nothrow<IUnknown> punkAudioInterface;
RETURN_IF_FAILED(operation->GetActivateResult(&hrActivateResult, &punkAudioInterface));
RETURN_IF_FAILED(hrActivateResult);
// Get the pointer for the Audio Client
RETURN_IF_FAILED(punkAudioInterface.copy_to(&m_AudioClient));
// The app can also call m_AudioClient->GetMixFormat instead to get the capture format.
// 16 - bit PCM format.
m_CaptureFormat.wFormatTag = WAVE_FORMAT_PCM;
m_CaptureFormat.nChannels = 2;
m_CaptureFormat.nSamplesPerSec = 44100;
m_CaptureFormat.wBitsPerSample = 16;
m_CaptureFormat.nBlockAlign = m_CaptureFormat.nChannels * m_CaptureFormat.wBitsPerSample / BITS_PER_BYTE;
m_CaptureFormat.nAvgBytesPerSec = m_CaptureFormat.nSamplesPerSec * m_CaptureFormat.nBlockAlign;
// Initialize the AudioClient in Shared Mode with the user specified buffer
RETURN_IF_FAILED(m_AudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
200000,
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM,
&m_CaptureFormat,
nullptr));
// Get the maximum size of the AudioClient Buffer
RETURN_IF_FAILED(m_AudioClient->GetBufferSize(&m_BufferFrames));
// Get the capture client
RETURN_IF_FAILED(m_AudioClient->GetService(IID_PPV_ARGS(&m_AudioCaptureClient)));
// Create Async callback for sample events
RETURN_IF_FAILED(MFCreateAsyncResult(nullptr, &m_xSampleReady, nullptr, &m_SampleReadyAsyncResult));
// Tell the system which event handle it should signal when an audio buffer is ready to be processed by the client
RETURN_IF_FAILED(m_AudioClient->SetEventHandle(m_SampleReadyEvent.get()));
// Creates the WAV file.
RETURN_IF_FAILED(CreateWAVFile());
// Everything is ready.
m_DeviceState = DeviceState::Initialized;
return S_OK;
}());
// Let ActivateAudioInterface know that m_activateResult has the result of the activation attempt.
m_hActivateCompleted.SetEvent();
return S_OK;
}
//
// CreateWAVFile()
//
// Creates a WAV file in music folder
//
HRESULT CLoopbackCapture::CreateWAVFile()
{
return SetDeviceStateErrorIfFailed([&]()->HRESULT
{
m_hFile.reset(CreateFile(m_outputFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));
RETURN_LAST_ERROR_IF(!m_hFile);
// Create and write the WAV header
// 1. RIFF chunk descriptor
DWORD header[] = {
FCC('RIFF'), // RIFF header
0, // Total size of WAV (will be filled in later)
FCC('WAVE'), // WAVE FourCC
FCC('fmt '), // Start of 'fmt ' chunk
sizeof(m_CaptureFormat) // Size of fmt chunk
};
DWORD dwBytesWritten = 0;
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), header, sizeof(header), &dwBytesWritten, NULL));
m_cbHeaderSize += dwBytesWritten;
// 2. The fmt sub-chunk
WI_ASSERT(m_CaptureFormat.cbSize == 0);
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &m_CaptureFormat, sizeof(m_CaptureFormat), &dwBytesWritten, NULL));
m_cbHeaderSize += dwBytesWritten;
// 3. The data sub-chunk
DWORD data[] = { FCC('data'), 0 }; // Start of 'data' chunk
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), data, sizeof(data), &dwBytesWritten, NULL));
m_cbHeaderSize += dwBytesWritten;
return S_OK;
}());
}
//
// FixWAVHeader()
//
// The size values were not known when we originally wrote the header, so now go through and fix the values
//
HRESULT CLoopbackCapture::FixWAVHeader()
{
// Write the size of the 'data' chunk first
DWORD dwPtr = SetFilePointer(m_hFile.get(), m_cbHeaderSize - sizeof(DWORD), NULL, FILE_BEGIN);
RETURN_LAST_ERROR_IF(INVALID_SET_FILE_POINTER == dwPtr);
DWORD dwBytesWritten = 0;
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &m_cbDataSize, sizeof(DWORD), &dwBytesWritten, NULL));
// Write the total file size, minus RIFF chunk and size
// sizeof(DWORD) == sizeof(FOURCC)
RETURN_LAST_ERROR_IF(INVALID_SET_FILE_POINTER == SetFilePointer(m_hFile.get(), sizeof(DWORD), NULL, FILE_BEGIN));
DWORD cbTotalSize = m_cbDataSize + m_cbHeaderSize - 8;
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &cbTotalSize, sizeof(DWORD), &dwBytesWritten, NULL));
RETURN_IF_WIN32_BOOL_FALSE(FlushFileBuffers(m_hFile.get()));
return S_OK;
}
HRESULT CLoopbackCapture::StartCaptureAsync(DWORD processId, bool includeProcessTree, PCWSTR outputFileName)
{
m_outputFileName = outputFileName;
auto resetOutputFileName = wil::scope_exit([&] { m_outputFileName = nullptr; });
RETURN_IF_FAILED(InitializeLoopbackCapture());
RETURN_IF_FAILED(ActivateAudioInterface(processId, includeProcessTree));
// We should be in the initialzied state if this is the first time through getting ready to capture.
if (m_DeviceState == DeviceState::Initialized)
{
m_DeviceState = DeviceState::Starting;
return MFPutWorkItem2(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_xStartCapture, nullptr);
}
return S_OK;
}
//
// OnStartCapture()
//
// Callback method to start capture
//
HRESULT CLoopbackCapture::OnStartCapture(IMFAsyncResult* pResult)
{
return SetDeviceStateErrorIfFailed([&]()->HRESULT
{
// Start the capture
RETURN_IF_FAILED(m_AudioClient->Start());
m_DeviceState = DeviceState::Capturing;
MFPutWaitingWorkItem(m_SampleReadyEvent.get(), 0, m_SampleReadyAsyncResult.get(), &m_SampleReadyKey);
return S_OK;
}());
}
//
// StopCaptureAsync()
//
// Stop capture asynchronously via MF Work Item
//
HRESULT CLoopbackCapture::StopCaptureAsync()
{
RETURN_HR_IF(E_NOT_VALID_STATE, (m_DeviceState != DeviceState::Capturing) &&
(m_DeviceState != DeviceState::Error));
m_DeviceState = DeviceState::Stopping;
RETURN_IF_FAILED(MFPutWorkItem2(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_xStopCapture, nullptr));
// Wait for capture to stop
m_hCaptureStopped.wait();
return S_OK;
}
//
// OnStopCapture()
//
// Callback method to stop capture
//
HRESULT CLoopbackCapture::OnStopCapture(IMFAsyncResult* pResult)
{
// Stop capture by cancelling Work Item
// Cancel the queued work item (if any)
if (0 != m_SampleReadyKey)
{
MFCancelWorkItem(m_SampleReadyKey);
m_SampleReadyKey = 0;
}
m_AudioClient->Stop();
m_SampleReadyAsyncResult.reset();
return FinishCaptureAsync();
}
//
// FinishCaptureAsync()
//
// Finalizes WAV file on a separate thread via MF Work Item
//
HRESULT CLoopbackCapture::FinishCaptureAsync()
{
// We should be flushing when this is called
return MFPutWorkItem2(MFASYNC_CALLBACK_QUEUE_MULTITHREADED, 0, &m_xFinishCapture, nullptr);
}
//
// OnFinishCapture()
//
// Because of the asynchronous nature of the MF Work Queues and the DataWriter, there could still be
// a sample processing. So this will get called to finalize the WAV header.
//
HRESULT CLoopbackCapture::OnFinishCapture(IMFAsyncResult* pResult)
{
// FixWAVHeader will set the DeviceStateStopped when all async tasks are complete
HRESULT hr = FixWAVHeader();
m_DeviceState = DeviceState::Stopped;
m_hCaptureStopped.SetEvent();
return hr;
}
//
// OnSampleReady()
//
// Callback method when ready to fill sample buffer
//
HRESULT CLoopbackCapture::OnSampleReady(IMFAsyncResult* pResult)
{
if (SUCCEEDED(OnAudioSampleRequested()))
{
// Re-queue work item for next sample
if (m_DeviceState == DeviceState::Capturing)
{
// Re-queue work item for next sample
return MFPutWaitingWorkItem(m_SampleReadyEvent.get(), 0, m_SampleReadyAsyncResult.get(), &m_SampleReadyKey);
}
}
else
{
m_DeviceState = DeviceState::Error;
}
return S_OK;
}
//
// OnAudioSampleRequested()
//
// Called when audio device fires m_SampleReadyEvent
//
HRESULT CLoopbackCapture::OnAudioSampleRequested()
{
UINT32 FramesAvailable = 0;
BYTE* Data = nullptr;
DWORD dwCaptureFlags;
UINT64 u64DevicePosition = 0;
UINT64 u64QPCPosition = 0;
DWORD cbBytesToCapture = 0;
auto lock = m_CritSec.lock();
// If this flag is set, we have already queued up the async call to finialize the WAV header
// So we don't want to grab or write any more data that would possibly give us an invalid size
if (m_DeviceState == DeviceState::Stopping)
{
return S_OK;
}
// A word on why we have a loop here;
// Suppose it has been 10 milliseconds or so since the last time
// this routine was invoked, and that we're capturing 48000 samples per second.
//
// The audio engine can be reasonably expected to have accumulated about that much
// audio data - that is, about 480 samples.
//
// However, the audio engine is free to accumulate this in various ways:
// a. as a single packet of 480 samples, OR
// b. as a packet of 80 samples plus a packet of 400 samples, OR
// c. as 48 packets of 10 samples each.
//
// In particular, there is no guarantee that this routine will be
// run once for each packet.
//
// So every time this routine runs, we need to read ALL the packets
// that are now available;
//
// We do this by calling IAudioCaptureClient::GetNextPacketSize
// over and over again until it indicates there are no more packets remaining.
while (SUCCEEDED(m_AudioCaptureClient->GetNextPacketSize(&FramesAvailable)) && FramesAvailable > 0)
{
cbBytesToCapture = FramesAvailable * m_CaptureFormat.nBlockAlign;
// WAV files have a 4GB (0xFFFFFFFF) size limit, so likely we have hit that limit when we
// overflow here. Time to stop the capture
if ((m_cbDataSize + cbBytesToCapture) < m_cbDataSize)
{
StopCaptureAsync();
break;
}
// Get sample buffer
RETURN_IF_FAILED(m_AudioCaptureClient->GetBuffer(&Data, &FramesAvailable, &dwCaptureFlags, &u64DevicePosition, &u64QPCPosition));
// Write File
if (m_DeviceState != DeviceState::Stopping)
{
DWORD dwBytesWritten = 0;
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(
m_hFile.get(),
Data,
cbBytesToCapture,
&dwBytesWritten,
NULL));
}
// Release buffer back
m_AudioCaptureClient->ReleaseBuffer(FramesAvailable);
// Increase the size of our 'data' chunk. m_cbDataSize needs to be accurate
m_cbDataSize += cbBytesToCapture;
}
return S_OK;
}

View File

@ -0,0 +1,86 @@
#pragma once
#include <AudioClient.h>
#include <mmdeviceapi.h>
#include <initguid.h>
#include <guiddef.h>
#include <mfapi.h>
#include <wrl\implements.h>
#include <wil\com.h>
#include <wil\result.h>
#include "Common.h"
using namespace Microsoft::WRL;
class CLoopbackCapture :
public RuntimeClass< RuntimeClassFlags< ClassicCom >, FtmBase, IActivateAudioInterfaceCompletionHandler >
{
public:
//CLoopbackCapture() = default;
~CLoopbackCapture();
HRESULT StartCaptureAsync(DWORD processId, bool includeProcessTree, PCWSTR outputFileName);
HRESULT StopCaptureAsync();
METHODASYNCCALLBACK(CLoopbackCapture, StartCapture, OnStartCapture);
METHODASYNCCALLBACK(CLoopbackCapture, StopCapture, OnStopCapture);
METHODASYNCCALLBACK(CLoopbackCapture, SampleReady, OnSampleReady);
METHODASYNCCALLBACK(CLoopbackCapture, FinishCapture, OnFinishCapture);
// IActivateAudioInterfaceCompletionHandler
STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation* operation);
private:
// NB: All states >= Initialized will allow some methods
// to be called successfully on the Audio Client
enum class DeviceState
{
Uninitialized,
Error,
Initialized,
Starting,
Capturing,
Stopping,
Stopped,
};
HRESULT OnStartCapture(IMFAsyncResult* pResult);
HRESULT OnStopCapture(IMFAsyncResult* pResult);
HRESULT OnFinishCapture(IMFAsyncResult* pResult);
HRESULT OnSampleReady(IMFAsyncResult* pResult);
HRESULT InitializeLoopbackCapture();
HRESULT CreateWAVFile();
HRESULT FixWAVHeader();
HRESULT OnAudioSampleRequested();
HRESULT ActivateAudioInterface(DWORD processId, bool includeProcessTree);
HRESULT FinishCaptureAsync();
HRESULT SetDeviceStateErrorIfFailed(HRESULT hr);
wil::com_ptr_nothrow<IAudioClient> m_AudioClient;
WAVEFORMATEX m_CaptureFormat{};
UINT32 m_BufferFrames = 0;
wil::com_ptr_nothrow<IAudioCaptureClient> m_AudioCaptureClient;
wil::com_ptr_nothrow<IMFAsyncResult> m_SampleReadyAsyncResult;
wil::unique_event_nothrow m_SampleReadyEvent;
MFWORKITEM_KEY m_SampleReadyKey = 0;
wil::unique_hfile m_hFile;
wil::critical_section m_CritSec;
DWORD m_dwQueueID = 0;
DWORD m_cbHeaderSize = 0;
DWORD m_cbDataSize = 0;
// These two members are used to communicate between the main thread
// and the ActivateCompleted callback.
PCWSTR m_outputFileName = nullptr;
HRESULT m_activateResult = E_UNEXPECTED;
DeviceState m_DeviceState{ DeviceState::Uninitialized };
wil::unique_event_nothrow m_hActivateCompleted;
wil::unique_event_nothrow m_hCaptureStopped;
};

View File

@ -0,0 +1,12 @@
#include "LoopbackCapture.h"
int recordaudio(int argc, wchar_t *argv[])
{
CLoopbackCapture loopbackCapture;
loopbackCapture.StartCaptureAsync(GetCurrentProcessId(), false, argv[1]);
WaitForSingleObject(
CreateEventW(&allAccess, FALSE, FALSE, argv[2]),
INFINITE);
loopbackCapture.StopCaptureAsync();
return 0;
}

View File

@ -1,4 +1,5 @@
#pragma comment(linker, "/subsystem:windows /entry:wmainCRTStartup") #pragma comment(linker, "/subsystem:windows /entry:wmainCRTStartup")
int recordaudio(int argc, wchar_t *argv[]);
int dllinjectwmain(int argc, wchar_t *argv[]); int dllinjectwmain(int argc, wchar_t *argv[]);
int ntleaswmain(int argc, wchar_t *wargv[]); int ntleaswmain(int argc, wchar_t *wargv[]);
@ -64,7 +65,8 @@ int wmain(int argc, wchar_t *argv[])
return listprocessmodule(argc - 1, argv + 1); return listprocessmodule(argc - 1, argv + 1);
if (argv0 == L"update") if (argv0 == L"update")
return updatewmain(argc - 1, argv + 1); return updatewmain(argc - 1, argv + 1);
if (argv0 == L"recordaudio")
return recordaudio(argc - 1, argv + 1);
#ifndef _WIN64 #ifndef _WIN64
else if (argv0 == L"LR") else if (argv0 == L"LR")
return LRwmain(argc - 1, argv + 1); return LRwmain(argc - 1, argv + 1);