c++ - 如何在 Win32 C++ 中通过 IMFMediaBuffer 使用自动内存管理
问题描述
我有以下代码片段
IMFMediaBuffer* pBuffer;
pSample->ConvertToContiguousBuffer(&pBuffer);
DWORD length;
pBuffer->GetCurrentLength(&length);
unsigned char* data;
HRESULT hr = pBuffer->Lock(&data, NULL, &length);
ConvertToContiguousBuffer() 函数为我提供了一个作为 IMFMediaBuffer 对象的连续缓冲区。然后 Lock() 方法设置数据指针指向数据。问题是我需要在多个线程之间共享数据。我想使用自动内存管理(shared_ptr),但问题是指针是在 Lock 函数中初始化的。在这种情况下如何使用 shared_ptr。如何使用悬空指针(数据)初始化 shared_ptr。我想在 IMFMediaBuffer 上使用 CComPtr 并使用 std::move() 来移动数据。问题是我需要通过网络发送数据。我需要数据采用无符号字符格式。我需要类似的东西:
unsigned char* data;
HRESULT hr = pBuffer->Lock(&data, NULL, &length);
std::shared_ptr<unsigned char> datapointer (data);
问题是我遇到了读取访问冲突。我认为 IMFMediaBuffer 正在被释放,然后数据不再可用。如何使 IMFMediaBuffer 持久化?另外,如何从悬空数据指针中创建 shared_ptr ?
编辑
最后我做了以下
CComPtr<IMFMediaBuffer> pBuffer;
pSample->ConvertToContiguousBuffer(&pBuffer);
DWORD length;
pBuffer->GetCurrentLength(&length);
unsigned char* data;
hr = pBuffer->Lock(&data, NULL, &length);
if (FAILED(hr))
std::cout << "Failed to get pointer from buffer";
std::shared_ptr<unsigned char[]> datacopy(new unsigned char[length]);
unsigned char* datacopypointer = datacopy.get();
memcpy(datacopypointer, data, length);
编辑
这是一个文件中的最小可重现示例。
#include <Windows.h>
#include <iostream>
#include <thread>
#include <dshow.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <shlwapi.h>
#include <vector>
#include <dvdmedia.h>
#include <CommCtrl.h>
#include <atlbase.h>
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfreadwrite")
#pragma comment(lib, "shlwapi")
#pragma comment(lib, "mfuuid")
#pragma comment(lib, "strmbase")
class SpinLock
{
private:
volatile LONG dest = 0;
LONG exchange = 100;
LONG compare = 0;
public:
void Acquire() {
while (true)
{
if (InterlockedCompareExchange(&dest, exchange, compare) == 0)
{
break;
}
}
}
void Release() {
dest = 0;
}
};
std::vector<CComPtr<IMFMediaBuffer>> images;
HWND hwnd;
int ScreenWidth;
int ScreenHeight;
std::shared_ptr<SpinLock> videolock (new SpinLock());
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
}
break;
case WM_CLOSE:
{
DestroyWindow(hwnd);
}
break;
case WM_DESTROY:
{
PostQuitMessage(0);
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
break;
}
}
template< typename T >
struct Deleter
{
void operator ()(T* p)
{
DeleteObject(p);
}
};
void StartRenderingInBackground() {
while (true) {
videolock->Acquire();
if (images.size() > 0) {
BITMAPINFOHEADER header = { sizeof(BITMAPINFOHEADER), 640, 480, 1, 24, BI_RGB, 0, NULL, NULL, NULL, NULL };
BITMAPINFO info = { header, NULL };
HDC hdc = GetDC(hwnd);
HDC drawingHDC = CreateCompatibleDC(hdc);
unsigned char* data;
DWORD length;
images.at(0)->Lock(&data, NULL, &length);
std::unique_ptr<HBITMAP__, Deleter<HBITMAP__>> hBitmap(CreateDIBitmap(hdc, &header, CBM_INIT, data, &info, DIB_RGB_COLORS));
std::unique_ptr<HBITMAP__, Deleter<HBITMAP__>> scaledHBitmap(CreateCompatibleBitmap(hdc, ScreenWidth, ScreenHeight));
SelectObject(drawingHDC, hBitmap.get());
SelectObject(hdc, scaledHBitmap.get());
StretchBlt(hdc, 0, 0, ScreenWidth, ScreenHeight, drawingHDC, 0, 0, 640, 480, SRCCOPY);
images.erase(images.begin());
}
videolock->Release();
}
}
void StartCameraInBackground() {
HRESULT hr = MFStartup(MF_VERSION);
IMFSourceReader* pReader = NULL;
IMFMediaSource* pSource = NULL;
IMFAttributes* pConfig = NULL;
IMFActivate** ppDevices = NULL;
hr = MFCreateAttributes(&pConfig, 1);
if (FAILED(hr))
std::cout << "Failed to create attribute store";
hr = pConfig->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr))
std::cout << "Failed to request capture devices";
UINT32 count = 0;
hr = MFEnumDeviceSources(pConfig, &ppDevices, &count);
if (FAILED(hr))
std::cout << "Failed to enumerate capture devices";
hr = ppDevices[0]->ActivateObject(IID_PPV_ARGS(&pSource));
if (FAILED(hr))
std::cout << "Failed to connect camera to source";
hr = MFCreateSourceReaderFromMediaSource(pSource, pConfig, &pReader);
if (FAILED(hr))
std::cout << "Failed to create source reader";
for (unsigned int i = 0; i < count; i++)
ppDevices[i]->Release();
CoTaskMemFree(ppDevices);
pSource->Release();
pConfig->Release();
DWORD streamIndex, flags;
LONGLONG llTimeStamp;
std::thread th(StartRenderingInBackground);
th.detach();
while (true) {
CComPtr<IMFSample> pSample;
hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &streamIndex, &flags, &llTimeStamp, &pSample);
if (FAILED(hr))
std::cout << "Failed to get image from camera";
if (pSample != NULL) {
CComPtr<IMFMediaBuffer> pBuffer;
pSample->ConvertToContiguousBuffer(&pBuffer);
DWORD length;
pBuffer->GetCurrentLength(&length);
unsigned char* data;
hr = pBuffer->Lock(&data, NULL, &length);
if (FAILED(hr))
std::cout << "Failed to get pointer from buffer";
videolock->Acquire();
images.insert(images.begin(), std::move(pBuffer));
videolock->Release();
}
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
AllocConsole();
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"Window";
RegisterClass(&wc);
hwnd = CreateWindowExW(0, L"Window", L"Window", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, SW_MAXIMIZE);
HMONITOR currentMonitor = MonitorFromWindow(hwnd, NULL);
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(currentMonitor, &monitorInfo);
ScreenWidth = monitorInfo.rcMonitor.right;
ScreenHeight = monitorInfo.rcMonitor.bottom;
std::thread th(StartCameraInBackground);
th.detach();
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
我将图像作为 IMFMediaBuffer 传递给受 SpinLock 实现保护的 IMFMediaBuffer 向量中的另一个线程。这是将数据传递到另一个线程的好方法吗?
此外,它起初可以工作,但一段时间后图像会冻结。你认为是什么导致了这个问题?
此外,内存使用量每 15 秒线性增加约 1MB,然后在图像冻结时停止增加。
解决方案
COM 接口是引用计数的。 std::shared_ptr
也被引用计数。std::shared_ptr
因此,当您可以使用IMFMediaBuffer
自己的引用计数时,您实际上并不需要使用(CComPtr
将为您管理)。
ConvertToContiguousBuffer()
输出一个IMFMediaBuffer
初始引用计数为 1 的值。因此,只需根据需要将该缓冲区传递给您的线程,通过其AddRef()
方法(CComPtr
可以为您处理)增加每个线程的引用计数。当每个线程完成后,它可以调用缓冲区的Release()
方法(同样,CComPtr
可以为您处理),减少引用计数。只要引用计数高于 0,data
指针就会一直保持活动状态,直到您使用Unlock()
它为止。
事实上,evenIMFMediaBuffer::Lock()
也是引用计数的!文档说您可以Lock()
多次调用,并且必须调用Unlock()
相同的次数。所以,你真的不需要Lock()
在调用的同一个线程中缓存缓冲区,ConvertToContiguousBuffer()
然后传递data
指针。您可以将其IMFMediaBuffer
本身传递给您的线程,并根据需要让它们每个Lock()
和Unlock()
它。只要其引用计数保持在 0 以上,就不应释放底层数据。
CComPtr<IMFMediaBuffer> pBuffer;
if (FAILED(pSample->ConvertToContiguousBuffer(&pBuffer))
{
std::cerr << "Failed to get media buffer";
return;
}
// pass pBuffer to each thread as needed...
// in each thread:
{
CComPtr<IMFMediaBuffer> pBuffer = ...; // received from above
DWORD length;
unsigned char* data;
if (FAILED(pBuffer->Lock(&data, NULL, &length)))
{
std::cerr << "Failed to get pointer from buffer";
return;
}
std::unique_ptr<IMFMediaBuffer, void(*)(IMFMediaBuffer*)> unlocker(pBuffer, [](IMFMediaBuffer *buf){ buf->Unlock(); });
// use data up to length as needed...
}
但是,如果您真的不想依赖这种行为,而是更愿意使用std::shared_ptr
,那么您可以将CComPtr
anddata
指针包装在一个简单的内部,然后struct
您可以传递,例如:
struct MediaBufferAndData
{
CComPtr<IMFMediaBuffer> MediaBuffer;
unsigned char* Data = nullptr;
DWORD Length = 0;
bool CanUnlock = false;
MediaBufferAndData() = default;
~MediaBufferAndData()
{
if (CanUnlock)
MediaBuffer->Unlock();
}
};
std::shared_ptr<MediaBufferAndData> pBuffer = std::make_shared<MediaBufferAndData>();
if (FAILED(pSample->ConvertToContiguousBuffer(&(pBuffer->MediaBuffer))))
{
std::cerr << "Failed to get media buffer";
return;
}
if (FAILED(pBuffer->MediaBuffer->Lock(&(pBuffer->Data), NULL, &(pBuffer->Length))))
{
std::cerr << "Failed to get pointer from buffer";
return;
}
pBuffer->CanUnlock = true;
// pass pBuffer to each thread as needed...
// in each thread:
{
std::shared_ptr<MediaBufferAndData> pBuffer = ...; // received from above
// use pBuffer->Data up to pBuffer->Length as needed...
}
推荐阅读
- angular - 将页面自动滚动到 Angular 中的选定组件
- electron - 压缩电子应用程序的大小
- spring-batch - 如何为不同的 Spring Batch Job 编写多个日志文件?
- java - 为什么输出给用户的字符串颜色名称与数组中的颜色名称不匹配?
- flutter - Flutter - 在 Visual Studio Code 中快速选择整个小部件的快捷键
- python - 使用正则表达式抓取 USGS html 数据
- python - 将 LibreOffice Impress 导出的 flash 文件转换为 mp4 格式?
- python - 在 python 上使用 TensorRT .engine 文件进行推理
- javascript - FAQ 手风琴搜索
- ios - IOS Swift-UIButton 在它自己的按钮点击事件中没有被禁用