首页 > 解决方案 > 如何在 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,然后在图像冻结时停止增加。

标签: c++winapi

解决方案


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,那么您可以将CComPtranddata指针包装在一个简单的内部,然后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...
}

推荐阅读