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

2023-12-21

我正在尝试从 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)
        );            
}

我得到的错误是:

App1.exe 中 0x76814B32 处的首次机会异常:Microsoft C++ 异常:Platform::InvalidArgumentException ^ 位于内存位置 0x07AFF60C。 H结果:0x80070057

我不太确定如何进一步追求它,因为正如我所说,关于它的文档很少。

这是修改后的示例 http://www.box.net/s/0hcg28pwka2x3s2od9om我正在工作。此问题特定于 WinRT(Windows 8 应用程序)。


UPDATE成功!!请参阅底部的编辑


Some partial成功,但也许足以回答你的问题。请继续阅读。

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

所以,一些谷歌搜索导致了我的第一个发现 - 显然有NVIDIA 驱动程序中的一个错误 http://social.msdn.microsoft.com/Forums/en-US/winappswithnativecode/thread/2f467b60-a99b-481d-baa1-fe0be124d8bb- 这意味着 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
    };

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

更多谷歌搜索导致我的第二个发现 http://answers.flyppdevportal.com/categories/metro/nativecode.aspx?ID=69b3aa89-c5af-4fc4-9dd6-d38387a4ef8b -

这是一个显示使用的示例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()功能不会失败。视频帧渲染到纹理,像素数据成功复制到内存缓冲区。

在查看是否存在进一步的问题之前,也许现在是看看您是否可以取得与我迄今为止相同的进展的好时机。如果您对此答案发表评论并提供更多信息,我将编辑答案以在可能的情况下添加更多帮助。

EDIT

对纹理描述进行的更改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

SUCCESS

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

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

一旦包含、引用并构建了上述库,添加以下内容#include's to 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));            

我现在没有时间进一步讨论,但我对迄今为止所取得的成就感到非常满意:-))

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Windows 8 现代应用程序上从视频流中抓取帧? 的相关文章

随机推荐