6

Sto cercando di estrarre le immagini da un flusso video mp4. Dopo aver guardato le cose, sembra che il modo corretto per farlo sia usare Media Foundations in C++ e aprire il frame/leggere roba da esso.Come posso afferrare fotogrammi da un flusso video su app moderne di Windows 8?

C'è molto poco di documentazione e esempi, ma dopo alcuni scavi, sembra che alcune persone abbiano avuto successo nel fare ciò leggendo fotogrammi in una trama e copiando il contenuto di quella texture in una trama leggibile dalla memoria (Non sono nemmeno sicuro se sto usando i termini corretti qui). Provare ciò che ho trovato mi dà errori e probabilmente sto sbagliando un sacco di cose.

Ecco un breve pezzo di codice da dove provo a farlo (il progetto stesso è allegato in basso).

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

L'errore che ottengo è:

eccezione first-chance a 0x76814B32 in App1.exe: Microsoft C++ eccezione: Piattaforma :: InvalidArgumentException^in posizione di memoria 0x07AFF60C. HRESULT: 0x80070057

Non sono davvero sicuro di come perseguirlo ulteriormente poiché, come ho detto, ci sono pochissimi documenti a riguardo.

Here's the modified sample Sto lavorando fuori di. Questa domanda è specifica per WinRT (applicazioni Windows 8).

+0

Puoi condividere il tuo codice con me visto che sto sviluppando un'applicazione per WP 8.1 e sono sconosciuto alla programmazione C++? Sarebbe un grande aiuto da parte tua. – Xyroid

+0

Ciao, sto lavorando ad un'applicazione winrt e devo anche estrarre i frame dal video. Ho usato il tuo codice ma estrae solo il primo 2 fotogramma. C'è qualcosa di specifico da cambiare. Ho aggiornato il codice come indicato nell'edit apr di questa domanda. –

+0

Puoi condividere codice funzionante? –

risposta

5

UPDATE successo !! vedi di modifica nella parte inferiore


Alcuni parziale successo, ma forse abbastanza per rispondere alla tua domanda. Per favore continua a leggere.

Sul mio sistema, il debug dell'eccezione indica che la funzione OnTimer() non è riuscita quando si è tentato di chiamare TransferVideoFrame(). L'errore che ha dato è stato InvalidArgumentException.

Quindi, un po 'di Google ha portato alla mia prima scoperta - apparentemente c'è a bug in NVIDIA drivers - il che significa che la riproduzione del video sembra fallire con 11 e 10 livelli di funzionalità.

Così il mio primo cambiamento era in funzione CreateDX11Device() come segue:

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

Ora TransferVideoFrame() non riesce ancora, ma dà E_FAIL (come un HRESULT) invece di un argomento non valido.

Più Googling ha portato al mio second discovery -

Che era un esempio che mostra l'utilizzo di TransferVideoFrame() senza utilizzare CreateTexture2D() di pre-creare la trama. Vedo che hai già un codice in OnTimer() simile a questo ma che non è stato utilizzato, quindi suppongo che tu abbia trovato lo stesso link.

Ad ogni modo, ora usato questo codice per ottenere il fotogramma video:

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

Dopo aver fatto questo, vedo che TransferVideoFrame() riesce (bene!), Ma chiamando Map() sulla consistenza copiato - m_spCopyTexture - non riesce perché la trama non è stata creata con l'accesso in lettura della CPU.

Quindi, ho appena usato la lettura/scrittura m_spRenderTexture come destinazione della copia, perché ha le bandiere corrette e, a causa della modifica precedente, non lo usavo più.

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

Ora, sul mio sistema, la funzione OnTimer() non fallisce. I fotogrammi video vengono renderizzati alla trama e i dati dei pixel vengono copiati correttamente nel buffer di memoria.

Prima di vedere se ci sono ulteriori problemi, forse questo è un buon momento per vedere se è possibile fare gli stessi progressi che ho finora. Se commentate questa risposta con maggiori informazioni, modificheremo la risposta per aggiungere ulteriore aiuto se possibile.

EDIT

Le modifiche apportate alla descrizione texture 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)); 

Si noti anche che ci sia una perdita di memoria che deve essere chiarito qualche volta (sono sicuro che siete a conoscenza) - la memoria allocata nella riga successiva viene mai liberato:

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

SUCCESSO

Ora sono riuscito a salvare un singolo fotogramma, ma ora senza l'uso della trama di copia.

Innanzitutto, ho scaricato l'ultima versione di the DirectXTex Library, che fornisce le funzioni di supporto di texture DX11, ad esempio per estrarre un'immagine da una trama e salvarla in un file. The instructions per aggiungere la libreria DirectXTex alla soluzione in quanto un progetto esistente deve essere seguito attentamente, prendendo nota delle modifiche necessarie per le App Store di Windows 8.

volta, la libreria di cui sopra è incluso, riferimento e costruito, aggiungere le seguenti #include 's per FramePlayer.cpp

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

Infine, la sezione centrale del codice FramePlayer::OnTimer() deve essere simile alla seguente. Vedrai che mi limiterò a salvare lo stesso nome ogni volta, quindi è necessario modificare per aggiungere ad es.un numero di frame al nome

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

non ho tempo ora di prendere questo ulteriormente, ma io sono molto soddisfatto di quello che ho ottenuto finora :-))

Potete prendere un nuovo aspetto e aggiornare i risultati nei commenti?

+0

Wow. Grazie. Lo guarderò oggi o domani. –

+1

Ok buona fortuna! Tieni presente che il mio test mp4 era 176 * 144, quindi le dimensioni codificate in alto sostituivano il tuo 600 * 400. Queste dimensioni dovrebbero in realtà venire dalla dimensione effettiva del frame. E-mail: –

+0

Hey .. Iniziato a guardare questo, ma fallendo all'inizio ... La riga: \t \t \t 'MEDIA :: ThrowIfFailed (m_spDX11Device-> CreateTexture2D (& texDesc, NULL, & m_spRenderTexture));' sembra fallire su un argomento non valido . Hai fatto delle modifiche lì? –

0

Vedere lo Video Thumbnail Sample e la documentazione Source Reader.

È possibile trovare il codice di esempio sotto SDK Root\Samples\multimedia\mediafoundation\VideoThumbnail

+0

Questo non è mirato a WinRT. –

-2

Penso OpenCV può aiutare. OpenCV offre api per catturare fotogrammi da fotocamera o file video. Puoi scaricarlo qui http://opencv.org/downloads.html. Quanto segue è una demo che ho scritto con "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; 
} 
+0

Sto cercando una soluzione WinRT. OpenCV non supporta questo. Almeno non ancora. –