I am modifying the desktop duplication api sample kindly provided by Microsoft to capture the screen and send updates over the network to my application. I know how to actually send the data; my problem is getting the data from the ID3D11Texture2D object.
ID3D11Texture2D* m_AcquiredDesktopImage;
IDXGIResource* desktopResource = nullptr;
DXGI_OUTDUPL_FRAME_INFO FrameInfo;
// Get new frame
HRESULT hr = m_DeskDupl->AcquireNextFrame(500, &FrameInfo, &desktopResource);
// QI for IDXGIResource
hr = desktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&m_AcquiredDesktopImage));
At this point, I think the screen updates are in m_AcquiredDesktopImage. I need to transmit this data over the wire (as efficiently as possible).
This answer seems to be on the right track, but I'm new to Windows programming, so I need some additional help.
This is the only solution I can imagine utilizing IDXGIObject::GetPrivateData
Private Data are not what you are looking for at all. They are only here to attach custom values to d3d objects.
Once you have the ID3D11Texture2D object you need to read back the image from, you need to create a second one in the staging memory pool from the ID3D11Device (get the original description, change the pool, and remove the binding).
Then, you need to use the ID3D11DeviceContext to copy the texture to your staging one using CopyResource. Then you can use the context Map and Unmap api to read the image.
I got a good link which does that.. Look for the method SaveTextureToBmp
[...]
// map the texture
ComPtr<ID3D11Texture2D> mappedTexture;
D3D11_MAPPED_SUBRESOURCE mapInfo;
mapInfo.RowPitch;
hr = d3dContext->Map(
Texture,
0, // Subresource
D3D11_MAP_READ,
0, // MapFlags
&mapInfo);
if (FAILED(hr)) {
// If we failed to map the texture, copy it to a staging resource
if (hr == E_INVALIDARG) {
D3D11_TEXTURE2D_DESC desc2;
desc2.Width = desc.Width;
desc2.Height = desc.Height;
desc2.MipLevels = desc.MipLevels;
desc2.ArraySize = desc.ArraySize;
desc2.Format = desc.Format;
desc2.SampleDesc = desc.SampleDesc;
desc2.Usage = D3D11_USAGE_STAGING;
desc2.BindFlags = 0;
desc2.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc2.MiscFlags = 0;
ComPtr<ID3D11Texture2D> stagingTexture;
hr = d3dDevice->CreateTexture2D(&desc2, nullptr, &stagingTexture);
if (FAILED(hr)) {
throw MyException::Make(hr, L"Failed to create staging texture");
}
// copy the texture to a staging resource
d3dContext->CopyResource(stagingTexture.Get(), Texture);
// now, map the staging resource
hr = d3dContext->Map(
stagingTexture.Get(),
0,
D3D11_MAP_READ,
0,
&mapInfo);
if (FAILED(hr)) {
throw MyException::Make(hr, L"Failed to map staging texture");
}
mappedTexture = std::move(stagingTexture);
} else {
throw MyException::Make(hr, L"Failed to map texture.");
}
} else {
mappedTexture = Texture;
}
auto unmapResource = Finally([&] {
d3dContext->Unmap(mappedTexture.Get(), 0);
});
[...]
hr = frameEncode->WritePixels(
desc.Height,
mapInfo.RowPitch,
desc.Height * mapInfo.RowPitch,
reinterpret_cast<BYTE*>(mapInfo.pData));
if (FAILED(hr)) {
throw MyException::Make(hr, L"frameEncode->WritePixels(...) failed.");
}
Related
Is there a way to screen capture multi monitor setups with windows duplication api (IDXIOutputDuplication) rather than gdi? Windows duplication is now the preferred (faster and more efficient) way of screen capturing on Windows machines. This is a question on the Microsoft forum but the link to the code is broken and I can't find it anywhere on their website 1. Examples of multi monitor screen casting with GDI can be found but they use a separate API 2,3.
I have successfully captured one monitor in C++ using the links for Windows duplication api found here 4. The problem is that the api requires an adapter and an output. I could possibly create two threads and run the windows api on each for separate adapters. I was just wondering if there was a more elegant/better performance solution.
P.S. I have had trouble posting questions on StackOverflow in the past. Before closing this question, please let me know how I can change the content to better suit the rules:)
In response to the request for code, here is a snippet from the header file
ID3D11Device* D3DDevice = nullptr;
ID3D11DeviceContext* D3DDeviceContext = nullptr;
IDXGIOutputDuplication* DeskDupl = nullptr;
DXGI_OUTPUT_DESC OutputDesc;
from the cpp file (there are two parts, initializing and capuring):
From the initializing function:
// Create device
for (size_t i = 0; i < numDriverTypes; i++) {
hr = D3D11CreateDevice(nullptr, driverTypes[i], nullptr, 0, featureLevels, (UINT) numFeatureLevels,
D3D11_SDK_VERSION, &D3DDevice, &featureLevel, &D3DDeviceContext);
if (SUCCEEDED(hr))
break;
}
if (FAILED(hr))
return tsf::fmt("D3D11CreateDevice failed: %v", hr);
// Initialize the Desktop Duplication system
//m_OutputNumber = Output;
// Get DXGI device
IDXGIDevice* dxgiDevice = nullptr;
hr = D3DDevice->QueryInterface(__uuidof(IDXGIDevice), (void**) &dxgiDevice);
if (FAILED(hr))
return tsf::fmt("D3DDevice->QueryInterface failed: %v", hr);
// Get DXGI adapter
IDXGIAdapter* dxgiAdapter = nullptr;
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**) &dxgiAdapter);
dxgiDevice->Release();
dxgiDevice = nullptr;
if (FAILED(hr)) {
return tsf::fmt("dxgiDevice->GetParent failed: %v", hr);
//return ProcessFailure(m_Device, L"Failed to get parent DXGI Adapter", L"Error", hr, SystemTransitionsExpectedErrors);
}
// Get output
IDXGIOutput* dxgiOutput = nullptr;
hr = dxgiAdapter->EnumOutputs(OutputNumber, &dxgiOutput);
dxgiAdapter->Release();
dxgiAdapter = nullptr;
if (FAILED(hr)) {
return tsf::fmt("dxgiAdapter->EnumOutputs failed: %v", hr);
//return ProcessFailure(m_Device, L"Failed to get specified output in DUPLICATIONMANAGER", L"Error", hr, EnumOutputsExpectedErrors);
}
dxgiOutput->GetDesc(&OutputDesc);
// QI for Output 1
IDXGIOutput1* dxgiOutput1 = nullptr;
hr = dxgiOutput->QueryInterface(__uuidof(dxgiOutput1), (void**) &dxgiOutput1);
dxgiOutput->Release();
dxgiOutput = nullptr;
if (FAILED(hr))
return tsf::fmt("dxgiOutput->QueryInterface failed: %v", hr);
// Create desktop duplication
hr = dxgiOutput1->DuplicateOutput(D3DDevice, &DeskDupl);
dxgiOutput1->Release();
dxgiOutput1 = nullptr;
if (FAILED(hr)) {
if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE) {
//MessageBoxW(nullptr, L"There is already the maximum number of applications using the Desktop Duplication API running, please close one of those applications and then try again.", L"Error", MB_OK);
return "Too many desktop recorders already active";
}
return tsf::fmt("DuplicateOutput failed: %v", hr);
//return ProcessFailure(m_Device, L"Failed to get duplicate output in DUPLICATIONMANAGER", L"Error", hr, CreateDuplicationExpectedErrors);
}
From the capturing function:
IDXGIResource* deskRes = nullptr;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
hr = DeskDupl->AcquireNextFrame(0, &frameInfo, &deskRes);
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
// nothing to see here
return false;
}
if (FAILED(hr)) {
// perhaps shutdown and reinitialize
auto msg = tsf::fmt("Acquire failed: %x\n", hr);
OutputDebugStringA(msg.c_str());
return false;
}
HaveFrameLock = true;
ID3D11Texture2D* gpuTex = nullptr;
hr = deskRes->QueryInterface(__uuidof(ID3D11Texture2D), (void**) &gpuTex);
deskRes->Release();
deskRes = nullptr;
if (FAILED(hr)) {
// not expected
return false;
}
bool ok = true;
D3D11_TEXTURE2D_DESC desc;
gpuTex->GetDesc(&desc);
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.MiscFlags = 0; // D3D11_RESOURCE_MISC_GDI_COMPATIBLE ?
ID3D11Texture2D* cpuTex = nullptr;
hr = D3DDevice->CreateTexture2D(&desc, nullptr, &cpuTex);
if (SUCCEEDED(hr)) {
D3DDeviceContext->CopyResource(cpuTex, gpuTex);
} else {
// not expected
ok = false;
}
//UINT subresource = D3D11CalcSubresource(0, 0, 0);
D3D11_MAPPED_SUBRESOURCE sr;
hr = D3DDeviceContext->Map(cpuTex, 0, D3D11_MAP_READ, 0, &sr);
DeskDupl is tied to a specific adapter and output.
You will need to obtain an IDXGIOutputDuplication interface for each IDXGIOutput you wish to have duplicated. Whether those outputs are on one adapter or multiple doesn't matter. I can't speak to whether you will need to run each duplication loop on a dedicated thread.
I am porting my code to D3D12 from D3D11 and I'm trying to obtain display's refresh rate on D3D12. I use the refresh rate for precise animation timing (this is a hard requirement). This code works on D3D11:
HRESULT GetRefreshRate(IUnknown* device, IDXGISwapChain* swapChain, double* outRefreshRate)
{
Microsoft::WRL::ComPtr<IDXGIOutput> dxgiOutput;
HRESULT hr = swapChain->GetContainingOutput(&dxgiOutput);
if (FAILED(hr))
return hr;
Microsoft::WRL::ComPtr<IDXGIOutput1> dxgiOutput1;
hr = dxgiOutput.As(&dxgiOutput1);
if (FAILED(hr))
return hr;
DXGI_MODE_DESC1 emptyMode = {};
DXGI_MODE_DESC1 modeDescription;
hr = dxgiOutput1->FindClosestMatchingMode1(&emptyMode, &modeDescription, device);
if (SUCCEEDED(hr))
*outRefreshRate = (double)modeDescription.RefreshRate.Numerator / (double)modeDescription.RefreshRate.Denominator;
return hr;
}
Unfortunately, ID3D12Device does not implement IDXGIDevice interface, and FindClosestMatchingMode1 therefore fails with this error:
DXGI ERROR: IDXGIOutput::FindClosestMatchingMode: pConcernedDevice doesn't support the IDXGIDevice interface [ MISCELLANEOUS ERROR #69: ]
Is there a way to obtain IDXGIDevice when using D3D12? Alternatively, how do I determine display's refresh rate on D3D12?
I know about EnumDisplaySettings however it returns an integer and therefore lacks precision, causing drift in animations. I also found DwmGetCompositionTimingInfo, however, it seems to only support getting info for the main monitor.
I also need a solution that would work on both traditional Win32 and UWP applications. I am open to having to use two code paths for different application models if needed.
We can get the refresh rate using CCD api, here is the code for your reference:
HRESULT GetRefreshRate(IDXGISwapChain* swapChain, double* outRefreshRate)
{
ComPtr<IDXGIOutput> dxgiOutput;
HRESULT hr = swapChain->GetContainingOutput(&dxgiOutput);
// if swap chain get failed to get DXGIoutput then follow the below link get the details from remarks section
//https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-getcontainingoutput
if (SUCCEEDED(hr))
{
ComPtr<IDXGIOutput1> dxgiOutput1;
hr = dxgiOutput.As(&dxgiOutput1);
if (SUCCEEDED(hr))
{
// get the descriptor for current output
// from which associated mornitor will be fetched
DXGI_OUTPUT_DESC outputDes{};
hr = dxgiOutput->GetDesc(&outputDes);
if (SUCCEEDED(hr))
{
MONITORINFOEXW info;
info.cbSize = sizeof(info);
// get the associated monitor info
if (GetMonitorInfoW(outputDes.Monitor, &info) != 0)
{
// using the CCD get the associated path and display configuration
UINT32 requiredPaths, requiredModes;
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, &requiredModes) == ERROR_SUCCESS)
{
std::vector<DISPLAYCONFIG_PATH_INFO> paths(requiredPaths);
std::vector<DISPLAYCONFIG_MODE_INFO> modes2(requiredModes);
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, paths.data(), &requiredModes, modes2.data(), nullptr) == ERROR_SUCCESS)
{
// iterate through all the paths until find the exact source to match
for (auto& p : paths) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName;
sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
sourceName.header.size = sizeof(sourceName);
sourceName.header.adapterId = p.sourceInfo.adapterId;
sourceName.header.id = p.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&sourceName.header) == ERROR_SUCCESS)
{
// find the matched device which is associated with current device
// there may be the possibility that display may be duplicated and windows may be one of them in such scenario
// there may be two callback because source is same target will be different
// as window is on both the display so either selecting either one is ok
if (wcscmp(info.szDevice, sourceName.viewGdiDeviceName) == 0) {
// get the refresh rate
UINT numerator = p.targetInfo.refreshRate.Numerator;
UINT denominator = p.targetInfo.refreshRate.Denominator;
double refrate = (double)numerator / (double)denominator;
*outRefreshRate = refrate;
break;
}
}
}
}
else
{
hr = E_FAIL;
}
}
else
{
hr = E_FAIL;
}
}
}
}
}
return hr;
}
More details about CCD API, you can refer the link below:
https://learn.microsoft.com/en-us/windows-hardware/drivers/display/ccd-apis
I'm currently trying to develop a personal project where I will light up LEDs according to what's happening on the screen of my computer.
I've tried several solutions to capture my screen, and DirectX11 with DXGI is the fastest way I found to have a good FPS Rate.
My only issue is the following: When in full screen (for example, watching Netflix trogh Win10 App or playing any game in fullscreen), it seems that nothing is captured. I have two functions (one to set up and another one to capture a frame:
Setup Up Function:
bool DXGIScreenCapturer::Init() {
int lresult(-1);
D3D_FEATURE_LEVEL lFeatureLevel;
HRESULT hr(E_FAIL);
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
gFeatureLevels,
gNumFeatureLevels,
D3D11_SDK_VERSION,
&_lDevice,
&lFeatureLevel,
&_lImmediateContext);
if (FAILED(hr))
return false;
if (!_lDevice)
return false;
// Get DXGI device
ComPtr<IDXGIDevice> lDxgiDevice;
hr = _lDevice.As(&lDxgiDevice);
if (FAILED(hr))
return false;
// Get DXGI adapter
ComPtr<IDXGIAdapter> lDxgiAdapter;
hr = lDxgiDevice->GetParent(__uuidof(IDXGIAdapter), &lDxgiAdapter);
if (FAILED(hr))
return false;
lDxgiDevice.Reset();
UINT Output = 0;
// Get output
ComPtr<IDXGIOutput> lDxgiOutput;
hr = lDxgiAdapter->EnumOutputs(Output, &lDxgiOutput);
if (FAILED(hr))
return false;
lDxgiAdapter.Reset();
hr = lDxgiOutput->GetDesc(&_lOutputDesc);
if (FAILED(hr))
return false;
// QI for Output 1
ComPtr<IDXGIOutput1> lDxgiOutput1;
hr = lDxgiOutput.As(&lDxgiOutput1);
if (FAILED(hr))
return false;
lDxgiOutput.Reset();
// Create desktop duplication
hr = lDxgiOutput1->DuplicateOutput(_lDevice.Get(), &_lDeskDupl);
if (FAILED(hr))
return false;
lDxgiOutput1.Reset();
// Create GUI drawing texture
_lDeskDupl->GetDesc(&_lOutputDuplDesc);
// Create CPU access texture
_desc.Width = _lOutputDuplDesc.ModeDesc.Width;
_desc.Height = _lOutputDuplDesc.ModeDesc.Height;
_desc.Format = _lOutputDuplDesc.ModeDesc.Format;
std::cout << _desc.Width << "x" << _desc.Height << "\n\n\n";
_desc.ArraySize = 1;
_desc.BindFlags = 0;
_desc.MiscFlags = 0;
_desc.SampleDesc.Count = 1;
_desc.SampleDesc.Quality = 0;
_desc.MipLevels = 1;
_desc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_READ;
_desc.Usage = D3D11_USAGE::D3D11_USAGE_STAGING;
while (!CaptureScreen(_result));
_result = cv::Mat(_desc.Height, _desc.Width, CV_8UC4, _resource.pData);
return true;
}
And the capture function:
bool DXGIScreenCapturer::CaptureScreen(cv::Mat& output)
{
HRESULT hr(E_FAIL);
ComPtr<IDXGIResource> lDesktopResource = nullptr;
DXGI_OUTDUPL_FRAME_INFO lFrameInfo;
ID3D11Texture2D* currTexture = NULL;
hr = _lDeskDupl->AcquireNextFrame(999, &lFrameInfo, &lDesktopResource);
if (FAILED(hr))
return false;
if (lFrameInfo.LastPresentTime.HighPart == 0) // not interested in just mouse updates, which can happen much faster than 60fps if you really shake the mouse
{
hr = _lDeskDupl->ReleaseFrame();
return false;
}
//int accum_frames = lFrameInfo.AccumulatedFrames;
//if (accum_frames > 1) {// && current_frame != 1) {
// // TOO MANY OF THESE is the problem
// // especially after having to wait >17ms in AcquireNextFrame()
//}
// QI for ID3D11Texture2D
hr = lDesktopResource.As(&_lAcquiredDesktopImage);
// Copy image into a newly created CPU access texture
hr = _lDevice->CreateTexture2D(&_desc, NULL, &currTexture);
if (FAILED(hr))
{
hr = _lDeskDupl->ReleaseFrame();
return false;
}
if (!currTexture)
{
hr = _lDeskDupl->ReleaseFrame();
return false;
}
_lImmediateContext->CopyResource(currTexture, _lAcquiredDesktopImage.Get());
UINT subresource = D3D11CalcSubresource(0, 0, 0);
_lImmediateContext->Map(currTexture, subresource, D3D11_MAP_READ, 0, &_resource);
_lImmediateContext->Unmap(currTexture, 0);
currTexture->Release();
hr = _lDeskDupl->ReleaseFrame();
output = _result;
return true;
}
What i want to do is set the Windows Media Player's volume level. By default the volume is in-/decreased by 10% when e.g. clicking the down or up menu item (Play -> Volume -> Up), but this is, in my opinion, not fine enough (especially when e.g. skypeing with someone while listening to music).
The media player should stay an independent application.
Currently i'm using a little tool that sends app commands via SendMessage to the player with parameters as seen in spy++.
I thought of three ways to achieve my goal:
using WASAPI to get the media player's audio session and dynamically set the volume level
sending mouse down/up events to the volume slider of the media player host control by point
getting an instance of the media player control via IWMPPlayer4
including a media player control in my WPF application within a windows forms host (not preferred due to loss of independence)
Point 2 seems rather ugly due to the fact that the media player control is a COM element and has as far spy++ displays only one handle, meaning i would have to determine the exact position of the volume slider and send very precise mouse events. Additional i don't know whether this would work at all.
Point 3 has the presupposition that one can get an instance of a COM element by a handle. Since i have yet not worked with COM elements i don't know if this is possible.
Update: One can get an instance of a remote mediay player using the IWMPPlayer4 interface. Though i have to see whether one can change settings.
Point 1 has the impression on me that it would be possible without much effort.
Though i'd be facing the next problem: identifying the media players audio session. Enumerating them using IAudioSessionManager2 and displaying the name using
IAudioSessionControl2 ctrl2 = NULL;
// ...
hr = ctrl2->GetDisplayName(&name);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
String ^sessionName = gcnew String(name);
Console::WriteLine("Session name: '" + sessionName + "'");
prints most the times an emtpy string except for Mozilla Firefox and System Sounds (the other processes might not have set a session name themselfes => a default name is chosen and GetDisplayName returns an empty string).
Update 2:
As Simon Mourier pointed out one can compare the process ids to get the right ISimpleAudioVolume instance and it works as far as it comes to WMP to adopt the changes. The said instance is aquired the following way:
IMMDeviceEnumerator *pEnumerator = NULL;
ISimpleAudioVolume *pVolume = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager2 *pManager = NULL;
IAudioSessionEnumerator *pSessionEnumerator = NULL;
int sessionCount = 0;
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &pDevice);
pDevice->GetState(&deviceState);
pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&pManager);
pManager->GetSessionEnumerator(&pSessionEnumerator);
pSessionEnumerator->GetCount(&sessionCount);
for (int i = 0; i < sessionCount; i++)
{
IAudioSessionControl *ctrl = NULL;
IAudioSessionControl2 *ctrl2 = NULL;
DWORD processId = 0;
hr = pSessionEnumerator->GetSession(i, &ctrl);
if (FAILED(hr))
{
continue;
}
hr = ctrl->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&ctrl2);
if (FAILED(hr))
{
SafeRelease(ctrl);
continue;
}
hr = ctrl2->GetProcessId(&processId);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
if (processId == wmpProcessId)
{
hr = ctrl2->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&pVolume);
SafeRelease(ctrl);
SafeRelease(ctrl2);
break;
}
SafeRelease(ctrl);
SafeRelease(ctrl2);
}
When aquiering an ISimpleAudioVolume instance via a IAudioClient one has to provide a session id to have volume changes reported to event subscribers. Is this possible using this approach?
Though i know that adding a media player control to my application would be the easiest way, i'd like to not use this option if possible.
I don't know what may happened during my initial try to set the media player's volume level, but the following code works (most exception handling excluded):
HRESULT hr;
IMMDeviceEnumerator *pEnumerator = NULL;
ISimpleAudioVolume *pVolume = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager2 *pManager = NULL;
IAudioSessionEnumerator *pSessionEnumerator = NULL;
int sessionCount = 0;
int wmpProcess = GetWmpProcessId(); // Aquire WMPs process id
// Get the device enumerator and initialize the application for COM
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
// Get the default device
hr = pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
ERole::eMultimedia, &pDevice);
// Get the session 2 manager
hr = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,
NULL, (void**)&pManager);
// Get the session enumerator
hr = pManager->GetSessionEnumerator(&pSessionEnumerator);
// Get the session count
hr = pSessionEnumerator->GetCount(&sessionCount);
// Loop through all sessions
for (int i = 0; i < sessionCount; i++)
{
IAudioSessionControl *ctrl = NULL;
IAudioSessionControl2 *ctrl2 = NULL;
DWORD processId = 0;
hr = pSessionEnumerator->GetSession(i, &ctrl);
if (FAILED(hr))
{
continue;
}
hr = ctrl->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&ctrl2);
if (FAILED(hr))
{
SafeRelease(ctrl);
continue;
}
//Identify WMP process
hr = ctrl2->GetProcessId(&processId);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
if (processId != wmpProcess)
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
hr = ctrl2->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&pVolume);
if (FAILED(hr))
{
Error(hr, "Failed to get ISimpleAudioVolume.");
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
// Set the master volume
hr = pVolume->SetMasterVolume(1.0, NULL);
if (FAILED(hr))
{
Error(hr, "Failed to set the master volume.");
SafeRelease(ctrl);
SafeRelease(ctrl2);
SafeRelease(pVolume);
continue;
}
SafeRelease(ctrl);
SafeRelease(ctrl2);
SafeRelease(pVolume);
}
I'm experiencing an important memory leak in a camera application that uses an EVR (Enhanced Video Renderer). The leak happens when I prepare, run, stop and unprepare a graph several times (several MB every cycle, depending on the video resolution).
I simplified the code as much as I could; you can download it from here:
https://daptech.box.com/s/6csm91vhcawiw18u42kq
The graph has a video capture filter, a SampleGrabber filter and a renderer. When connecting, it creates an AVI Decompressor filter to complete the graph.
Here are some excerpts...
Declarations:
CComPtr<IGraphBuilder> pGraphBuilder;
CComPtr<IBaseFilter> pSource; // The capture device filter
CComPtr<IBaseFilter> pSampleGrabberFilter; // Filter to capture data that flows through the graph
CComPtr<IBaseFilter> pRenderer; // EVR
Graph preparation (validation removed):
hr = m_p->pGraphBuilder.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC);
hr = m_p->pSampleGrabberFilter.CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC);
CComQIPtr<ISampleGrabber, &IID_ISampleGrabber> pSampleGrabber(m_p->pSampleGrabberFilter);
hr = pSampleGrabber->SetBufferSamples(FALSE); // Don't copy image data for better performances
// Setup the callback
hr = pSampleGrabber->SetCallback(&m_p->cb, 1); // 0: SampleCB() is called, so that we directly access the buffer. 1: BufferCB() is called. The doc states that the function always makes a copy of the data.
CComPtr<IPin> pSampleGrabberInputPin( FindPin(m_p->pSampleGrabberFilter, PINDIR_INPUT, NULL) );
CComPtr<IPin> pSampleGrabberOutputPin( FindPin(m_p->pSampleGrabberFilter, PINDIR_OUTPUT, NULL) );
hr = m_p->pGraphBuilder->AddFilter(m_p->pSampleGrabberFilter, L"SampleGrabber");
hr = FindVideoCaptureDevice(m_p->pSource, (unsigned)iCameraIndex);
CComPtr<IPin> pSourceOutputPin( FindPin(m_p->pSource, PINDIR_OUTPUT, L"CAPTURE") );
hr = m_p->pGraphBuilder->AddFilter(m_p->pSource, L"Camera");
hr = m_p->pRenderer.CoCreateInstance(CLSID_EnhancedVideoRenderer, NULL, CLSCTX_INPROC);
hr = m_p->pGraphBuilder->AddFilter(m_p->pRenderer, L"Renderer");
hr = ConfigEVR(m_p->pRenderer, m_staticPreview.m_hWnd, uWidth, uHeight);
CComPtr<IPin> pRendererInputPin( FindPin(m_p->pRenderer, PINDIR_INPUT, NULL) );
// Now select the format:
if (!SelectFormat(pSourceOutputPin, uWidth, uHeight, bCompression))
{ MessageBox(L"No appropriate format found.\n"); return; }
// Connection:
hr = m_p->pGraphBuilder->Connect(pSourceOutputPin, pSampleGrabberInputPin);
hr = m_p->pGraphBuilder->Connect(pSampleGrabberOutputPin, pRendererInputPin);
This function is called to "unprepare" the graph:
void FreeDirectShowGraph()
{
if (pSource) DisconnectDownstream(pSource);
RemoveAndRelease(pRenderer);
RemoveAndRelease(pSampleGrabberFilter);
RemoveAndRelease(pSource);
if (pGraphBuilder) pGraphBuilder.Release();
}
Any clue would be appreciated!