Как получить кадры из видеопотока в современных приложениях Windows 8?

Я пытаюсь извлечь изображения из видеопотока в формате mp4. После поиска вещей, кажется, что правильный способ сделать это — использовать Media Foundations в 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)
);
}

Я получаю ошибку:

Исключение первого шанса в 0x76814B32 в App1.exe: исключение Microsoft C ++: Platform :: InvalidArgumentException ^ в ячейке памяти 0x07AFF60C. HRESULT: 0x80070057

Я не совсем уверен, как продолжать это, поскольку, как я уже сказал, есть очень мало документов об этом.

Вот модифицированный образец Я работаю над Этот вопрос специфичен для WinRT (приложения для Windows 8).

7

Решение

ОБНОВИТЬ успех !! см редактировать внизу


Немного частичный успех, но, возможно, достаточно, чтобы ответить на ваш вопрос. Пожалуйста, продолжайте читать.

Отладка исключения в моей системе показала, что OnTimer() сбой функции при попытке вызова TransferVideoFrame(), Ошибка, которую он дал, была InvalidArgumentException,

Итак, немного Googling привело к моему первому открытию — там, по-видимому, ошибка в драйверах 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 — не удается, потому что эта текстура не была создана с правами чтения ЦП.

Итак, я просто использовал ваше чтение / запись 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.

После того, как вышеуказанная библиотека включена, на нее есть ссылки и она построена, добавьте следующее #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));

У меня сейчас нет времени, чтобы продолжить, но я очень доволен тем, чего я достиг на данный момент :-))

Можете ли вы по-новому взглянуть и обновить свои результаты в комментариях?

6

Другие решения

Посмотрите на Пример миниатюры видео и Источник Читатель документация.

Вы можете найти образец кода под SDK Root\Samples\multimedia\mediafoundation\VideoThumbnail

0

Я думаю, что 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;
}
-2
По вопросам рекламы [email protected]