mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1325 lines
46 KiB
C++
1325 lines
46 KiB
C++
/**
|
|
* DSVideoCaptureEngine.cpp
|
|
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "DSVideoCaptureEngine.h"
|
|
|
|
// 3rd Party Headers
|
|
#undef INTSAFE_E_ARITHMETIC_OVERFLOW
|
|
#include "streams.h"
|
|
|
|
// SystemHeaders
|
|
#include <initguid.h>
|
|
#include <WMSysPrf.h>
|
|
#include <wmsdk.h>
|
|
#include <Dshowasf.h>
|
|
#include <control.h>
|
|
|
|
// 3rd Party Headers
|
|
#include "fmod.h"
|
|
#include "fmod.hpp"
|
|
|
|
// Roblox Headers
|
|
#include "rbx/Debug.h"
|
|
#include "util/FileSystem.h"
|
|
#include "util/Statistics.h"
|
|
#include "GfxCore/Device.h"
|
|
#include "GfxCore/Framebuffer.h"
|
|
#include "V8DataModel/GuiService.h"
|
|
|
|
const char VidCapID[] = "VideoCapture";
|
|
|
|
#define ERROR_ON_FAIL(expr)\
|
|
hr = expr;\
|
|
if (FAILED(hr))\
|
|
{\
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Call failed 0x%x", hr);\
|
|
goto Error;\
|
|
}\
|
|
|
|
#ifndef SAFE_RELEASE
|
|
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
|
|
#endif
|
|
|
|
using namespace RBX;
|
|
|
|
LOGGROUP(VideoCapture)
|
|
LOGGROUP(DeviceLost)
|
|
|
|
namespace RBX {
|
|
|
|
namespace DS {
|
|
// {172325B6-8DA1-4DF0-A5B4-428E382E2968}
|
|
DEFINE_GUID(CLSID_GameVideoStream,
|
|
0x172325b6, 0x8da1, 0x4df0, 0xa5, 0xb4, 0x42, 0x8e, 0x38, 0x2e, 0x29, 0x68);
|
|
|
|
// {C75FC93F-0FD9-4fea-895D-5F5BFB59F621}
|
|
DEFINE_GUID(CLSID_GameAudioStream,
|
|
0xc75fc93f, 0xfd9, 0x4fea, 0x89, 0x5d, 0x5f, 0x5b, 0xfb, 0x59, 0xf6, 0x21);
|
|
|
|
const LPWSTR RbxVideoId = L"RBXVideo";
|
|
const LPWSTR VideoFileExt = L"wmv";
|
|
const LPWSTR ProcTitle = L"watrbxapp";
|
|
const LPWSTR RbxAudioId = L"RBXAudio";
|
|
const int MaxWaitTime = 33; //2fps is very bad
|
|
const int MaxFramesQueue = 10; // no more than 5 frames kept (will prevent out of memory on slow machines)
|
|
const int DefSampleRate = 48000;
|
|
|
|
class CVideoStream;
|
|
|
|
class CVideoStreamFilter : public CSource
|
|
{
|
|
public:
|
|
CVideoStreamFilter(LPUNKNOWN lpunk, HRESULT* phr, int cx, int cy);
|
|
|
|
IPin* GetPin() const { return m_paStreams[0]; }
|
|
void pushNextFrame(BYTE* data, int size, int cx, int cy, int bpp);
|
|
int GetTargetFrameRate() const;
|
|
int GetActualFrameRate() const;
|
|
void SetAudioClock(IAudioTime* clock);
|
|
void GetCaptureSize(int& width, int& height);
|
|
int GetQueueSize();
|
|
private:
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Class CVideoStream
|
|
//
|
|
// This class implements the stream which is used to output the images from game into encoding pipeline
|
|
//------------------------------------------------------------------------------
|
|
class CVideoStream : public CSourceStream
|
|
{
|
|
|
|
public:
|
|
CVideoStream(HRESULT* phr, CVideoStreamFilter* pParent, LPCWSTR pPinName, int cx, int cy);
|
|
~CVideoStream();
|
|
|
|
HRESULT FillBuffer(IMediaSample* pms);
|
|
HRESULT DecideBufferSize(IMemAllocator* pIMemAlloc, ALLOCATOR_PROPERTIES* pProperties);
|
|
HRESULT SetMediaType(const CMediaType* pMediaType);
|
|
HRESULT CheckMediaType(const CMediaType* pMediaType);
|
|
HRESULT GetMediaType(int iPosition, CMediaType* pmt);
|
|
HRESULT OnThreadCreate(void);
|
|
|
|
STDMETHODIMP Notify(IBaseFilter* pSender, Quality q);
|
|
|
|
void pushNextFrame(BYTE* data, int size, int cx, int cy, int bpp);
|
|
int GetTargetFrameRate() const { return targetFrameRateTime; }
|
|
int GetActualFrameRate() const { return frameRate; };
|
|
void SetAudioClock(IAudioTime* clock);
|
|
void GetCaptureSize(int& width, int& height);
|
|
int GetQueueSize();
|
|
private:
|
|
CCritSec framesQueueLock;
|
|
CAMEvent frameReadyEvent;
|
|
|
|
std::queue<std::pair<BYTE*, int>> framesQueue;
|
|
IAudioTime* audioTime;
|
|
|
|
int height;
|
|
int width;
|
|
const int targetFrameRateTime;
|
|
int frameRate;
|
|
|
|
int pushedFrames;
|
|
|
|
CCritSec m_cSharedState; // Lock on m_rtSampleTime and m_Ball
|
|
CRefTime m_rtSampleTime; // The time stamp for each sample
|
|
};
|
|
|
|
|
|
class CAudioStream;
|
|
|
|
class CAudioStreamFilter : public CSource
|
|
{
|
|
public:
|
|
// It is only allowed to to create these objects with CreateInstance
|
|
CAudioStreamFilter(LPUNKNOWN lpunk, HRESULT* phr, SoundState* s, DS::IAudioTime* t);
|
|
|
|
IPin* GetPin() const { return m_paStreams[0]; }
|
|
int GetExpectedFramesCount(int frameDelay) const;
|
|
IAudioTime* GetAudioTime();
|
|
void StopSoundCapture();
|
|
private:
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Class CAudioStream
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
class CAudioStream : public CSourceStream, public IAudioTime
|
|
{
|
|
|
|
public:
|
|
CAudioStream(HRESULT* phr, CAudioStreamFilter* pParent, LPCWSTR pPinName, SoundState* s, DS::IAudioTime* t);
|
|
~CAudioStream();
|
|
|
|
HRESULT FillBuffer(IMediaSample* pms);
|
|
HRESULT DecideBufferSize(IMemAllocator* pIMemAlloc, ALLOCATOR_PROPERTIES* pProperties);
|
|
HRESULT SetMediaType(const CMediaType* pMediaType);
|
|
HRESULT CheckMediaType(const CMediaType* pMediaType);
|
|
HRESULT GetMediaType(int iPosition, CMediaType* pmt);
|
|
HRESULT OnThreadCreate(void);
|
|
STDMETHODIMP Notify(IBaseFilter* pSender, Quality q);
|
|
int GetExpectedFramesCount(int frameDelay) const;
|
|
virtual LONG GetTime() { return m_rtSampleTime.Millisecs(); }
|
|
virtual LONG GetAbsoluteTime();
|
|
void StopSoundCapture();
|
|
private:
|
|
static FMOD_RESULT F_CALLBACK audioGrabber(FMOD_DSP_STATE* dsp_state, float* inbuffer, float* outbuffer, unsigned int length, int inchannels, int* outchannels);
|
|
|
|
CCritSec framesQueueLock;
|
|
CAMEvent frameReadyEvent;
|
|
|
|
std::queue<std::pair<short*, int>> framesQueue;
|
|
|
|
CCritSec m_cSharedState; // Lock on m_rtSampleTime and m_Ball
|
|
CRefTime m_rtSampleTime; // The time stamp for each sample
|
|
SoundState* soundState;
|
|
bool firstSampleDelivered;
|
|
LONGLONG sampleMediaTimeStart;
|
|
FMOD_DSP_DESCRIPTION dspdesc;
|
|
FMOD::DSP* dsp;
|
|
DS::IAudioTime* time;
|
|
};
|
|
|
|
CVideoStreamFilter::CVideoStreamFilter(LPUNKNOWN lpunk, HRESULT* phr, int cx, int cy) :
|
|
CSource(NAME("GameVideoStream"), lpunk, CLSID_GameVideoStream)
|
|
{
|
|
ASSERT(phr);
|
|
CAutoLock cAutoLock(&m_cStateLock);
|
|
|
|
m_paStreams = (CSourceStream**) new CVideoStream * [1];
|
|
if (m_paStreams == NULL)
|
|
{
|
|
if (phr)
|
|
*phr = E_OUTOFMEMORY;
|
|
|
|
return;
|
|
}
|
|
|
|
m_paStreams[0] = new CVideoStream(phr, this, RbxVideoId, cx, cy);
|
|
if (m_paStreams[0] == NULL)
|
|
{
|
|
if (phr)
|
|
*phr = E_OUTOFMEMORY;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CVideoStreamFilter::pushNextFrame(BYTE* data, int size, int cx, int cy, int bpp)
|
|
{
|
|
(reinterpret_cast<CVideoStream*>(m_paStreams[0]))->pushNextFrame(data, size, cx, cy, bpp);
|
|
}
|
|
|
|
int CVideoStreamFilter::GetTargetFrameRate() const
|
|
{
|
|
return (reinterpret_cast<const CVideoStream*>(m_paStreams[0]))->GetTargetFrameRate();
|
|
}
|
|
|
|
int CVideoStreamFilter::GetActualFrameRate() const
|
|
{
|
|
return (reinterpret_cast<const CVideoStream*>(m_paStreams[0]))->GetActualFrameRate();
|
|
}
|
|
|
|
void CVideoStreamFilter::SetAudioClock(IAudioTime* clock)
|
|
{
|
|
return (reinterpret_cast<CVideoStream*>(m_paStreams[0]))->SetAudioClock(clock);
|
|
}
|
|
|
|
void CVideoStreamFilter::GetCaptureSize(int& width, int& height)
|
|
{
|
|
(reinterpret_cast<CVideoStream*>(m_paStreams[0]))->GetCaptureSize(width, height);
|
|
}
|
|
|
|
int CVideoStreamFilter::GetQueueSize()
|
|
{
|
|
return (reinterpret_cast<CVideoStream*>(m_paStreams[0]))->GetQueueSize();
|
|
}
|
|
|
|
CVideoStream::CVideoStream(HRESULT* phr, CVideoStreamFilter* pParent, LPCWSTR pPinName, int cx, int cy) :
|
|
CSourceStream(NAME("GameVideoStream"), phr, pParent, pPinName),
|
|
width(cx),
|
|
height(cy),
|
|
targetFrameRateTime(33),
|
|
frameRate(0),
|
|
pushedFrames(0)
|
|
{
|
|
ASSERT(phr);
|
|
CAutoLock cAutoLock(&m_cSharedState);
|
|
}
|
|
|
|
CVideoStream::~CVideoStream()
|
|
{
|
|
CAutoLock cAutoLock(&m_cSharedState);
|
|
CAutoLock lock(&framesQueueLock);
|
|
while (framesQueue.size())
|
|
{
|
|
std::pair<BYTE*, int> frameData = framesQueue.front();
|
|
delete[] frameData.first;
|
|
framesQueue.pop();
|
|
}
|
|
|
|
frameReadyEvent.Set();
|
|
}
|
|
|
|
void CVideoStream::SetAudioClock(IAudioTime* clock)
|
|
{
|
|
audioTime = clock;
|
|
}
|
|
|
|
void CVideoStream::pushNextFrame(BYTE* data, int size, int cx, int cy, int bpp)
|
|
{
|
|
CAutoLock lock(&framesQueueLock);
|
|
|
|
framesQueue.push(std::pair<BYTE*, int>(data, size));
|
|
frameReadyEvent.Set();
|
|
}
|
|
|
|
int CVideoStream::GetQueueSize()
|
|
{
|
|
CAutoLock lock(&framesQueueLock);
|
|
|
|
return framesQueue.size();
|
|
}
|
|
|
|
void CVideoStream::GetCaptureSize(int& width, int& height)
|
|
{
|
|
width = this->width;
|
|
height = this->height;
|
|
}
|
|
|
|
HRESULT CVideoStream::FillBuffer(IMediaSample* pms)
|
|
{
|
|
CheckPointer(pms, E_POINTER);
|
|
|
|
BYTE* pData;
|
|
long lDataLen;
|
|
|
|
pms->GetPointer(&pData);
|
|
lDataLen = pms->GetSize();
|
|
BYTE* frame = NULL;
|
|
{
|
|
CAutoLock cAutoLockShared(&m_cSharedState);
|
|
|
|
framesQueueLock.Lock();
|
|
|
|
if (framesQueue.size() == 0) {
|
|
framesQueueLock.Unlock();
|
|
frameReadyEvent.Wait(MaxWaitTime);
|
|
framesQueueLock.Lock();
|
|
}
|
|
frameReadyEvent.Reset();
|
|
|
|
if (!framesQueue.empty())
|
|
{
|
|
std::pair<BYTE*, int> frameData = framesQueue.front();
|
|
|
|
pushedFrames++;
|
|
frameRate = audioTime->GetAbsoluteTime() / pushedFrames;
|
|
|
|
RBXASSERT(lDataLen == frameData.second);
|
|
frame = frameData.first;
|
|
|
|
RBXASSERT(frame);
|
|
memcpy(pData, frame, lDataLen);
|
|
|
|
delete[] frame;
|
|
framesQueue.pop();
|
|
}
|
|
|
|
framesQueueLock.Unlock();
|
|
|
|
// The current time is the sample's start
|
|
CRefTime rtStart = m_rtSampleTime;
|
|
|
|
// Increment to find the finish time
|
|
m_rtSampleTime += (LONG)frameRate;
|
|
|
|
pms->SetTime((REFERENCE_TIME*)&rtStart, (REFERENCE_TIME*)&m_rtSampleTime);
|
|
}
|
|
|
|
pms->SetSyncPoint(TRUE);
|
|
return NOERROR;
|
|
}
|
|
|
|
//
|
|
// Notify
|
|
//
|
|
// Alter the repeat rate according to quality management messages sent from
|
|
// the downstream filter (often the renderer). Wind it up or down according
|
|
// to the flooding level - also skip forward if we are notified of Late-ness
|
|
//
|
|
STDMETHODIMP CVideoStream::Notify(IBaseFilter* pSender, Quality q)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CVideoStream::GetMediaType(int iPosition, CMediaType* pmt)
|
|
{
|
|
CheckPointer(pmt, E_POINTER);
|
|
|
|
CAutoLock cAutoLock(m_pFilter->pStateLock());
|
|
if (iPosition < 0) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (iPosition > 0) {
|
|
return VFW_S_NO_MORE_ITEMS;
|
|
}
|
|
|
|
VIDEOINFO* pvi = (VIDEOINFO*)pmt->AllocFormatBuffer(sizeof(VIDEOINFO));
|
|
if (NULL == pvi)
|
|
return(E_OUTOFMEMORY);
|
|
|
|
ZeroMemory(pvi, sizeof(VIDEOINFO));
|
|
|
|
switch (iPosition)
|
|
{
|
|
case 0:
|
|
{
|
|
pvi->bmiHeader.biCompression = BI_RGB;
|
|
pvi->bmiHeader.biBitCount = 32;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// (Adjust the parameters common to all formats...)
|
|
pvi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
pvi->bmiHeader.biWidth = width;
|
|
pvi->bmiHeader.biHeight = height;
|
|
pvi->bmiHeader.biPlanes = 1;
|
|
pvi->bmiHeader.biSizeImage = GetBitmapSize(&pvi->bmiHeader);
|
|
pvi->bmiHeader.biClrImportant = 0;
|
|
|
|
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(FALSE);
|
|
|
|
// Work out the GUID for the subtype from the header info.
|
|
const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
|
|
pmt->SetSubtype(&SubTypeGUID);
|
|
pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT CVideoStream::CheckMediaType(const CMediaType* pMediaType)
|
|
{
|
|
CheckPointer(pMediaType, E_POINTER);
|
|
|
|
if ((*(pMediaType->Type()) != MEDIATYPE_Video) || !(pMediaType->IsFixedSize())) {
|
|
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 the image size. As my default ball is 10 pixels big
|
|
// look for at least a 20x20 image. This is an arbitary size constraint,
|
|
// but it avoids balls that are bigger than the picture...
|
|
|
|
if ((pvi->bmiHeader.biWidth < 20) || (abs(pvi->bmiHeader.biHeight) < 20)) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// Check if the image width & height have changed
|
|
if (pvi->bmiHeader.biWidth != width || abs(pvi->bmiHeader.biHeight) != height) {
|
|
// 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.
|
|
}
|
|
|
|
|
|
HRESULT CVideoStream::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;
|
|
|
|
ASSERT(pProperties->cbBuffer);
|
|
|
|
// Ask the allocator to reserve us some sample memory, NOTE the function
|
|
// can succeed (that is 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;
|
|
}
|
|
|
|
if (Actual.cbBuffer < pProperties->cbBuffer)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Make sure that we have only 1 buffer (we erase the ball in the
|
|
// old buffer to save having to zero a 200k+ buffer every time
|
|
// we draw a frame)
|
|
|
|
ASSERT(Actual.cBuffers == 1);
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CVideoStream::SetMediaType(const CMediaType* pMediaType)
|
|
{
|
|
CAutoLock cAutoLock(m_pFilter->pStateLock());
|
|
|
|
HRESULT hr = CSourceStream::SetMediaType(pMediaType);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
VIDEOINFO* pvi = (VIDEOINFO*)m_mt.Format();
|
|
if (pvi == NULL)
|
|
return E_UNEXPECTED;
|
|
|
|
switch (pvi->bmiHeader.biBitCount) {
|
|
case 32:
|
|
break;
|
|
|
|
default:
|
|
// We should never agree any other pixel sizes
|
|
RBXASSERT(0);
|
|
break;
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// OnThreadCreate
|
|
//
|
|
// As we go active reset the stream time to zero
|
|
//
|
|
HRESULT CVideoStream::OnThreadCreate()
|
|
{
|
|
CAutoLock cAutoLockShared(&m_cSharedState);
|
|
m_rtSampleTime = 0;
|
|
|
|
return NOERROR;
|
|
} // OnThreadCreate
|
|
|
|
CAudioStreamFilter::CAudioStreamFilter(LPUNKNOWN lpunk, HRESULT* phr, SoundState* s, DS::IAudioTime* t) :
|
|
CSource(NAME("GameAudioStream"), lpunk, CLSID_GameAudioStream)
|
|
{
|
|
ASSERT(phr);
|
|
CAutoLock cAutoLock(&m_cStateLock);
|
|
|
|
m_paStreams = (CSourceStream**) new CAudioStream * [1];
|
|
if (m_paStreams == NULL)
|
|
{
|
|
if (phr)
|
|
*phr = E_OUTOFMEMORY;
|
|
|
|
return;
|
|
}
|
|
|
|
m_paStreams[0] = new CAudioStream(phr, this, RbxAudioId, s, t);
|
|
if (m_paStreams[0] == NULL)
|
|
{
|
|
if (phr)
|
|
*phr = E_OUTOFMEMORY;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
int CAudioStreamFilter::GetExpectedFramesCount(int frameDelay) const
|
|
{
|
|
return (reinterpret_cast<const CAudioStream*>(m_paStreams[0]))->GetExpectedFramesCount(frameDelay);
|
|
};
|
|
|
|
IAudioTime* CAudioStreamFilter::GetAudioTime()
|
|
{
|
|
return (reinterpret_cast<CAudioStream*>(m_paStreams[0]));
|
|
}
|
|
|
|
void CAudioStreamFilter::StopSoundCapture()
|
|
{
|
|
return (reinterpret_cast<CAudioStream*>(m_paStreams[0]))->StopSoundCapture();
|
|
}
|
|
|
|
CAudioStream::CAudioStream(HRESULT* phr, CAudioStreamFilter* pParent, LPCWSTR pPinName, SoundState* s, DS::IAudioTime* t) :
|
|
CSourceStream(NAME("GameVideoStream"), phr, pParent, pPinName),
|
|
soundState(s),
|
|
time(t),
|
|
firstSampleDelivered(false),
|
|
sampleMediaTimeStart(0)
|
|
{
|
|
FMOD_DSP_DESCRIPTION dspdesc;
|
|
memset(&dspdesc, 0, sizeof(FMOD_DSP_DESCRIPTION));
|
|
|
|
strncpy_s(dspdesc.name, sizeof(dspdesc.name) / sizeof(dspdesc.name[0]), "SCRBX", sizeof(dspdesc.name) / sizeof(dspdesc.name[0]));
|
|
//dspdesc.channels = 0; // 0 = whatever comes in, else specify, no longer a property in FMOD STUDIO
|
|
dspdesc.read = audioGrabber;
|
|
dspdesc.userdata = (void*)this;
|
|
|
|
dsp = s->createDSPFunction(dspdesc);
|
|
|
|
if (!dsp)
|
|
{
|
|
*phr = E_FAIL;
|
|
}
|
|
}
|
|
|
|
CAudioStream::~CAudioStream()
|
|
{
|
|
if (dsp)
|
|
{
|
|
dsp->setBypass(true);
|
|
dsp->release();
|
|
}
|
|
|
|
CAutoLock lock(&framesQueueLock);
|
|
while (framesQueue.size())
|
|
{
|
|
std::pair<short*, int> frameInfo = framesQueue.front();
|
|
delete[] frameInfo.first;
|
|
framesQueue.pop();
|
|
}
|
|
|
|
frameReadyEvent.Set();
|
|
}
|
|
|
|
LONG CAudioStream::GetAbsoluteTime()
|
|
{
|
|
return time->GetAbsoluteTime();
|
|
}
|
|
|
|
int CAudioStream::GetExpectedFramesCount(int frameDelay) const
|
|
{
|
|
return (int)(const_cast<CRefTime&>(m_rtSampleTime).Millisecs() / frameDelay);
|
|
}
|
|
|
|
void CAudioStream::StopSoundCapture()
|
|
{
|
|
if (dsp)
|
|
{
|
|
dsp->setBypass(true);
|
|
dsp->release();
|
|
dsp = NULL;
|
|
}
|
|
frameReadyEvent.Set();
|
|
}
|
|
|
|
FMOD_RESULT F_CALLBACK CAudioStream::audioGrabber(FMOD_DSP_STATE* dsp_state, float* inbuffer, float* outbuffer, unsigned int length, int inchannels, int* outchannels)
|
|
{
|
|
FMOD::DSP* dsp = (FMOD::DSP*)dsp_state->instance;
|
|
void* userdata = NULL;
|
|
dsp->getUserData((void**)&userdata);
|
|
CAudioStream* stream = (CAudioStream*)userdata;
|
|
const unsigned short videoChannelsCount = 2;
|
|
short* soundData = new short[videoChannelsCount * length];
|
|
RBXASSERT(inchannels == *outchannels);
|
|
|
|
for (unsigned int count = 0; count < length; count++)
|
|
{
|
|
for (int count2 = 0; count2 < *outchannels; count2++)
|
|
{
|
|
outbuffer[(count * (*outchannels)) + count2] = inbuffer[(count * inchannels) + count2];
|
|
|
|
if (count2 < videoChannelsCount)
|
|
{
|
|
float inFloat = inbuffer[(count * inchannels) + count2];
|
|
inFloat *= 32768;
|
|
int outInt = inFloat < -32768 ? -32768 : inFloat > 32767 ? 32767 : (signed short)inFloat;
|
|
|
|
short s = (short)(outInt);
|
|
soundData[(count * videoChannelsCount) + count2] = s;
|
|
}
|
|
}
|
|
}
|
|
|
|
CAutoLock lock(&stream->framesQueueLock);
|
|
|
|
stream->framesQueue.push(std::pair<short*, int>(soundData, videoChannelsCount * length));
|
|
stream->frameReadyEvent.Set();
|
|
|
|
return FMOD_OK;
|
|
}
|
|
|
|
|
|
HRESULT CAudioStream::FillBuffer(IMediaSample* pms)
|
|
{
|
|
CheckPointer(pms, E_POINTER);
|
|
|
|
BYTE* pData;
|
|
|
|
HRESULT hr = pms->GetPointer(&pData);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
// This lock must be held because this function uses
|
|
// m_dwTempPCMBufferSize, m_hPCMToMSADPCMConversionStream,
|
|
// m_rtSampleTime, m_fFirstSampleDelivered and
|
|
// m_llSampleMediaTimeStart.
|
|
CAutoLock lShared(&m_cSharedState);
|
|
|
|
WAVEFORMATEX* pwfexCurrent = (WAVEFORMATEX*)m_mt.Format();
|
|
|
|
RBXASSERT(pwfexCurrent->wBitsPerSample == 16 && pwfexCurrent->nChannels == 2);
|
|
|
|
short* sound = NULL;
|
|
int actualLen = 0;
|
|
framesQueueLock.Lock();
|
|
|
|
if (framesQueue.size() == 0) {
|
|
framesQueueLock.Unlock();
|
|
frameReadyEvent.Wait(MaxWaitTime);
|
|
framesQueueLock.Lock();
|
|
}
|
|
frameReadyEvent.Reset();
|
|
|
|
if (!framesQueue.empty())
|
|
{
|
|
std::pair<short*, int> frameInfo = framesQueue.front();
|
|
sound = frameInfo.first;
|
|
actualLen = frameInfo.second * sizeof(short);
|
|
framesQueue.pop();
|
|
|
|
RBXASSERT(sound);
|
|
memcpy(pData, sound, actualLen);
|
|
delete[] sound;
|
|
}
|
|
|
|
framesQueueLock.Unlock();
|
|
|
|
hr = pms->SetActualDataLength(actualLen);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
// Set the sample's time stamps.
|
|
CRefTime rtStart = m_rtSampleTime;
|
|
|
|
m_rtSampleTime = rtStart + (REFERENCE_TIME)(UNITS * pms->GetActualDataLength()) /
|
|
(REFERENCE_TIME)pwfexCurrent->nAvgBytesPerSec;
|
|
|
|
hr = pms->SetTime((REFERENCE_TIME*)&rtStart, (REFERENCE_TIME*)&m_rtSampleTime);
|
|
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Set the sample's properties.
|
|
hr = pms->SetPreroll(FALSE);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = pms->SetMediaType(NULL);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = pms->SetDiscontinuity(!firstSampleDelivered);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
hr = pms->SetSyncPoint(!firstSampleDelivered);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
LONGLONG llMediaTimeStart = sampleMediaTimeStart;
|
|
|
|
DWORD dwNumAudioSamplesInPacket = (pms->GetActualDataLength() * 8) / (pwfexCurrent->nChannels * pwfexCurrent->wBitsPerSample);
|
|
|
|
LONGLONG llMediaTimeStop = sampleMediaTimeStart + dwNumAudioSamplesInPacket;
|
|
|
|
hr = pms->SetMediaTime(&llMediaTimeStart, &llMediaTimeStop);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
sampleMediaTimeStart = llMediaTimeStop;
|
|
firstSampleDelivered = true;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CAudioStream::DecideBufferSize(IMemAllocator* pIMemAlloc, ALLOCATOR_PROPERTIES* pProperties)
|
|
{
|
|
CheckPointer(pIMemAlloc, E_POINTER);
|
|
CheckPointer(pProperties, E_POINTER);
|
|
|
|
WAVEFORMATEX* pwfexCurrent = (WAVEFORMATEX*)m_mt.Format();
|
|
|
|
RBXASSERT(WAVE_FORMAT_PCM == pwfexCurrent->wFormatTag);
|
|
pProperties->cbBuffer = 16 * 1024;
|
|
|
|
int nBitsPerSample = pwfexCurrent->wBitsPerSample;
|
|
int nSamplesPerSec = pwfexCurrent->nSamplesPerSec;
|
|
int nChannels = pwfexCurrent->nChannels;
|
|
|
|
pProperties->cBuffers = (nChannels * nSamplesPerSec * nBitsPerSample) / (pProperties->cbBuffer * 8);
|
|
|
|
// Get 1/2 second worth of buffers
|
|
pProperties->cBuffers /= 2;
|
|
if (pProperties->cBuffers < 1)
|
|
{
|
|
pProperties->cBuffers = 1;
|
|
}
|
|
|
|
// Ask the allocator to reserve us the memory
|
|
|
|
ALLOCATOR_PROPERTIES Actual;
|
|
HRESULT hr = pIMemAlloc->SetProperties(pProperties, &Actual);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
// Is this allocator unsuitable
|
|
if (Actual.cbBuffer < pProperties->cbBuffer)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT CAudioStream::SetMediaType(const CMediaType* pMediaType)
|
|
{
|
|
CAutoLock cAutoLock(m_pFilter->pStateLock());
|
|
|
|
HRESULT hr = CSourceStream::SetMediaType(pMediaType);
|
|
|
|
return CheckMediaType(pMediaType);
|
|
}
|
|
|
|
HRESULT CAudioStream::CheckMediaType(const CMediaType* pMediaType)
|
|
{
|
|
CheckPointer(pMediaType, E_POINTER);
|
|
CAutoLock lock(m_pFilter->pStateLock());
|
|
|
|
CMediaType mt;
|
|
GetMediaType(0, &mt);
|
|
|
|
if (mt == *pMediaType)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
HRESULT CAudioStream::GetMediaType(int iPosition, CMediaType* pmt)
|
|
{
|
|
CheckPointer(pmt, E_POINTER);
|
|
|
|
CAutoLock cAutoLock(m_pFilter->pStateLock());
|
|
if (iPosition < 0) {
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if (iPosition > 0) {
|
|
return VFW_S_NO_MORE_ITEMS;
|
|
}
|
|
|
|
WAVEFORMATEX* pwfex = (WAVEFORMATEX*)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
|
|
if (NULL == pwfex)
|
|
return(E_OUTOFMEMORY);
|
|
|
|
ZeroMemory(pwfex, sizeof(WAVEFORMATEX));
|
|
|
|
pwfex->wFormatTag = WAVE_FORMAT_PCM;
|
|
pwfex->nChannels = 2;
|
|
pwfex->nSamplesPerSec = DefSampleRate;
|
|
pwfex->wBitsPerSample = 16;
|
|
pwfex->nBlockAlign = (WORD)((pwfex->wBitsPerSample * pwfex->nChannels) / 8);
|
|
pwfex->nAvgBytesPerSec = pwfex->nBlockAlign * pwfex->nSamplesPerSec;
|
|
pwfex->cbSize = 0;
|
|
|
|
return CreateAudioMediaType(pwfex, pmt, FALSE);
|
|
}
|
|
|
|
HRESULT CAudioStream::OnThreadCreate(void)
|
|
{
|
|
CAutoLock cAutoLockShared(&m_cSharedState);
|
|
m_rtSampleTime = 0;
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP CAudioStream::Notify(IBaseFilter* pSender, Quality q)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
DSVideoCaptureEngine::DSVideoCaptureEngine() :
|
|
defNx(16.0),
|
|
defNy(9.0),
|
|
MaxRecordTime(14 * 60 * 1000), //14 minutes in milliseconds
|
|
frameData(NULL),
|
|
frameDataSize(0)
|
|
{
|
|
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
switch (hr)
|
|
{
|
|
case S_OK:
|
|
case S_FALSE:
|
|
break;
|
|
default:
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "CoInitializeEx result = 0x%x", hr);
|
|
}
|
|
graph = NULL;
|
|
mediaControl = NULL;
|
|
videoSource = NULL;
|
|
audioSource = NULL;
|
|
soundState = NULL;
|
|
framesPushed = 0;
|
|
}
|
|
|
|
DSVideoCaptureEngine::~DSVideoCaptureEngine() {
|
|
if (isRunning())
|
|
{
|
|
stop();
|
|
}
|
|
CoUninitialize();
|
|
}
|
|
|
|
HRESULT DSVideoCaptureEngine::BuildCaptureGraphNoThrow(int cx, int cy, bool forceNoAudio)
|
|
{
|
|
__try
|
|
{
|
|
return BuildCaptureGraph(cx, cy, false);
|
|
}
|
|
__except (true)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
|
|
bool DSVideoCaptureEngine::start(int cx, int cy, SoundState* s) {
|
|
if (!isRunning())
|
|
{
|
|
soundState = s;
|
|
framesPushed = 0;
|
|
lastPushedVideoFrameTime = 0;
|
|
int ncx, ncy;
|
|
GetSquareSizes(cx, cy, ncx, ncy);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (s->enabledFunction() && s->getSampleRateFunction() == DS::DefSampleRate)
|
|
{
|
|
hr = BuildCaptureGraphNoThrow(ncx, ncy, false);
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
ReportStatisticWithMessage(GetBaseURL(), VidCapID, RBX::format("Audio enabled = %d, Expected frame freq = 48000 actual freq = %d", (int)s->enabledFunction(), s->getSampleRateFunction()));
|
|
|
|
DestroyCaptureGgaph();
|
|
hr = BuildCaptureGraph(ncx, ncy, true);
|
|
}
|
|
|
|
RBXASSERT(graph);
|
|
RBXASSERT(mediaControl);
|
|
|
|
hr = mediaControl->Run();
|
|
if (!SUCCEEDED(hr))
|
|
{
|
|
ReportStatisticWithMessage(GetBaseURL(), VidCapID, RBX::format("Video capture startup failed error = %d", hr));
|
|
return false;
|
|
}
|
|
|
|
frameDataSize = cx * cy * 4;
|
|
frameData = new unsigned char[frameDataSize];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DSVideoCaptureEngine::stop() {
|
|
|
|
bool running = isRunning();
|
|
FASTLOG1(FLog::VideoCapture, "Stopping video capture. IsRunning: %b", running);
|
|
HRESULT hr = S_OK;
|
|
|
|
if (frameData)
|
|
{
|
|
delete[] frameData;
|
|
frameData = NULL;
|
|
frameDataSize = 0;
|
|
}
|
|
|
|
|
|
if (running)
|
|
{
|
|
RBXASSERT(mediaControl);
|
|
if (audioSource)
|
|
{
|
|
audioSource->StopSoundCapture();
|
|
}
|
|
|
|
hr = mediaControl->Stop();
|
|
if (FAILED(hr))
|
|
{
|
|
Sleep(1000);
|
|
hr = mediaControl->Stop();
|
|
RBXASSERT(SUCCEEDED(hr));
|
|
}
|
|
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Video recording stopped");
|
|
}
|
|
DestroyCaptureGgaph();
|
|
return SUCCEEDED(hr);
|
|
}
|
|
|
|
void DSVideoCaptureEngine::setVideoQuality(int vq) {
|
|
}
|
|
|
|
bool DSVideoCaptureEngine::isRunning()
|
|
{
|
|
if (!mediaControl)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OAFilterState state;
|
|
HRESULT hr = mediaControl->GetState(INFINITE, &state);
|
|
RBXASSERT(SUCCEEDED(hr));
|
|
if (FAILED(hr))
|
|
{
|
|
FASTLOG(FLog::Error, "MediaControl GetState failed");
|
|
return false;
|
|
}
|
|
|
|
return (state == State_Running);
|
|
}
|
|
|
|
void DSVideoCaptureEngine::GetSquareSizes(int cx, int cy, int& ncx, int& ncy)
|
|
{
|
|
ncx = cx;
|
|
ncy = cy;
|
|
double dcx = cx;
|
|
double dcy = cy;
|
|
if (dcx / dcy > defNx / defNy)
|
|
{
|
|
ncy = (defNy / defNx) * cx;
|
|
}
|
|
else
|
|
{
|
|
ncx = (defNx / defNy) * cy;
|
|
}
|
|
|
|
// it looks like we need this guy, otherwise graph will not start
|
|
ncx += ncx % 4;
|
|
ncy += ncy % 4;
|
|
}
|
|
|
|
static void doNothingCallbackContinuation() {}
|
|
|
|
static void cancelRecording(DataModel* dm, Verb* cancelAction)
|
|
{
|
|
RBX::GuiService* guiService = RBX::ServiceProvider::find<GuiService>(dm);
|
|
|
|
if (guiService && !guiService->notificationCallback.empty())
|
|
{
|
|
guiService->notificationCallback("Recording Stopped", "Because game window resolution changed", boost::bind(&doNothingCallbackContinuation), boost::bind(&doNothingCallbackContinuation));
|
|
}
|
|
|
|
// it supposed to be safe to pass NULL in this case
|
|
cancelAction->doIt(NULL);
|
|
}
|
|
|
|
void DSVideoCaptureEngine::pushNextFrame(void* device, Verb* cancelAction)
|
|
{
|
|
FASTLOG(FLog::VideoCapture, "Pushing next frame.");
|
|
RBXASSERT(isRunning());
|
|
RBXASSERT(videoSource);
|
|
RBXASSERT(cancelAction);
|
|
HRESULT hr = S_OK;
|
|
|
|
LONG absTime = GetAbsoluteTime();
|
|
|
|
// lets limit video size by time
|
|
if (absTime > MaxRecordTime)
|
|
{
|
|
FASTLOG(FLog::DeviceLost, "DSVideoCaptureEngine: Cancel by time");
|
|
RBXASSERT(cancelAction);
|
|
cancelAction->doIt(NULL);
|
|
return;
|
|
}
|
|
|
|
RBX::Graphics::Device* graphicsDevice = static_cast<RBX::Graphics::Device*>(device);
|
|
RBX::Graphics::Framebuffer* fb = graphicsDevice->getMainFramebuffer();
|
|
// lets limit video size by time
|
|
if (!fb)
|
|
{
|
|
FASTLOG(FLog::DeviceLost, "DSVideoCaptureEngine: Cancel by device lost");
|
|
RBXASSERT(cancelAction);
|
|
cancelAction->doIt(NULL);
|
|
return;
|
|
}
|
|
|
|
int bpp = 4;
|
|
int cx = fb->getWidth();
|
|
int cy = fb->getHeight();
|
|
|
|
int ncx, ncy;
|
|
GetSquareSizes(cx, cy, ncx, ncy);
|
|
|
|
int capcx, capcy;
|
|
videoSource->GetCaptureSize(capcx, capcy);
|
|
|
|
if (ncx != capcx || ncy != capcy)
|
|
{
|
|
FASTLOG(FLog::DeviceLost, "DSVideoCaptureEngine: Cancel by resize");
|
|
|
|
if (RBX::DataModel* dm = dynamic_cast<RBX::DataModel*>(cancelAction->getContainer()))
|
|
{
|
|
// This is not safe if the verb dies before the DM does! But we can't take ownership of the verb here and generally verbs are tied to DM lifetime...
|
|
dm->submitTask(boost::bind(cancelRecording, dm, cancelAction), DataModelJob::Write);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ((absTime - lastPushedVideoFrameTime) < videoSource->GetTargetFrameRate() || videoSource->GetQueueSize() >= RBX::DS::MaxFramesQueue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
unsigned dataSize = bpp * cx * cy;
|
|
if (dataSize != frameDataSize)
|
|
{
|
|
delete[] frameData;
|
|
frameDataSize = dataSize;
|
|
frameData = new unsigned char[frameDataSize];
|
|
}
|
|
|
|
fb->download(frameData, dataSize);
|
|
|
|
int actualSize = bpp * ncx * ncy;
|
|
unsigned char* videoData = new unsigned char[actualSize];
|
|
memset(videoData, 0, actualSize);
|
|
|
|
// this is address of first valid pixel (there might be padding)
|
|
unsigned char* videoStart = videoData + ((ncy - cy) / 2) * ncx * bpp + ((ncx - cx) / 2) * bpp;
|
|
|
|
for (int i = 0; i < cy; ++i)
|
|
{
|
|
unsigned originalIdx = i * cx * bpp;
|
|
unsigned destIdx = (cy - 1 - i) * ncx * bpp;
|
|
|
|
unsigned char* src = &frameData[originalIdx];
|
|
for (int x = 0; x < cx; ++x)
|
|
std::swap(src[x * bpp + 0], src[x * bpp + 2]);
|
|
|
|
memcpy(&videoStart[destIdx], src, cx * bpp);
|
|
}
|
|
|
|
// videoData are now owned by videoSource and it will delete it after it doesn't need it
|
|
videoSource->pushNextFrame(videoData, actualSize, ncx, ncy, (ncx - cx) * bpp);
|
|
}
|
|
|
|
LONG DSVideoCaptureEngine::GetTime()
|
|
{
|
|
return audioSource != NULL ? audioSource->GetAudioTime()->GetTime() : 0;
|
|
}
|
|
|
|
LONG DSVideoCaptureEngine::GetAbsoluteTime()
|
|
{
|
|
DWORD curTime = timeGetTime();
|
|
return curTime - startTime;
|
|
}
|
|
|
|
const WCHAR profileDataV[] = L"<profile version=\"589824\" \
|
|
storageformat=\"1\" \
|
|
name=\"RBXVideo\" \
|
|
description=\"Streams: 1 video\"> \
|
|
<streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" \
|
|
streamnumber=\"1\" streamname=\"Video1\" inputname=\"\" \
|
|
bitrate=\"4000000\" bufferwindow=\"3000\" reliabletransport=\"0\" decodercomplexity=\"\" rfc1766langid=\"en-us\"> \
|
|
<videomediaprops maxkeyframespacing=\"80000000\" quality=\"100\"/> \
|
|
<wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" \
|
|
bfixedsizesamples=\"0\" btemporalcompression=\"1\" lsamplesize=\"0\"> \
|
|
<videoinfoheader dwbitrate=\"4000000\" dwbiterrorrate=\"0\" avgtimeperframe=\"333333\"> \
|
|
<rcsource left=\"0\" top=\"0\" right=\"1280\" bottom=\"720\"/> \
|
|
<rctarget left=\"0\" top=\"0\" right=\"1280\" bottom=\"720\"/> \
|
|
<bitmapinfoheader biwidth=\"1280\" biheight=\"720\" biplanes=\"1\" bibitcount=\"24\" \
|
|
bicompression=\"WMV3\" bisizeimage=\"0\" bixpelspermeter=\"0\" \
|
|
biypelspermeter=\"0\" biclrused=\"0\" biclrimportant=\"0\"/> \
|
|
</videoinfoheader> \
|
|
</wmmediatype> \
|
|
</streamconfig> \
|
|
</profile>";
|
|
|
|
// const WCHAR newProfileDataAV[] = L"<profile version=\"589824\" \r\n storageformat=\"1\" \r\n name=\"RBXVideo\" \r\n description=\"Streams: 1 audio 1 video\"> \r\n <streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" \r\n streamnumber=\"1\" \r\n streamname=\"Video1\" \r\n inputname=\"\" \r\n bitrate=\"4000000\" \r\n bufferwindow=\"3000\" \r\n reliabletransport=\"0\" \r\n decodercomplexity=\"\" \r\n rfc1766langid=\"en-us\" \r\n > \r\n <videomediaprops maxkeyframespacing=\"80000000\" \r\n quality=\"100\"/> \r\n <wmmediatype subtype=\"{32564D57-0000-0010-8000-00AA00389B71}\" \r\n bfixedsizesamples=\"0\" \r\n btemporalcompression=\"1\" \r\n lsamplesize=\"0\"> \r\n <videoinfoheader dwbitrate=\"4000000\" \r\n dwbiterrorrate=\"0\" \r\n avgtimeperframe=\"555555\"> \r\n <rcsource left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"480\"/> \r\n <rctarget left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"720\"/> \r\n <bitmapinfoheader biwidth=\"1280\" \r\n biheight=\"720\" \r\n biplanes=\"1\" \r\n bibitcount=\"24\" \r\n bicompression=\"WMV2\" \r\n bisizeimage=\"0\" \r\n bixpelspermeter=\"0\" \r\n biypelspermeter=\"0\" \r\n biclrused=\"0\" \r\n biclrimportant=\"0\"/> \r\n </videoinfoheader>\r\n </wmmediatype>\r\n </streamconfig>\r\n <streamconfig majortype=\"{73647561-0000-0010-8000-00AA00389B71}\" \r\n streamnumber=\"2\" \r\n streamname=\"Audio2\" \r\n inputname=\"\" \r\n bitrate=\"4000000\" \r\n bufferwindow=\"3000\" \r\n reliabletransport=\"0\" \r\n decodercomplexity=\"\" \r\n rfc1766langid=\"en-us\" \r\n > \r\n <wmmediatype subtype=\"{00000161-0000-0010-8000-00AA00389B71}\" \r\n bfixedsizesamples=\"1\" \r\n btemporalcompression=\"0\" \r\n lsamplesize=\"0\"> \r\n <waveformatex wFormatTag=\"353\" \r\n nChannels=\"2\" \r\n nSamplesPerSec=\"48000\" \r\n nAvgBytesPerSec="12000" \r\n nBlockAlign=\"4096\" \r\n wBitsPerSample=\"16\" \r\n codecdata=\"008800000F0000000000\"/> \r\n </wmmediatype>\r\n </streamconfig>\r\n </profile> \r\n";
|
|
const WCHAR newProfileDataAV[] = L"<profile version=\"589824\" \r\n storageformat=\"1\" \r\n name=\"RBXVideo\" \r\n description=\"Streams: 1 audio 1 video\"> \r\n <streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" \r\n streamnumber=\"1\" \r\n streamname=\"Video1\" \r\n inputname=\"\" \r\n bitrate=\"4000000\" \r\n bufferwindow=\"3000\" \r\n reliabletransport=\"0\" \r\n decodercomplexity=\"\" \r\n rfc1766langid=\"en-us\" \r\n > \r\n <videomediaprops maxkeyframespacing=\"80000000\" \r\n quality=\"100\"/> \r\n <wmmediatype subtype=\"{32564D57-0000-0010-8000-00AA00389B71}\" \r\n bfixedsizesamples=\"0\" \r\n btemporalcompression=\"1\" \r\n lsamplesize=\"0\"> \r\n <videoinfoheader dwbitrate=\"4000000\" \r\n dwbiterrorrate=\"0\" \r\n avgtimeperframe=\"555555\"> \r\n <rcsource left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"720\"/> \r\n <rctarget left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"720\"/> \r\n <bitmapinfoheader biwidth=\"1280\" \r\n biheight=\"720\" \r\n biplanes=\"1\" \r\n bibitcount=\"24\" \r\n bicompression=\"WMV3\" \r\n bisizeimage=\"0\" \r\n bixpelspermeter=\"0\" \r\n biypelspermeter=\"0\" \r\n biclrused=\"0\" \r\n biclrimportant=\"0\"/> \r\n </videoinfoheader>\r\n </wmmediatype>\r\n </streamconfig>\r\n <streamconfig majortype=\"{73647561-0000-0010-8000-00AA00389B71}\" \r\n streamnumber=\"2\" \r\n streamname=\"Audio2\" \r\n inputname=\"\" \r\n bitrate=\"4000000\" \r\n bufferwindow=\"3000\" \r\n reliabletransport=\"0\" \r\n decodercomplexity=\"\" \r\n rfc1766langid=\"en-us\" \r\n > \r\n <wmmediatype subtype=\"{00000162-0000-0010-8000-00AA00389B71}\" \r\n bfixedsizesamples=\"1\" \r\n btemporalcompression=\"0\" \r\n lsamplesize=\"0\"> \r\n <waveformatex wFormatTag=\"354\" \r\n nChannels=\"2\" \r\n nSamplesPerSec=\"48000\" \r\n nAvgBytesPerSec=\"12000\" \r\n nBlockAlign=\"2048\" \r\n wBitsPerSample=\"16\" \r\n codecdata=\"1000030000000000000000000000600042C0\"/> \r\n </wmmediatype>\r\n </streamconfig>\r\n </profile> \r\n";
|
|
|
|
const WCHAR profWinXPAV[] = L"<profile version=\"589824\" \r\n storageformat=\"1\" \r\n name=\"RBXVidXP\" \r\n description=\"Streams: 1 audio 1 video\"> \r\n <streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" \r\n streamnumber=\"1\" \r\n streamname=\"Video1\" \r\n inputname=\"\" \r\n bitrate=\"4000000\" \r\n bufferwindow=\"3000\" \r\n reliabletransport=\"0\" \r\n decodercomplexity=\"\" \r\n rfc1766langid=\"en-us\" \r\n > \r\n <videomediaprops maxkeyframespacing=\"80000000\" \r\n quality=\"100\"/> \r\n <wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" \r\n bfixedsizesamples=\"0\" \r\n btemporalcompression=\"1\" \r\n lsamplesize=\"0\"> \r\n <videoinfoheader dwbitrate=\"4000000\" \r\n dwbiterrorrate=\"0\" \r\n avgtimeperframe=\"555555\"> \r\n <rcsource left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"720\"/> \r\n <rctarget left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"720\"/> \r\n <bitmapinfoheader biwidth=\"1280\" \r\n biheight=\"720\" \r\n biplanes=\"1\" \r\n bibitcount=\"24\" \r\n bicompression=\"WMV3\" \r\n bisizeimage=\"0\" \r\n bixpelspermeter=\"0\" \r\n biypelspermeter=\"0\" \r\n biclrused=\"0\" \r\n biclrimportant=\"0\"/> \r\n </videoinfoheader>\r\n </wmmediatype>\r\n </streamconfig>\r\n <streamconfig majortype=\"{73647561-0000-0010-8000-00AA00389B71}\" \r\n streamnumber=\"2\" \r\n streamname=\"Audio2\" \r\n inputname=\"\" \r\n bitrate=\"4000000\" \r\n bufferwindow=\"3000\" \r\n reliabletransport=\"0\" \r\n decodercomplexity=\"\" \r\n rfc1766langid=\"en-us\" \r\n> \r\n <wmmediatype subtype=\"{00000161-0000-0010-8000-00AA00389B71}\" \r\n bfixedsizesamples=\"1\" \r\n btemporalcompression=\"0\" \r\n lsamplesize=\"0\"> \r\n <waveformatex wFormatTag=\"353\" \r\n nChannels=\"2\" \r\n nSamplesPerSec=\"48000\" \r\n nAvgBytesPerSec=\"12000\" \r\n nBlockAlign=\"4096\" \r\n wBitsPerSample=\"16\" \r\n codecdata=\"008800000F0000000000\"/> \r\n </wmmediatype>\r\n </streamconfig>\r\n </profile> \r\n";
|
|
|
|
const WCHAR profWinXPV[] = L"<profile version=\"589824\" \r\n storageformat=\"1\" \r\n name=\"RBXVidXP\" \r\n description=\"Streams: 1 video\"> \r\n <streamconfig majortype=\"{73646976-0000-0010-8000-00AA00389B71}\" \r\n streamnumber=\"1\" \r\n streamname=\"Video1\" \r\n inputname=\"\" \r\n bitrate=\"4000000\" \r\n bufferwindow=\"3000\" \r\n reliabletransport=\"0\" \r\n decodercomplexity=\"\" \r\n rfc1766langid=\"en-us\" \r\n > \r\n <videomediaprops maxkeyframespacing=\"80000000\" \r\n quality=\"100\"/> \r\n <wmmediatype subtype=\"{33564D57-0000-0010-8000-00AA00389B71}\" \r\n bfixedsizesamples=\"0\" \r\n btemporalcompression=\"1\" \r\n lsamplesize=\"0\"> \r\n <videoinfoheader dwbitrate=\"4000000\" \r\n dwbiterrorrate=\"0\" \r\n avgtimeperframe=\"555555\"> \r\n <rcsource left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"720\"/> \r\n <rctarget left=\"0\" \r\n top=\"0\" \r\n right=\"1280\" \r\n bottom=\"720\"/> \r\n <bitmapinfoheader biwidth=\"1280\" \r\n biheight=\"720\" \r\n biplanes=\"1\" \r\n bibitcount=\"24\" \r\n bicompression=\"WMV3\" \r\n bisizeimage=\"0\" \r\n bixpelspermeter=\"0\" \r\n biypelspermeter=\"0\" \r\n biclrused=\"0\" \r\n biclrimportant=\"0\"/> \r\n </videoinfoheader>\r\n </wmmediatype>\r\n </streamconfig>\r\n </profile> \r\n";
|
|
|
|
HRESULT DSVideoCaptureEngine::BuildCaptureGraph(int cx, int cy, bool forceNoAudio)
|
|
{
|
|
IBaseFilter* asfWriterFilter = NULL;
|
|
IFileSinkFilter2* fileSink = NULL;
|
|
IWMProfileManager* profileMgr = NULL;
|
|
IWMProfile* profile = NULL;
|
|
IConfigAsfWriter* asfConfig = NULL;
|
|
|
|
OSVERSIONINFO osvi = { 0 };
|
|
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
|
GetVersionEx(&osvi);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
ERROR_ON_FAIL(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&graph));
|
|
|
|
ERROR_ON_FAIL(graph->QueryInterface(IID_IMediaControl, (void**)&mediaControl));
|
|
|
|
videoSource = new DS::CVideoStreamFilter(NULL, &hr, cx, cy);
|
|
if (FAILED(hr)) {
|
|
goto Error;
|
|
}
|
|
ERROR_ON_FAIL(graph->AddFilter(videoSource, L"GameVideoSource"));
|
|
|
|
ERROR_ON_FAIL(CoCreateInstance(CLSID_WMAsfWriter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&asfWriterFilter));
|
|
|
|
ERROR_ON_FAIL(asfWriterFilter->QueryInterface(IID_IFileSinkFilter2, (void**)&fileSink));
|
|
|
|
WCHAR path[MAX_PATH];
|
|
GenerateFileName(path);
|
|
ERROR_ON_FAIL(fileSink->SetFileName(path, NULL));
|
|
|
|
ERROR_ON_FAIL(graph->AddFilter(asfWriterFilter, L"asfWrite"));
|
|
|
|
ERROR_ON_FAIL(WMCreateProfileManager(&profileMgr));
|
|
|
|
// sound should be enables we should not force you to no sound and we have sound only on Vista & Win7 for now
|
|
if (soundState->enabledFunction() && !forceNoAudio && osvi.dwMajorVersion > 5)
|
|
{
|
|
if (osvi.dwMajorVersion > 5)
|
|
{
|
|
ERROR_ON_FAIL(profileMgr->LoadProfileByData(newProfileDataAV, &profile));
|
|
}
|
|
else
|
|
{
|
|
ERROR_ON_FAIL(profileMgr->LoadProfileByData(profWinXPAV, &profile));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (osvi.dwMajorVersion > 5)
|
|
{
|
|
ERROR_ON_FAIL(profileMgr->LoadProfileByData(profileDataV, &profile));
|
|
}
|
|
else
|
|
{
|
|
ERROR_ON_FAIL(profileMgr->LoadProfileByData(profWinXPV, &profile));
|
|
}
|
|
}
|
|
|
|
ERROR_ON_FAIL(asfWriterFilter->QueryInterface(IID_IConfigAsfWriter, (void**)&asfConfig));
|
|
|
|
ERROR_ON_FAIL(asfConfig->ConfigureFilterUsingProfile(profile));
|
|
|
|
ERROR_ON_FAIL(hr = graph->Render(videoSource->GetPin()));
|
|
|
|
if (soundState->enabledFunction() && !forceNoAudio)
|
|
{
|
|
audioSource = new DS::CAudioStreamFilter(NULL, &hr, soundState, this);
|
|
if (FAILED(hr)) {
|
|
goto Error;
|
|
}
|
|
ERROR_ON_FAIL(graph->AddFilter(audioSource, L"AudioStream"));
|
|
|
|
ERROR_ON_FAIL(graph->Render(audioSource->GetPin()));
|
|
}
|
|
|
|
startTime = timeGetTime();
|
|
|
|
videoSource->SetAudioClock(this);
|
|
Error:
|
|
SAFE_RELEASE(asfWriterFilter);
|
|
SAFE_RELEASE(fileSink);
|
|
|
|
SAFE_RELEASE(profileMgr);
|
|
SAFE_RELEASE(profile);
|
|
SAFE_RELEASE(asfConfig);
|
|
|
|
return hr;
|
|
}
|
|
|
|
void DSVideoCaptureEngine::DestroyCaptureGgaph()
|
|
{
|
|
SAFE_RELEASE(graph);
|
|
SAFE_RELEASE(mediaControl);
|
|
|
|
graph = NULL;
|
|
mediaControl = NULL;
|
|
videoSource = NULL;
|
|
}
|
|
|
|
int DSVideoCaptureEngine::GenerateFileName(LPWSTR fileName)
|
|
{
|
|
SYSTEMTIME time;
|
|
::GetLocalTime(&time);
|
|
|
|
boost::filesystem::path folder = RBX::FileSystem::getUserDirectory(true, RBX::DirVideo);
|
|
|
|
wchar_t name[MAX_PATH];
|
|
int iLen = _snwprintf_s(name, _MAX_PATH - 1, _MAX_PATH - 1, L"%s-%d%02d%02d-%02d%02d%02d%01d%s.%s",
|
|
DS::ProcTitle,
|
|
time.wYear, time.wMonth, time.wDay,
|
|
time.wHour, time.wMinute, time.wSecond,
|
|
time.wMilliseconds / 100, // uniqueness to tenths of a sec.
|
|
L"",
|
|
DS::VideoFileExt);
|
|
|
|
boost::filesystem::path path = folder / name;
|
|
wcsncpy_s(fileName, MAX_PATH, path.native().c_str(), _TRUNCATE); // world sucks...
|
|
|
|
// TODO: Localization: Unicode????
|
|
fullFileName.clear();
|
|
for (wchar_t* p = fileName; *p; ++p)
|
|
{
|
|
fullFileName.push_back(*p);
|
|
}
|
|
return iLen;
|
|
}
|
|
}
|