#ifdef USE_WMF
VideoCompressor::VideoCompressor()
{
_Writer = NULL;
_Sample = NULL;
_Buffer = NULL;
}
VideoCompressor::~VideoCompressor()
{
FreeMemory();
}
void VideoCompressor::FreeMemory()
{
if(_Sample)
{
_Sample->Release();
_Sample = NULL;
}
if(_Buffer)
{
_Buffer->Release();
_Buffer = NULL;
}
if(_AudioCapture.Capturing())
{
_AudioCapture.StopCapture();
}
if(_Writer)
{
HRESULT hr = _Writer->Finalize();
PersistentAssert(SUCCEEDED(hr), "Finalize failed");
_Writer->Release();
_Writer = NULL;
}
}
enum eAVEncH264VProfile {
eAVEncH264VProfile_unknown = 0,
eAVEncH264VProfile_Simple = 66,
eAVEncH264VProfile_Base = 66,
eAVEncH264VProfile_Main = 77,
eAVEncH264VProfile_High = 100,
eAVEncH264VProfile_422 = 122,
eAVEncH264VProfile_High10 = 110,
eAVEncH264VProfile_444 = 144,
eAVEncH264VProfile_Extended = 88
};
void VideoCompressor::InitMediaType(IMFMediaType *M, const GUID &Format, UINT BitRate, UINT Width, UINT Height, UINT FrameRate)
{
M->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
M->SetGUID(MF_MT_SUBTYPE, Format);
M->SetUINT32(MF_MT_AVG_BITRATE, BitRate);
MFSetAttributeSize(M, MF_MT_FRAME_SIZE, Width, Height);
MFSetAttributeRatio(M, MF_MT_FRAME_RATE, FrameRate, 1);
MFSetAttributeRatio(M, MF_MT_FRAME_RATE_RANGE_MAX, FrameRate, 1);
MFSetAttributeRatio(M, MF_MT_FRAME_RATE_RANGE_MIN, FrameRate / 2, 1);
MFSetAttributeRatio(M, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
M->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
M->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1);
M->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, 1);
M->SetUINT32(MF_MT_SAMPLE_SIZE, Width * Height * 4);
M->SetUINT32(MF_MT_MPEG2_PROFILE, eAVEncH264VProfile_Main);
}
VideoCompressorResult VideoCompressor::OpenFile(const String &Filename, UINT Width, UINT Height, UINT BitRate, UINT FrameRate, UINT AudioDeviceIndex, Clock *Timer)
{
VideoCompressorResult Result = VideoCompressorResultSuccess;
_Width = Width;
_Height = Height;
_CapturingAudio = (AudioDeviceIndex != 0xFFFFFFFF);
_Clock = Timer;
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
hr = MFStartup(MF_VERSION);
PersistentAssert(SUCCEEDED(hr), "MFStartup failed");
hr = MFCreateSinkWriterFromURL(
UnicodeString(Filename).CString(),
NULL,
NULL,
&_Writer
);
PersistentAssert(SUCCEEDED(hr), "MFCreateSinkWriterFromURL failed");
const UINT RawBufferSize = Width * Height * 4;
IMFMediaType *OutputMediaType;
MFCreateMediaType(&OutputMediaType);
InitMediaType(OutputMediaType, MFVideoFormat_H264, BitRate, Width, Height, FrameRate);
IMFMediaType *InputMediaType;
MFCreateMediaType(&InputMediaType);
InitMediaType(InputMediaType, MFVideoFormat_RGB32, RawBufferSize, Width, Height, FrameRate);
DWORD VideoStreamIndex;
hr = _Writer->AddStream(OutputMediaType, &VideoStreamIndex);
PersistentAssert(SUCCEEDED(hr), "AddStream failed");
OutputMediaType->Release();
hr = _Writer->SetInputMediaType(VideoStreamIndex, InputMediaType, NULL);
InputMediaType->Release();
if(FAILED(hr))
{
if(Width > 1920 || Height > 1080)
{
MessageBox(NULL, "The maximum resolution for H.264 video is 1920x1080.", "Invalid Window Dimensions", MB_OK | MB_ICONERROR);
}
else
{
MessageBox(NULL, "There was an error when attempting to initialize video capture. The maximum resolution for H.264 video is 1920x1080.", "Invalid Window Dimensions", MB_OK | MB_ICONERROR);
}
_Writer->Release();
_Writer = NULL;
_Clock = NULL;
return VideoCompressorResultFailure;
}
if(_CapturingAudio)
{
IMFMediaType *OutputAudioType;
hr = MFCreateMediaType( &OutputAudioType );
PersistentAssert(SUCCEEDED(hr), "MFCreateMediaType failed");
const UINT SamplesPerSecond = 44100;
const UINT AverageBytesPerSecond = 24000;
const UINT ChannelCount = 2;
const UINT BitsPerSample = 16;
OutputAudioType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ) ;
OutputAudioType->SetGUID( MF_MT_SUBTYPE, MFAudioFormat_AAC ) ;
OutputAudioType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, SamplesPerSecond ) ;
OutputAudioType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, BitsPerSample ) ;
OutputAudioType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, ChannelCount ) ;
OutputAudioType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, AverageBytesPerSecond ) ;
OutputAudioType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, 1 ) ;
DWORD AudioStreamIndex;
hr = _Writer->AddStream( OutputAudioType, &AudioStreamIndex );
PersistentAssert(SUCCEEDED(hr), "AddStream failed");
IMFMediaType *InputAudioType;
MFCreateMediaType( &InputAudioType );
InputAudioType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio );
InputAudioType->SetGUID( MF_MT_SUBTYPE, MFAudioFormat_PCM );
InputAudioType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, BitsPerSample );
InputAudioType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, SamplesPerSecond );
InputAudioType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, ChannelCount );
hr = _Writer->SetInputMediaType( AudioStreamIndex, InputAudioType, NULL );
PersistentAssert(SUCCEEDED(hr), "SetInputMediaType failed");
_AudioCapture.StartCapture(this, AudioDeviceIndex);
}
hr = _Writer->BeginWriting();
PersistentAssert(SUCCEEDED(hr), "BeginWriting failed");
hr = MFCreateSample(&_Sample);
PersistentAssert(SUCCEEDED(hr), "MFCreateSample failed");
hr = MFCreateMemoryBuffer(RawBufferSize, &_Buffer);
_Buffer->SetCurrentLength(RawBufferSize);
_Sample->AddBuffer(_Buffer);
return Result;
}
#ifdef D3D9_IN_NAMESPACE
void VideoCompressor::AddFrame(D3D9Base::LPDIRECT3DSURFACE9 Surface, const Vec2i &Start, double TimeInSeconds)
#else
void VideoCompressor::AddFrame(LPDIRECT3DSURFACE9 Surface, const Vec2i &Start, double TimeInSeconds)
#endif
{
if(_Clock != NULL)
{
TimeInSeconds = _Clock->Elapsed();
}
_Sample->SetSampleTime( LONGLONG( TimeInSeconds * 10000000.0 ) );
BYTE *BufferData;
HRESULT hr = _Buffer->Lock(&BufferData, NULL, NULL);
PersistentAssert(SUCCEEDED(hr), "_Buffer->Lock failed");
D3DSURFACE_DESC Desc;
D3DLOCKED_RECT LockedRect;
D3DAlwaysValidate(Surface->GetDesc(&Desc), "GetDesc");
PersistentAssert(Desc.Format == D3DFMT_A8R8G8B8 ||
Desc.Format == D3DFMT_X8R8G8B8, "Invalid surface format");
D3DAlwaysValidate(Surface->LockRect(&LockedRect, NULL, 0), "LockRect");
const UINT VideoWidth = _Width;
const UINT VideoHeight = _Height;
const UINT BufferPitch = sizeof(RGBColor) * VideoWidth;
Vec2i CorrectedStart = Start;
if(CorrectedStart.x < 0)
{
CorrectedStart.x = 0;
}
if(CorrectedStart.x + VideoWidth > Desc.Width)
{
CorrectedStart.x = Desc.Width - VideoWidth;
}
if(CorrectedStart.y < 0)
{
CorrectedStart.y = 0;
}
if(CorrectedStart.y + VideoHeight > Desc.Height)
{
CorrectedStart.y = Desc.Height - VideoHeight;
}
bool SizingCorrect = (CorrectedStart.x >= 0 && CorrectedStart.x + VideoWidth <= Desc.Width ) &&
(CorrectedStart.y >= 0 && CorrectedStart.y + VideoHeight <= Desc.Height);
if(!SizingCorrect)
{
for(UINT y = 0; y < VideoHeight; y++)
{
memset(BufferData + y * BufferPitch, 0, BufferPitch);
}
}
else if(Desc.Format == D3DFMT_A8R8G8B8 || Desc.Format == D3DFMT_X8R8G8B8)
{
for(UINT y = 0; y < VideoHeight; y++)
{
RGBColor *RowStart = (RGBColor *)((BYTE*)LockedRect.pBits + (y + CorrectedStart.y) * LockedRect.Pitch);
memcpy(BufferData + (VideoHeight - 1 - y) * BufferPitch, RowStart + CorrectedStart.x, BufferPitch);
}
}
D3DAlwaysValidate(Surface->UnlockRect(), "UnlockRect");
_Buffer->Unlock();
hr = _Writer->WriteSample(0, _Sample);
PersistentAssert(SUCCEEDED(hr), "WriteSample failed");
}
void VideoCompressor::AddFrame(const Bitmap &Bmp, double TimeInSeconds)
{
if(_Clock != NULL)
{
TimeInSeconds = _Clock->Elapsed();
}
_Sample->SetSampleTime( LONGLONG( TimeInSeconds * 10000000.0 ) );
BYTE *BufferData;
HRESULT hr = _Buffer->Lock(&BufferData, NULL, NULL);
PersistentAssert(SUCCEEDED(hr), "_Buffer->Lock failed");
memcpy( BufferData, &(Bmp[0][0]), Bmp.Width() * Bmp.Height() * sizeof(RGBColor) );
_Buffer->Unlock();
hr = _Writer->WriteSample(0, _Sample);
PersistentAssert(SUCCEEDED(hr), "WriteSample failed");
}
void VideoCompressor::AudioSample32Bit2Channel(float *Samples, UINT FrameCount, UINT64 CaptureStartTime)
{
const UINT SamplesPerSecond = 44100;
const UINT ChannelCount = 2;
const UINT SampleCount = FrameCount * ChannelCount;
const UINT BitsPerSample = 16;
const UINT BufferLength = BitsPerSample / 8 * ChannelCount * FrameCount;
const LONGLONG SampleDuration = LONGLONG(FrameCount) * LONGLONG(10000000) / SamplesPerSecond;
IMFSample *spSample;
IMFMediaBuffer *spBuffer;
BYTE *pbBuffer = NULL;
HRESULT hr = MFCreateSample( &spSample );
hr = spSample->SetSampleDuration( SampleDuration );
LONGLONG FileStartCounter = _Clock->StartTime();
LONGLONG CaptureStartCounter = CaptureStartTime * _Clock->TicksPerSecond() / LONGLONG(10000000);
hr = spSample->SetSampleTime( ( CaptureStartCounter - FileStartCounter ) * LONGLONG(10000000) / _Clock->TicksPerSecond() );
hr = MFCreateMemoryBuffer( BufferLength, &spBuffer );
hr = spBuffer->SetCurrentLength( BufferLength );
hr = spSample->AddBuffer( spBuffer );
hr = spBuffer->Lock( &pbBuffer, NULL, NULL );
__int16 *OutputAudioBuffer = (__int16 *)pbBuffer;
for(UINT SampleIndex = 0; SampleIndex < SampleCount; SampleIndex++)
{
OutputAudioBuffer[SampleIndex] = int(Samples[SampleIndex] * 32768.0f);
}
hr = spBuffer->Unlock();
hr = _Writer->WriteSample( 1, spSample );
PersistentAssert(SUCCEEDED(hr), "WriteSample failed");
spSample->Release();
spBuffer->Release();
}
#endif