7

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++ 例外: Platform::InvalidArgumentException ^ メモリ位置 0x07AFF60C で。HRESULT:0x80070057

私が言ったように、それについてのドキュメントはほとんどないので、それをさらに追求する方法が本当にわかりません。

これが私が取り組んでいる修正されたサンプルです。この質問は、WinRT (Windows 8 アプリ) に固有のものです。

4

3 に答える 3

6

アップデート成功!! 下部の編集を参照してください


部分的な成功ですが、質問に答えるには十分かもしれません。読み進めてください。

私のシステムでは、例外をデバッグする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 として) を返します。

さらにグーグルが私の2番目の発見につながりました-

これは、テクスチャを事前に作成するために を使用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

成功

個々のフレームを保存することに成功しましたが、コピー テクスチャを使用しなくなりました。

まず、DX11 テクスチャ ヘルパー機能を提供するDirectXTex ライブラリの最新バージョンをダウンロードしました。たとえば、テクスチャから画像を抽出してファイルに保存します。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));            

今はこれ以上進める時間はありませんが、これまでに達成したことには非常に満足しています :-))

もう一度見直して、コメントで結果を更新していただけますか?

于 2013-04-08T08:16:56.753 に答える
0

Video Thumbnail SampleSource Readerのドキュメントを参照してください。

サンプルコードは以下にありますSDK Root\Samples\multimedia\mediafoundation\VideoThumbnail

于 2013-04-05T11:59:16.647 に答える
-2

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;
}
于 2013-04-06T04:32:50.053 に答える