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

我正在尝试从mp4视频流中提取图像。 看完东西后,似乎正确的方法是在C ++中使用Media Foundations并从中打开框架/读取的东西。

通过文档和样本很少,但经过一些挖掘,似乎有些人通过将纹理读入框架并将该纹理的内容复制到内存可读纹理中来获得成功(我甚至没有当然如果我在这里使用正确的术语)。 尝试我发现虽然给了我错误,我可能做了一堆错误的东西。

下面是我尝试做的一小段代码(项目本身附在底部)。

    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 ++异常:内存位置0x07AFF60C处的Platform :: InvalidArgumentException ^。 HRESULT:80070057

我真的不知道如何进一步追求它,因为像我说的那样,关于它的文档非常少。

这是我正在修改的修改后的示例。 这个问题是特定于WinRT(Windows 8应用程序)。


更新成功! 请参阅底部的修改


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

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

因此,有一点谷歌搜索引发了我的第一个发现 - 显然是NVIDIA驱动程序中的一个错误 - 这意味着视频播放似乎失败了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)而不是无效的参数。

更多谷歌搜索引发了我的第二个发现 -

这是一个使用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

成功

我现在已经成功地保存了一个单独的框架,但现在没有使用复制纹理。

首先,我下载了最新版本的DirectXTex库,它提供DX11纹理帮助功能,例如从纹理中提取图像并保存到文件。 需要谨慎地遵循将DirectXTex库作为现有项目添加到解决方案的说明,并注意Windows 8商店应用程序所需的更改。

一旦上面的库被包含,引用和构建,请将以下#includeFramePlayer.cpp

#include "..DirectXTexDirectXTex.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));            

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

你可以重新看看并在评论中更新结果吗?


查看视频缩略图示例和源读者文档。

您可以在SDK RootSamplesmultimediamediafoundationVideoThumbnail下找到示例代码


我认为OpenCV可能会帮助你。 OpenCV提供了从相机或视频文件捕获帧的API。 你可以在这里下载http://opencv.org/downloads.html。 以下是我用“OpenCV 2.3.1”编写的演示。

#include "opencv.hpp"

using namespace cv;
int main()
{
    VideoCapture cap("demo.avi");   // open a video to capture
    if (!cap.isOpened())        // check if succeeded
        return -1;

    Mat frame;
    namedWindow("Demo", CV_WINDOW_NORMAL);
    // Loop to capture frame and show on the window
    while (1) {
        cap >> frame;
        if (frame.empty())
            break;

        imshow("Demo", frame);
        if (waitKey(33) >= 0)  // pause 33ms every frame
            break;
    }

    return 0;
}
链接地址: http://www.djcxy.com/p/70491.html

上一篇: How do I grab frames from a video stream on Windows 8 modern apps?

下一篇: platform audio library for .NET