6

Ich versuche, Bilder aus einem mp4-Video-Stream zu extrahieren. Nach der Suche nach Dingen scheint es die richtige Art zu sein, Media Foundations in C++ zu verwenden und den Rahmen zu öffnen/Dinge daraus zu lesen.Wie greife ich Frames aus einem Video-Stream in Windows 8 modernen Apps?

Es gibt sehr wenig Dokumentation und Beispiele, aber nach einigen Grabungen scheint es, als hätten einige Leute Erfolg damit gehabt, Frames in eine Textur zu lesen und den Inhalt dieser Textur in eine speicherlesbare Textur zu kopieren (Ich bin mir nicht einmal sicher, ob ich die richtigen Begriffe hier verwende). Zu versuchen, was ich gefunden habe, gibt mir aber Fehler und ich mache wahrscheinlich eine Menge Sachen falsch.

Hier ist ein kurzes Stück Code von wo ich versuche, das zu tun (Projekt selbst an der Unterseite angebracht).

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) 
     );    
} 

Der Fehler, den ich bekommen ist:

Erste-Chance-Ausnahme bei 0x76814B32 in App1.exe: Microsoft C++ Ausnahme: Platform :: InvalidArgumentException^am Speicherplatz 0x07AFF60C. HRESULT: 0x80070057

Ich bin nicht wirklich sicher, wie es weiter zu verfolgen, da, wie ich schon sagte, gibt es sehr wenig Dokumente darüber.

Here's the modified sample Ich arbeite ab. Diese Frage ist spezifisch für WinRT (Windows 8-Anwendungen).

+0

Kannst du mir bitte meinen Code mitteilen, da ich gerade eine App für WP 8.1 entwickle und mir die C++ Programmierung nicht bekannt ist? Es wäre eine große Hilfe von dir. – Xyroid

+0

Hallo, Ich arbeite an einer Winrt App und ich muss auch Frames aus Video extrahieren. Ich habe deinen Code benutzt, aber es extrahiert nur die ersten 2 Frames. Gibt es etwas Besonderes zu ändern? Ich habe den Code aktualisiert, wie Sie in der Bearbeitung dieser Frage erwähnt haben. –

+0

Können Sie Arbeitscode freigeben? –

Antwort

5

UPDATE Erfolg !! siehe bearbeiten unten


Einige Teil Erfolg, aber vielleicht genug, um Ihre Frage zu beantworten. Bitte lesen Sie weiter.

Auf meinem System hat das Debuggen der Ausnahme gezeigt, dass die OnTimer()-Funktion beim Versuch fehlgeschlagen ist, TransferVideoFrame() aufzurufen. Der Fehler war InvalidArgumentException.

Also, ein bisschen Googeln führte zu meiner ersten Entdeckung - es gibt anscheinend a bug in NVIDIA drivers - was bedeutet, die Videowiedergabe scheint mit 11 und 10 Feature-Ebenen zu versagen.

So war meine erste Änderung in der Funktion CreateDX11Device() wie folgt:

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 
    }; 

Jetzt TransferVideoFrame() immer noch nicht, sondern gibt E_FAIL (als HRESULT) anstelle eines ungültigen Argument.

Mehr führte googeln meiner second discovery -

, die ein Beispiel war die Verwendung von TransferVideoFrame() zeigt ohne CreateTexture2D() mit der Textur vorab erstellen. Ich sehe, Sie hatten bereits einen Code in ähnlich zu diesem, der aber nicht verwendet wurde, also haben Sie wahrscheinlich den gleichen Link gefunden.

Wie auch immer, ich jetzt diesen Code verwendet, um die Video-Frame zu erhalten:

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

Nachdem Sie das getan, ich sehe, dass TransferVideoFrame() erfolgreich ist (gut!) Aber Map() auf kopierte Textur Aufruf - m_spCopyTexture - weil das fehlschlägt Textur wurde nicht mit CPU-Lesezugriff erstellt.

So, ich habe nur Ihre Lese-/Schreib-m_spRenderTexture als Ziel der Kopie verwendet, weil das die richtigen Flags hat und aufgrund der vorherigen Änderung, die ich nicht mehr verwendet wurde.

 //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); 

nun auf meinem System, die OnTimer() Funktion nicht ausfällt. Videobilder werden an die Textur gerendert und die Pixeldaten werden erfolgreich in den Speicherpuffer kopiert.

Bevor Sie nachsehen, ob es weitere Probleme gibt, ist es vielleicht eine gute Zeit, um zu sehen, ob Sie den gleichen Fortschritt wie bisher machen können. Wenn Sie diese Antwort mit weiteren Informationen kommentieren, bearbeite ich die Antwort, um nach Möglichkeit weitere Hilfe hinzuzufügen.

EDIT

Änderungen an Textur Beschreibung in 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)); 

Beachten Sie auch, dass ein Speicherleck gibt es, dass irgendwann geklärt werden muss (ich bin sicher, dass Sie sich bewusst sind) - der Speicher in der folgenden Zeile zugeordnet wird nie freigegeben:

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

ERFOLG

Es ist mir jetzt gelungen, ein einzelnes Bild zu speichern, aber jetzt ohne die Verwendung der Kopiestruktur.

Zuerst habe ich die neueste Version von the DirectXTex Library heruntergeladen, die DX11 Textur Hilfsfunktionen bietet, zum Beispiel ein Bild aus einer Textur zu extrahieren und in Datei zu speichern. The instructions zum Hinzufügen der DirectXTex-Bibliothek zu Ihrer Lösung, da ein vorhandenes Projekt sorgfältig befolgt werden muss, wobei die für Windows 8 Store-Apps erforderlichen Änderungen zu beachten sind.

Einmal wird die obige Bibliothek enthalten, verwiesen wird und gebaut, fügen Sie die folgenden #include ‚s FramePlayer.cpp

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

Schließlich ist der zentrale Abschnitt des Codes in FramePlayer::OnTimer() Bedürfnisse der folgenden ähnlich sein. Du wirst sehen, ich speichere nur jedes Mal den gleichen Dateinamen, so dass dies geändert werden muss, um z.B.eine Rahmennummer an den Namen

// 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));    

Ich habe keine Zeit jetzt diese noch weiter zu nehmen, aber ich bin sehr zufrieden mit dem, was ich bisher erreicht habe :-))

Können Sie ein frisches Aussehen und aktualisieren Sie Ihre Ergebnisse in Kommentaren?

+0

Wow. Vielen Dank. Ich werde es heute oder morgen anschauen. –

+1

Ok viel Glück! Denken Sie daran, dass mein Test mp4 176 * 144 war, also haben die hartcodierten Größen Ihre 600 * 400 ersetzt. Diese Größen sollten wirklich von der tatsächlichen Rahmengröße kommen. –

+0

Hey .. fing an, dies zu betrachten, aber am Anfang fehlgeschlagen ... Die Zeile: \t \t \t 'MEDIA :: ThrowIfFailed (m_spDX11Device-> CreateTexture2D (& texDesc, NULL, & m_spRenderTexture));' scheint auf einem ungültigen Argument fehlschlagen . Haben Sie dort Änderungen vorgenommen? –

0

Schauen Sie sich die Video Thumbnail Sample und die Source Reader Dokumentation an.

Sie können Beispielcode unter SDK Root\Samples\multimedia\mediafoundation\VideoThumbnail

+0

Dies ist nicht auf WinRT ausgerichtet. –

-2

Ich denke, OpenCV können Sie helfen. OpenCV bietet API zum Aufnehmen von Bildern aus Kamera- oder Videodateien. Sie können es hier herunterladen http://opencv.org/downloads.html. Das Folgende ist eine Demo, die ich mit "OpenCV 2.3.1" geschrieben habe.

#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; 
} 
+0

Ich bin auf der Suche nach einer WinRT-Lösung. OpenCV unterstützt das nicht. Zumindest jetzt noch nicht. –