Я пытаюсь извлечь изображения из видеопотока в формате 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).
ОБНОВИТЬ успех !! см редактировать внизу
Немного частичный успех, но, возможно, достаточно, чтобы ответить на ваш вопрос. Пожалуйста, продолжайте читать.
Отладка исключения в моей системе показала, что 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));
У меня сейчас нет времени, чтобы продолжить, но я очень доволен тем, чего я достиг на данный момент :-))
Можете ли вы по-новому взглянуть и обновить свои результаты в комментариях?
Посмотрите на Пример миниатюры видео и Источник Читатель документация.
Вы можете найти образец кода под SDK Root\Samples\multimedia\mediafoundation\VideoThumbnail
Я думаю, что 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;
}