c++ - 如何在 Windows 8 现代应用程序上从视频流中抓取帧?

标签 c++ windows-8 windows-runtime winrt-xaml ms-media-foundation

我正在尝试从 mp4 视频流中提取图像。查找内容后,似乎正确的做法是使用 C++ 中的媒体基础并打开框架/从中读取内容。

文档和示例很少,但经过一些挖掘,似乎有些人通过将帧读入纹理并将该纹理的内容复制到内存可读纹理(我我什至不确定我在这里使用的术语是否正确)。尝试我发现的东西虽然给了我错误,但我可能做错了很多事情。

这是我尝试执行此操作的一小段代码(项目本身附加在底部)。

    ComPtr<ID3D11Texture2D> spTextureDst;
    MEDIA::ThrowIfFailed(
        m_spDX11SwapChain->GetBuffer(0, IID_PPV_ARGS(&spTextureDst))
        );

    auto rcNormalized = MFVideoNormalizedRect();
    rcNormalized.left = 0;
    rcNormalized.right = 1;
    rcNormalized.top = 0;
    rcNormalized.bottom = 1;
    MEDIA::ThrowIfFailed(
        m_spMediaEngine->TransferVideoFrame(m_spRenderTexture.Get(), &rcNormalized, &m_rcTarget, &m_bkgColor)
        );

    //copy the render target texture to the readable texture.
    m_spDX11DeviceContext->CopySubresourceRegion(m_spCopyTexture.Get(),0,0,0,0,m_spRenderTexture.Get(),0,NULL);
    m_spDX11DeviceContext->Flush();

    //Map the readable texture;                 
    D3D11_MAPPED_SUBRESOURCE mapped = {0};
    m_spDX11DeviceContext->Map(m_spCopyTexture.Get(),0,D3D11_MAP_READ,0,&mapped);
    void* buffer = ::CoTaskMemAlloc(600 * 400 * 3);
    memcpy(buffer, mapped.pData,600 * 400 * 3);
    //unmap so we can copy during next update.
    m_spDX11DeviceContext->Unmap(m_spCopyTexture.Get(),0);


    // and the present it to the screen
    MEDIA::ThrowIfFailed(
        m_spDX11SwapChain->Present(1, 0)
        );            
}

我得到的错误是:

First-chance exception at 0x76814B32 in App1.exe: Microsoft C++ exception: Platform::InvalidArgumentException ^ at memory location 0x07AFF60C. HRESULT:0x80070057

我不太确定如何进一步研究它,因为正如我所说,关于它的文档非常少。

Here's the modified sample我正在工作。此问题特定于 WinRT(Windows 8 应用程序)。

最佳答案

更新成功!!查看底部的编辑


有些部分 成功,但也许足以回答您的问题。请继续阅读。

在我的系统上,调试异常显示 OnTimer() 函数在尝试调用 TransferVideoFrame() 时失败。它给出的错误是 InvalidArgumentException

所以,谷歌搜索让我有了第一个发现 - 显然有 a bug in NVIDIA drivers - 这意味着视频播放似乎在 11 和 10 功能级别时失败。

所以我的第一个更改是在函数 CreateDX11Device() 中,如下所示:

static const D3D_FEATURE_LEVEL levels[] = {
/*
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,  
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
*/
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

现在 TransferVideoFrame() 仍然失败,但给出了 E_FAIL(作为 HRESULT)而不是无效参数。

更多谷歌搜索导致我的 second discovery -

这是一个示例,显示使用 TransferVideoFrame() 而不使用 CreateTexture2D() 来预创建纹理。我看到您在 OnTimer() 中已经有一些与此类似但未使用的代码,所以我猜您找到了相同的链接。

无论如何,我现在使用这段代码来获取视频帧:

ComPtr <ID3D11Texture2D> spTextureDst;
m_spDX11SwapChain->GetBuffer (0, IID_PPV_ARGS (&spTextureDst));
m_spMediaEngine->TransferVideoFrame (spTextureDst.Get (), nullptr, &m_rcTarget, &m_bkgColor);

完成此操作后,我看到 TransferVideoFrame() 成功(很好!)但是在您复制的纹理上调用了 Map() - m_spCopyTexture - 失败,因为该纹理不是使用 CPU 读取权限创建的。

所以,我只是使用您的读/写 m_spRenderTexture 作为拷贝的目标,因为它具有正确的标志,并且由于之前的更改,我不再使用它。

        //copy the render target texture to the readable texture.
        m_spDX11DeviceContext->CopySubresourceRegion(m_spRenderTexture.Get(),0,0,0,0,spTextureDst.Get(),0,NULL);
        m_spDX11DeviceContext->Flush();

        //Map the readable texture;                 
        D3D11_MAPPED_SUBRESOURCE mapped = {0};
        HRESULT hr = m_spDX11DeviceContext->Map(m_spRenderTexture.Get(),0,D3D11_MAP_READ,0,&mapped);
        void* buffer = ::CoTaskMemAlloc(176 * 144 * 3);
        memcpy(buffer, mapped.pData,176 * 144 * 3);
        //unmap so we can copy during next update.
        m_spDX11DeviceContext->Unmap(m_spRenderTexture.Get(),0);

现在,在我的系统上,OnTimer() 函数不会失败。视频帧渲染到纹理,像素数据成功复制到内存缓冲区。

在查看是否还有其他问题之前,也许现在是看看您是否能取得与我目前一样的进展的好时机。如果您对此答案发表评论并提供更多信息,我将编辑答案以尽可能添加更多帮助。

编辑

FramePlayer::CreateBackBuffers() 中对纹理描述所做的更改

        //make first texture cpu readable
        D3D11_TEXTURE2D_DESC texDesc = {0};
        texDesc.Width = 176;
        texDesc.Height = 144;
        texDesc.MipLevels = 1;
        texDesc.ArraySize = 1;
        texDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        texDesc.SampleDesc.Count = 1;
        texDesc.SampleDesc.Quality =  0;
        texDesc.Usage = D3D11_USAGE_STAGING;
        texDesc.BindFlags = 0;
        texDesc.CPUAccessFlags =  D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
        texDesc.MiscFlags = 0;

        MEDIA::ThrowIfFailed(m_spDX11Device->CreateTexture2D(&texDesc,NULL,&m_spRenderTexture));

另请注意,有时需要清除内存泄漏(我相信您知道)- 在以下行中分配的内存永远不会被释放:

void* buffer = ::CoTaskMemAlloc(176 * 144 * 3); // sizes changed for my test

成功

我现在已经成功保存了单个帧,但现在没有使用复制纹理。

首先,我下载了最新版the DirectXTex Library ,它提供 DX11 纹理辅助函数,例如从纹理中提取图像并保存到文件中。 The instructions将 DirectXTex 库添加到您的解决方案,因为需要仔细遵循现有项目,注意 Windows 8 商店应用程序所需的更改。

包含、引用和构建上述库后,将以下 #include 添加到 FramePlayer.cpp

#include "..\DirectXTex\DirectXTex.h"  // nb - use the relative path you copied to
#include <wincodec.h>

最后,FramePlayer::OnTimer() 中的核心代码部分需要类似于以下内容。你会看到我每次都保存到相同的文件名,所以这需要修改以添加例如名称的帧编号

// new frame available at the media engine so get it 
ComPtr<ID3D11Texture2D> spTextureDst;
MEDIA::ThrowIfFailed(m_spDX11SwapChain->GetBuffer(0, IID_PPV_ARGS(&spTextureDst)));

auto rcNormalized = MFVideoNormalizedRect();
rcNormalized.left = 0;
rcNormalized.right = 1;
rcNormalized.top = 0;
rcNormalized.bottom = 1;
MEDIA::ThrowIfFailed(m_spMediaEngine->TransferVideoFrame(spTextureDst.Get(), &rcNormalized, &m_rcTarget, &m_bkgColor));

// capture an image from the DX11 texture
DirectX::ScratchImage pImage;
HRESULT hr = DirectX::CaptureTexture(m_spDX11Device.Get(), m_spDX11DeviceContext.Get(), spTextureDst.Get(), pImage);
if (SUCCEEDED(hr))
{
    // get the image object from the wrapper
    const DirectX::Image *pRealImage = pImage.GetImage(0, 0, 0);

    // set some place to save the image frame
    StorageFolder ^dataFolder = ApplicationData::Current->LocalFolder;
    Platform::String ^szPath = dataFolder->Path + "\\frame.png";

    // save the image to file
    hr = DirectX::SaveToWICFile(*pRealImage, DirectX::WIC_FLAGS_NONE, GUID_ContainerFormatPng, szPath->Data());
}

// and the present it to the screen
MEDIA::ThrowIfFailed(m_spDX11SwapChain->Present(1, 0));            

我现在没有时间进一步了解,但我对目前所取得的成就感到非常满意:-))

您能否重新审视一下并在评论中更新您的结果?

关于c++ - 如何在 Windows 8 现代应用程序上从视频流中抓取帧?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15711054/

相关文章:

c++ - 如何将解码缓冲区从 ffmpeg 映射到 QVideoFrame?

c# - 文件流到字节 [] : Windows XP vs Windows 8

multithreading - WinRT API 中是否有与 Android 处理程序等效的东西?

c++ - C++ Metro 风格应用程序中的资源

c++ - Gtest 测试项目链接到其他可执行文件

c++ - fstream 系列的实现是否因平台而异?

c# - 如何将 C# 对象引用传入和传出 C++

xaml - Windows 8 应用程序 : Fixed button width: Failed to create a 'Windows.Foundation.Double' from the text '1in' ?

c++ - win32(全屏)无边框窗口重叠任务栏

c# - 将字符串转换为控件名称 c# wp8.1