Files
UnrealEngineUWP/Engine/Source/Runtime/AVIWriter/Private/CapturePin.cpp

376 lines
8.9 KiB
C++
Raw Normal View History

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "CapturePin.h"
#include "AVIWriter.h"
Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3209340) #lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3209340 on 2016/11/23 by Ben.Marsh Convert UE4 codebase to an "include what you use" model - where every header just includes the dependencies it needs, rather than every source file including large monolithic headers like Engine.h and UnrealEd.h. Measured full rebuild times around 2x faster using XGE on Windows, and improvements of 25% or more for incremental builds and full rebuilds on most other platforms. * Every header now includes everything it needs to compile. * There's a CoreMinimal.h header that gets you a set of ubiquitous types from Core (eg. FString, FName, TArray, FVector, etc...). Most headers now include this first. * There's a CoreTypes.h header that sets up primitive UE4 types and build macros (int32, PLATFORM_WIN64, etc...). All headers in Core include this first, as does CoreMinimal.h. * Every .cpp file includes its matching .h file first. * This helps validate that each header is including everything it needs to compile. * No engine code includes a monolithic header such as Engine.h or UnrealEd.h any more. * You will get a warning if you try to include one of these from the engine. They still exist for compatibility with game projects and do not produce warnings when included there. * There have only been minor changes to our internal games down to accommodate these changes. The intent is for this to be as seamless as possible. * No engine code explicitly includes a precompiled header any more. * We still use PCHs, but they're force-included on the compiler command line by UnrealBuildTool instead. This lets us tune what they contain without breaking any existing include dependencies. * PCHs are generated by a tool to get a statistical amount of coverage for the source files using it, and I've seeded the new shared PCHs to contain any header included by > 15% of source files. Tool used to generate this transform is at Engine\Source\Programs\IncludeTool. [CL 3209342 by Ben Marsh in Main branch]
2016-11-23 15:48:37 -05:00
#include "Misc/ScopeExit.h"
#include "CaptureSource.h"
DEFINE_LOG_CATEGORY(LogMovieCapture);
#if PLATFORM_WINDOWS && !UE_BUILD_MINIMAL
FCapturePin::FCapturePin(HRESULT *phr, CSource *pFilter, const FAVIWriter& InWriter)
: CSourceStream(NAME("Push Source"), phr, pFilter, L"Capture")
, FrameLength(UNITS/InWriter.Options.CaptureFPS)
, Writer(InWriter)
{
ImageWidth = Writer.GetWidth();
ImageHeight = Writer.GetHeight();
}
FCapturePin::~FCapturePin()
{
}
//
// GetMediaType
//
// Prefer 5 formats - 8, 16 (*2), 24 or 32 bits per pixel
//
// Prefered types should be ordered by quality, with zero as highest quality.
// Therefore, iPosition =
// 0 Return a 32bit mediatype
// 1 Return a 24bit mediatype
// 2 Return 16bit RGB565
// 3 Return a 16bit mediatype (rgb555)
// 4 Return 8 bit palettised format
// >4 Invalid
//
HRESULT FCapturePin::GetMediaType(int32 iPosition, CMediaType *pmt)
{
CheckPointer(pmt,E_POINTER);
CAutoLock cAutoLock(m_pFilter->pStateLock());
if(iPosition < 0)
{
return E_INVALIDARG;
}
// Have we run off the end of types?
if(iPosition > 4)
{
return VFW_S_NO_MORE_ITEMS;
}
VIDEOINFO *pvi = (VIDEOINFO *) pmt->AllocFormatBuffer(sizeof(VIDEOINFO));
if(NULL == pvi)
{
return(E_OUTOFMEMORY);
}
// Initialize the VideoInfo structure before configuring its members
ZeroMemory(pvi, sizeof(VIDEOINFO));
// We only supply 32bit RGB
// Since we use RGB888 (the default for 32 bit), there is
// no reason to use BI_BITFIELDS to specify the RGB
// masks. Also, not everything supports BI_BITFIELDS
pvi->bmiHeader.biCompression = BI_RGB;
pvi->bmiHeader.biBitCount = 32;
// Adjust the parameters common to all formats
pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pvi->bmiHeader.biWidth = ImageWidth;
pvi->bmiHeader.biHeight = ImageHeight;// * (-1); // negative 1 to flip the image vertically
pvi->bmiHeader.biPlanes = 1;
pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader);
pvi->bmiHeader.biClrImportant = 0;
pvi->AvgTimePerFrame = FrameLength;
SetRectEmpty(&(pvi->rcSource)); // we want the whole image area rendered.
SetRectEmpty(&(pvi->rcTarget)); // no particular destination rectangle
pmt->SetType(&MEDIATYPE_Video);
pmt->SetFormatType(&FORMAT_VideoInfo);
pmt->SetTemporalCompression(true);
// Work out the GUID for the subtype from the header info.
//const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
pmt->SetSubtype(&MEDIASUBTYPE_RGB32);
pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);
return NOERROR;
} // GetMediaType
//
// CheckMediaType
//
// We will accept 8, 16, 24 or 32 bit video formats, in any
// image size that gives room to bounce.
// Returns E_INVALIDARG if the mediatype is not acceptable
//
HRESULT FCapturePin::CheckMediaType(const CMediaType *pMediaType)
{
CheckPointer(pMediaType,E_POINTER);
if((*(pMediaType->Type()) != MEDIATYPE_Video) || // we only output video
!(pMediaType->IsFixedSize())) // in fixed size samples
{
return E_INVALIDARG;
}
// Check for the subtypes we support
const GUID *SubType = pMediaType->Subtype();
if (SubType == NULL)
{
return E_INVALIDARG;
}
if (*SubType != MEDIASUBTYPE_RGB32)
{
return E_INVALIDARG;
}
// Get the format area of the media type
VIDEOINFO *pvi = (VIDEOINFO *) pMediaType->Format();
if(pvi == NULL)
{
return E_INVALIDARG;
}
// Check if the image width & height have changed
if( pvi->bmiHeader.biWidth != ImageWidth ||
abs(pvi->bmiHeader.biHeight) != ImageHeight)
{
// If the image width/height is changed, fail CheckMediaType() to force
// the renderer to resize the image.
return E_INVALIDARG;
}
return S_OK; // This format is acceptable.
} // CheckMediaType
//
// DecideBufferSize
//
// This will always be called after the format has been successfully
// negotiated. So we have a look at m_mt to see what size image we agreed.
// Then we can ask for buffers of the correct size to contain them.
//
HRESULT FCapturePin::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties)
{
CheckPointer(pAlloc,E_POINTER);
CheckPointer(pProperties,E_POINTER);
CAutoLock cAutoLock(m_pFilter->pStateLock());
HRESULT hr = NOERROR;
VIDEOINFO *pvi = (VIDEOINFO *) m_mt.Format();
pProperties->cBuffers = 1;
pProperties->cbBuffer = pvi->bmiHeader.biSizeImage;
check(pProperties->cbBuffer);
// Ask the allocator to reserve us some sample memory. NOTE: the function
// can succeed (return NOERROR) but still not have allocated the
// memory that we requested, so we must check we got whatever we wanted.
ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProperties,&Actual);
if(FAILED(hr))
{
return hr;
}
// Is this allocator unsuitable?
if(Actual.cbBuffer < pProperties->cbBuffer)
{
return E_FAIL;
}
check(Actual.cBuffers == 1);
return NOERROR;
} // DecideBufferSize
//
// SetMediaType
//
// Called when a media type is agreed between filters
//
HRESULT FCapturePin::SetMediaType(const CMediaType *pMediaType)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());
// Pass the call up to my base class
HRESULT hr = CSourceStream::SetMediaType(pMediaType);
if(SUCCEEDED(hr))
{
VIDEOINFO * pvi = (VIDEOINFO *) m_mt.Format();
if (pvi == NULL)
{
return E_UNEXPECTED;
}
if (pvi->bmiHeader.biBitCount == 32)
{
return S_OK;
}
}
// We should never agree any other media types
check(false);
return E_INVALIDARG;
} // SetMediaType
// This is where we insert the DIB bits into the video stream.
// FillBuffer is called once for every sample in the stream.
HRESULT FCapturePin::FillBuffer(IMediaSample *pSample)
{
uint8 *pData;
long cbData;
CheckPointer(pSample, E_POINTER);
CAutoLock cAutoLockShared(&SharedState);
// Access the sample's data buffer
pSample->GetPointer(&pData);
cbData = pSample->GetSize();
// Check that we're still using video
check(m_mt.formattype == FORMAT_VideoInfo);
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)m_mt.pbFormat;
if (pData)
{
uint32 BytesPerRow = sizeof(FColor) * ImageWidth;
// Fill buffer bottom up
for (int32 Row = ImageHeight - 1; Row >= 0; --Row, pData += BytesPerRow)
{
const FColor* Read = &CurrentFrame->FrameData.GetData()[ImageWidth * Row];
FMemory::Memcpy(pData, Read, BytesPerRow);
}
}
// Set the timestamps that will govern playback frame rate.
// The current time is the sample's start.
// Not strictly necessary since AVI is constant framerate
REFERENCE_TIME StartTime = UNITS*CurrentFrame->StartTimeSeconds;
REFERENCE_TIME StopTime = UNITS*CurrentFrame->EndTimeSeconds;
pSample->SetTime(&StartTime, &StopTime);
REFERENCE_TIME StartMediaTime = CurrentFrame->FrameIndex;
REFERENCE_TIME StopMediaTime = CurrentFrame->FrameIndex+1;
pSample->SetMediaTime(&StartMediaTime, &StopMediaTime);
pSample->SetSyncPoint(true);
return S_OK;
}
// the loop executed while running
HRESULT FCapturePin::DoBufferProcessingLoop(void)
{
ON_SCOPE_EXIT
{
static_cast<FCaptureSource*>(m_pFilter)->OnFinishedCapturing();
};
OnThreadStartPlay();
bool bPaused = false, bShutdownRequested = false;
for (;;)
{
Command com;
if (CheckRequest(&com))
{
switch(com)
{
case CMD_RUN:
case CMD_PAUSE:
bPaused = com == CMD_PAUSE;
Reply(NOERROR);
break;
case CMD_STOP:
break;
default:
Reply((uint32) E_UNEXPECTED);
break;
}
}
bShutdownRequested = bShutdownRequested || !static_cast<FCaptureSource*>(m_pFilter)->ShouldCapture();
if (!bPaused)
{
TOptional<HRESULT> Result = ProcessFrames();
if (Result.IsSet())
{
return Result.GetValue();
}
}
if (bShutdownRequested &&
(bPaused || Writer.GetNumOutstandingFrames() == 0))
{
break;
}
}
return S_FALSE;
}
TOptional<HRESULT> FCapturePin::ProcessFrames()
{
uint32 WaitTimeMs = 100;
TArray<FCapturedFrame> PendingFrames = Writer.GetFrameData(WaitTimeMs);
// Capture the frames that we have
for (int32 FrameIndex = 0; FrameIndex < PendingFrames.Num(); ++FrameIndex)
{
CurrentFrame = &PendingFrames[FrameIndex];
IMediaSample *pSample = nullptr;
HRESULT hr;
hr = GetDeliveryBuffer(&pSample,NULL,NULL,0);
if (FAILED(hr))
{
UE_LOG(LogMovieCapture, Warning, TEXT("Failed to get delivery buffer: %08x; Stopping."), hr);
return S_OK;
}
hr = FillBuffer(pSample);
if (FAILED(hr))
{
pSample->Release();
UE_LOG(LogMovieCapture, Warning, TEXT("Failed to fill sample buffer: %08x; Stopping."), hr);
return S_OK;
}
hr = Deliver(pSample);
pSample->Release();
// downstream filter returns S_FALSE if it wants us to
// stop or an error if it's reporting an error.
if(hr != S_OK)
{
UE_LOG(LogMovieCapture, Warning, TEXT("Deliver() returned %08x; Stopping."), hr);
return S_OK;
}
if (CurrentFrame->FrameProcessedEvent)
{
CurrentFrame->FrameProcessedEvent->Trigger();
}
}
return TOptional<HRESULT>();
}
#endif //#if PLATFORM_WINDOWS