newCX and newCY were being changed when a sample was received. However, the sample associated with that size change will not be processed until it's used in Preprocess, so it could often change the texture size before the sample was even used. This could cause crashes under certain circumstances. A simple fix to this is to only change newCX/newCY when the sample is actually used.
1977 lines
64 KiB
C++
1977 lines
64 KiB
C++
/********************************************************************************
|
|
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
********************************************************************************/
|
|
|
|
|
|
#include "DShowPlugin.h"
|
|
|
|
struct ResSize
|
|
{
|
|
UINT cx;
|
|
UINT cy;
|
|
};
|
|
|
|
enum
|
|
{
|
|
COLORSPACE_AUTO,
|
|
COLORSPACE_709,
|
|
COLORSPACE_601
|
|
};
|
|
|
|
#undef DEFINE_GUID
|
|
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
|
EXTERN_C const GUID DECLSPEC_SELECTANY name \
|
|
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
|
|
|
#include "IVideoCaptureFilter.h" // for Elgato GameCapture
|
|
|
|
DWORD STDCALL PackPlanarThread(ConvertData *data);
|
|
|
|
#define NEAR_SILENT 3000
|
|
#define NEAR_SILENTf 3000.0
|
|
|
|
|
|
#define ELGATO_FORCE_BUFFERING 1 // Workaround to prevent jerky playback with HD60
|
|
|
|
#if ELGATO_FORCE_BUFFERING
|
|
// FMB NOTE 03-Feb-15: Workaround for Elgato Game Capture HD60 which plays jerky unless we add a little buffering.
|
|
// The buffer time for this workaround is so small that it shouldn't affect sync with other sources.
|
|
// FMB NOTE 18-Feb-15: Enable buffering for every Elgato device to make sure device timestamps are used.
|
|
// Should improve sync issues.
|
|
|
|
// param argBufferTime - 100-nsec unit (same as REFERENCE_TIME)
|
|
void ElgatoCheckBuffering(IBaseFilter* deviceFilter, bool& argUseBuffering, UINT& argBufferTime)
|
|
{
|
|
const int elgatoMinBufferTime = 1 * 10000; // 1 msec
|
|
|
|
if (!argUseBuffering || argBufferTime < elgatoMinBufferTime)
|
|
{
|
|
argUseBuffering = true;
|
|
argBufferTime = elgatoMinBufferTime;
|
|
Log(TEXT(" Elgato Game Capture: force buffering with %d msec"), elgatoMinBufferTime / 10000);
|
|
}
|
|
}
|
|
#endif // ELGATO_FORCE_BUFFERING
|
|
|
|
DeinterlacerConfig deinterlacerConfigs[DEINTERLACING_TYPE_LAST] = {
|
|
{DEINTERLACING_NONE, FIELD_ORDER_NONE, DEINTERLACING_PROCESSOR_CPU},
|
|
{DEINTERLACING_DISCARD, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_CPU},
|
|
{DEINTERLACING_RETRO, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_CPU | DEINTERLACING_PROCESSOR_GPU, true},
|
|
{DEINTERLACING_BLEND, FIELD_ORDER_NONE, DEINTERLACING_PROCESSOR_GPU},
|
|
{DEINTERLACING_BLEND2x, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_GPU, true},
|
|
{DEINTERLACING_LINEAR, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_GPU},
|
|
{DEINTERLACING_LINEAR2x, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_GPU, true},
|
|
{DEINTERLACING_YADIF, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_GPU},
|
|
{DEINTERLACING_YADIF2x, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_GPU, true},
|
|
{DEINTERLACING__DEBUG, FIELD_ORDER_TFF | FIELD_ORDER_BFF, DEINTERLACING_PROCESSOR_GPU},
|
|
};
|
|
|
|
bool DeviceSource::Init(XElement *data)
|
|
{
|
|
HRESULT err;
|
|
err = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, (REFIID)IID_IFilterGraph, (void**)&graph);
|
|
if(FAILED(err))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to build IGraphBuilder, result = %08lX"), err);
|
|
return false;
|
|
}
|
|
|
|
err = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, (REFIID)IID_ICaptureGraphBuilder2, (void**)&capture);
|
|
if(FAILED(err))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to build ICaptureGraphBuilder2, result = %08lX"), err);
|
|
return false;
|
|
}
|
|
|
|
hSampleMutex = OSCreateMutex();
|
|
if(!hSampleMutex)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: could not create sample mutex"));
|
|
return false;
|
|
}
|
|
|
|
capture->SetFiltergraph(graph);
|
|
|
|
int numThreads = MAX(OSGetTotalCores()-2, 1);
|
|
hConvertThreads = (HANDLE*)Allocate(sizeof(HANDLE)*numThreads);
|
|
convertData = (ConvertData*)Allocate(sizeof(ConvertData)*numThreads);
|
|
|
|
zero(hConvertThreads, sizeof(HANDLE)*numThreads);
|
|
zero(convertData, sizeof(ConvertData)*numThreads);
|
|
|
|
this->data = data;
|
|
UpdateSettings();
|
|
|
|
//if(!bFiltersLoaded)
|
|
// return false;
|
|
|
|
Log(TEXT("Using directshow input"));
|
|
|
|
return true;
|
|
}
|
|
|
|
DeviceSource::~DeviceSource()
|
|
{
|
|
Stop();
|
|
UnloadFilters();
|
|
|
|
FlushSamples();
|
|
|
|
SafeReleaseLogRef(capture);
|
|
SafeReleaseLogRef(graph);
|
|
|
|
if(hConvertThreads)
|
|
Free(hConvertThreads);
|
|
|
|
if(convertData)
|
|
Free(convertData);
|
|
|
|
if(hSampleMutex)
|
|
OSCloseMutex(hSampleMutex);
|
|
}
|
|
|
|
#define SHADER_PATH TEXT("plugins/DShowPlugin/shaders/")
|
|
|
|
String DeviceSource::ChooseShader()
|
|
{
|
|
if(colorType == DeviceOutputType_RGB && !bUseChromaKey)
|
|
return String();
|
|
|
|
String strShader;
|
|
strShader << SHADER_PATH;
|
|
|
|
if(bUseChromaKey)
|
|
strShader << TEXT("ChromaKey_");
|
|
|
|
if(colorType == DeviceOutputType_I420)
|
|
strShader << TEXT("YUVToRGB.pShader");
|
|
else if(colorType == DeviceOutputType_YV12)
|
|
strShader << TEXT("YVUToRGB.pShader");
|
|
else if(colorType == DeviceOutputType_YVYU)
|
|
strShader << TEXT("YVXUToRGB.pShader");
|
|
else if(colorType == DeviceOutputType_YUY2)
|
|
strShader << TEXT("YUXVToRGB.pShader");
|
|
else if(colorType == DeviceOutputType_UYVY)
|
|
strShader << TEXT("UYVToRGB.pShader");
|
|
else if(colorType == DeviceOutputType_HDYC)
|
|
strShader << TEXT("HDYCToRGB.pShader");
|
|
else
|
|
strShader << TEXT("RGB.pShader");
|
|
|
|
return strShader;
|
|
}
|
|
|
|
String DeviceSource::ChooseDeinterlacingShader()
|
|
{
|
|
String shader;
|
|
shader << SHADER_PATH << TEXT("Deinterlace_");
|
|
|
|
#ifdef _DEBUG
|
|
#define DEBUG__ _DEBUG
|
|
#undef _DEBUG
|
|
#endif
|
|
#define SELECT(x) case DEINTERLACING_##x: shader << String(TEXT(#x)).MakeLower(); break;
|
|
switch(deinterlacer.type)
|
|
{
|
|
SELECT(RETRO)
|
|
SELECT(BLEND)
|
|
SELECT(BLEND2x)
|
|
SELECT(LINEAR)
|
|
SELECT(LINEAR2x)
|
|
SELECT(YADIF)
|
|
SELECT(YADIF2x)
|
|
SELECT(_DEBUG)
|
|
}
|
|
return shader << TEXT(".pShader");
|
|
#undef SELECT
|
|
#ifdef DEBUG__
|
|
#define _DEBUG DEBUG__
|
|
#undef DEBUG__
|
|
#endif
|
|
}
|
|
|
|
const float yuv709Mat[16] = {0.182586f, 0.614231f, 0.062007f, 0.062745f,
|
|
-0.100644f, -0.338572f, 0.439216f, 0.501961f,
|
|
0.439216f, -0.398942f, -0.040274f, 0.501961f,
|
|
0.000000f, 0.000000f, 0.000000f, 1.000000f};
|
|
|
|
const float yuvMat[16] = {0.256788f, 0.504129f, 0.097906f, 0.062745f,
|
|
-0.148223f, -0.290993f, 0.439216f, 0.501961f,
|
|
0.439216f, -0.367788f, -0.071427f, 0.501961f,
|
|
0.000000f, 0.000000f, 0.000000f, 1.000000f};
|
|
|
|
const float yuvToRGB601[2][16] =
|
|
{
|
|
{
|
|
1.164384f, 0.000000f, 1.596027f, -0.874202f,
|
|
1.164384f, -0.391762f, -0.812968f, 0.531668f,
|
|
1.164384f, 2.017232f, 0.000000f, -1.085631f,
|
|
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
|
},
|
|
{
|
|
1.000000f, 0.000000f, 1.407520f, -0.706520f,
|
|
1.000000f, -0.345491f, -0.716948f, 0.533303f,
|
|
1.000000f, 1.778976f, 0.000000f, -0.892976f,
|
|
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
|
}
|
|
};
|
|
|
|
const float yuvToRGB709[2][16] = {
|
|
{
|
|
1.164384f, 0.000000f, 1.792741f, -0.972945f,
|
|
1.164384f, -0.213249f, -0.532909f, 0.301483f,
|
|
1.164384f, 2.112402f, 0.000000f, -1.133402f,
|
|
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
|
},
|
|
{
|
|
1.000000f, 0.000000f, 1.581000f, -0.793600f,
|
|
1.000000f, -0.188062f, -0.469967f, 0.330305f,
|
|
1.000000f, 1.862906f, 0.000000f, -0.935106f,
|
|
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
|
}
|
|
};
|
|
|
|
void DeviceSource::SetAudioInfo(AM_MEDIA_TYPE *audioMediaType, GUID &expectedAudioType)
|
|
{
|
|
expectedAudioType = audioMediaType->subtype;
|
|
|
|
if(audioMediaType->formattype == FORMAT_WaveFormatEx)
|
|
{
|
|
WAVEFORMATEX *pFormat = reinterpret_cast<WAVEFORMATEX*>(audioMediaType->pbFormat);
|
|
mcpy(&audioFormat, pFormat, sizeof(audioFormat));
|
|
|
|
Log(TEXT(" device audio info - bits per sample: %u, channels: %u, samples per sec: %u, block size: %u"),
|
|
audioFormat.wBitsPerSample, audioFormat.nChannels, audioFormat.nSamplesPerSec, audioFormat.nBlockAlign);
|
|
|
|
//avoid local resampling if possible
|
|
/*if(pFormat->nSamplesPerSec != 44100)
|
|
{
|
|
pFormat->nSamplesPerSec = 44100;
|
|
if(SUCCEEDED(audioConfig->SetFormat(audioMediaType)))
|
|
{
|
|
Log(TEXT(" also successfully set samples per sec to 44.1k"));
|
|
audioFormat.nSamplesPerSec = 44100;
|
|
}
|
|
}*/
|
|
}
|
|
else
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Audio format was not a normal wave format"));
|
|
soundOutputType = 0;
|
|
}
|
|
|
|
DeleteMediaType(audioMediaType);
|
|
}
|
|
|
|
bool DeviceSource::LoadFilters()
|
|
{
|
|
if(bCapturing || bFiltersLoaded)
|
|
return false;
|
|
|
|
bool bSucceeded = false;
|
|
|
|
List<MediaOutputInfo> outputList;
|
|
IAMStreamConfig *config = NULL;
|
|
bool bAddedVideoCapture = false, bAddedAudioCapture = false, bAddedDevice = false;
|
|
GUID expectedMediaType;
|
|
IPin *devicePin = NULL, *audioPin = NULL;
|
|
HRESULT err;
|
|
String strShader;
|
|
|
|
deinterlacer.isReady = true;
|
|
|
|
if(graph == NULL) {
|
|
err = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, (REFIID)IID_IFilterGraph, (void**)&graph);
|
|
if(FAILED(err))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to build IGraphBuilder, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
}
|
|
|
|
if(capture == NULL) {
|
|
err = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, (REFIID)IID_ICaptureGraphBuilder2, (void**)&capture);
|
|
if(FAILED(err))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to build ICaptureGraphBuilder2, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
|
|
capture->SetFiltergraph(graph);
|
|
}
|
|
|
|
bUseThreadedConversion = API->UseMultithreadedOptimizations() && (OSGetTotalCores() > 1);
|
|
|
|
//------------------------------------------------
|
|
// basic initialization vars
|
|
|
|
bool bForceCustomAudio = data->GetInt(TEXT("forceCustomAudioDevice")) != 0;
|
|
bUseAudioRender = data->GetInt(TEXT("useAudioRender")) != 0;
|
|
|
|
bUseCustomResolution = data->GetInt(TEXT("customResolution"));
|
|
strDevice = data->GetString(TEXT("device"));
|
|
strDeviceName = data->GetString(TEXT("deviceName"));
|
|
strDeviceID = data->GetString(TEXT("deviceID"));
|
|
strAudioDevice = data->GetString(TEXT("audioDevice"));
|
|
strAudioName = data->GetString(TEXT("audioDeviceName"));
|
|
strAudioID = data->GetString(TEXT("audioDeviceID"));
|
|
fullRange = data->GetInt(TEXT("fullrange")) != 0;
|
|
use709 = false;
|
|
|
|
bFlipVertical = data->GetInt(TEXT("flipImage")) != 0;
|
|
bFlipHorizontal = data->GetInt(TEXT("flipImageHorizontal")) != 0;
|
|
bUsePointFiltering = data->GetInt(TEXT("usePointFiltering")) != 0;
|
|
|
|
bool elgato = sstri(strDeviceName, L"elgato") != nullptr;
|
|
|
|
opacity = data->GetInt(TEXT("opacity"), 100);
|
|
|
|
float volume = data->GetFloat(TEXT("volume"), 1.0f);
|
|
|
|
bUseBuffering = data->GetInt(TEXT("useBuffering")) != 0;
|
|
bufferTime = data->GetInt(TEXT("bufferTime"))*10000;
|
|
|
|
//------------------------------------------------
|
|
// chrom key stuff
|
|
|
|
bUseChromaKey = data->GetInt(TEXT("useChromaKey")) != 0;
|
|
keyColor = data->GetInt(TEXT("keyColor"), 0xFFFFFFFF);
|
|
keySimilarity = data->GetInt(TEXT("keySimilarity"));
|
|
keyBlend = data->GetInt(TEXT("keyBlend"), 80);
|
|
keySpillReduction = data->GetInt(TEXT("keySpillReduction"), 50);
|
|
|
|
deinterlacer.type = data->GetInt(TEXT("deinterlacingType"), 0);
|
|
deinterlacer.fieldOrder = data->GetInt(TEXT("deinterlacingFieldOrder"), 0);
|
|
deinterlacer.processor = data->GetInt(TEXT("deinterlacingProcessor"), 0);
|
|
deinterlacer.doublesFramerate = data->GetInt(TEXT("deinterlacingDoublesFramerate"), 0) != 0;
|
|
|
|
if(keyBaseColor.x < keyBaseColor.y && keyBaseColor.x < keyBaseColor.z)
|
|
keyBaseColor -= keyBaseColor.x;
|
|
else if(keyBaseColor.y < keyBaseColor.x && keyBaseColor.y < keyBaseColor.z)
|
|
keyBaseColor -= keyBaseColor.y;
|
|
else if(keyBaseColor.z < keyBaseColor.x && keyBaseColor.z < keyBaseColor.y)
|
|
keyBaseColor -= keyBaseColor.z;
|
|
|
|
//------------------------------------------------
|
|
// get the device filter and pins
|
|
|
|
if(strDeviceName.IsValid())
|
|
deviceFilter = GetDeviceByValue(CLSID_VideoInputDeviceCategory, L"FriendlyName", strDeviceName, L"DevicePath", strDeviceID);
|
|
else
|
|
{
|
|
if(!strDevice.IsValid())
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Invalid device specified"));
|
|
goto cleanFinish;
|
|
}
|
|
|
|
deviceFilter = GetDeviceByValue(CLSID_VideoInputDeviceCategory, L"FriendlyName", strDevice);
|
|
}
|
|
|
|
if(!deviceFilter)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Could not create device filter"));
|
|
goto cleanFinish;
|
|
}
|
|
|
|
devicePin = GetOutputPin(deviceFilter, &MEDIATYPE_Video);
|
|
if(!devicePin)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Could not get device video pin"));
|
|
goto cleanFinish;
|
|
}
|
|
|
|
soundOutputType = data->GetInt(TEXT("soundOutputType")); //0 is for backward-compatibility
|
|
if (strAudioID.CompareI(TEXT("Disabled")))
|
|
soundOutputType = 0;
|
|
|
|
if(soundOutputType != 0)
|
|
{
|
|
if(!bForceCustomAudio)
|
|
{
|
|
err = capture->FindPin(deviceFilter, PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, FALSE, 0, &audioPin);
|
|
bDeviceHasAudio = SUCCEEDED(err);
|
|
}
|
|
else
|
|
bDeviceHasAudio = false;
|
|
|
|
if(!bDeviceHasAudio)
|
|
{
|
|
if(strDeviceName.IsValid())
|
|
{
|
|
audioDeviceFilter = GetDeviceByValue(CLSID_AudioInputDeviceCategory, L"FriendlyName", strAudioName, L"DevicePath", strAudioID);
|
|
if(!audioDeviceFilter)
|
|
AppWarning(TEXT("DShowPlugin: Invalid audio device: name '%s', path '%s'"), strAudioName.Array(), strAudioID.Array());
|
|
}
|
|
else if(strAudioDevice.IsValid())
|
|
{
|
|
audioDeviceFilter = GetDeviceByValue(CLSID_AudioInputDeviceCategory, L"FriendlyName", strAudioDevice);
|
|
if(!audioDeviceFilter)
|
|
AppWarning(TEXT("DShowPlugin: Could not create audio device filter"));
|
|
}
|
|
|
|
if(audioDeviceFilter)
|
|
err = capture->FindPin(audioDeviceFilter, PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, FALSE, 0, &audioPin);
|
|
else
|
|
err = E_FAIL;
|
|
}
|
|
|
|
if(FAILED(err) || !audioPin)
|
|
{
|
|
Log(TEXT("DShowPlugin: No audio pin, result = %lX"), err);
|
|
soundOutputType = 0;
|
|
}
|
|
}
|
|
else
|
|
bDeviceHasAudio = bForceCustomAudio = false;
|
|
|
|
int soundTimeOffset = data->GetInt(TEXT("soundTimeOffset"));
|
|
|
|
GetOutputList(devicePin, outputList);
|
|
|
|
//------------------------------------------------
|
|
// initialize the basic video variables and data
|
|
|
|
if(FAILED(err = devicePin->QueryInterface(IID_IAMStreamConfig, (void**)&config)))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Could not get IAMStreamConfig for device pin, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
|
|
renderCX = renderCY = newCX = newCY = 0;
|
|
frameInterval = 0;
|
|
|
|
IElgatoVideoCaptureFilter6* elgatoFilterInterface6 = nullptr; // FMB NOTE: IElgatoVideoCaptureFilter6 available since EGC v.2.20
|
|
if (SUCCEEDED(deviceFilter->QueryInterface(IID_IElgatoVideoCaptureFilter6, (void**)&elgatoFilterInterface6)))
|
|
elgatoFilterInterface6->Release();
|
|
bool elgatoSupportsIAMStreamConfig = (nullptr != elgatoFilterInterface6);
|
|
bool elgatoCanRenderFromPin = (nullptr != elgatoFilterInterface6);
|
|
|
|
UINT elgatoCX = 1280;
|
|
UINT elgatoCY = 720;
|
|
|
|
if(bUseCustomResolution)
|
|
{
|
|
renderCX = newCX = data->GetInt(TEXT("resolutionWidth"));
|
|
renderCY = newCY = data->GetInt(TEXT("resolutionHeight"));
|
|
frameInterval = data->GetInt(TEXT("frameInterval"));
|
|
}
|
|
else
|
|
{
|
|
SIZE size;
|
|
size.cx = 0;
|
|
size.cy = 0;
|
|
|
|
// blackmagic/decklink devices will display a black screen if the resolution/fps doesn't exactly match.
|
|
// they should rename the devices to blackscreen
|
|
if (sstri(strDeviceName, L"blackmagic") != NULL || sstri(strDeviceName, L"decklink") != NULL ||
|
|
!GetClosestResolutionFPS(outputList, size, frameInterval, true))
|
|
{
|
|
AM_MEDIA_TYPE *pmt;
|
|
if (SUCCEEDED(config->GetFormat(&pmt))) {
|
|
VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat);
|
|
|
|
// Use "preferred" format from the device
|
|
size.cx = pVih->bmiHeader.biWidth;
|
|
size.cy = pVih->bmiHeader.biHeight;
|
|
frameInterval = pVih->AvgTimePerFrame;
|
|
|
|
DeleteMediaType(pmt);
|
|
} else {
|
|
if (!outputList.Num()) {
|
|
AppWarning(L"DShowPlugin: Not even an output list! What the f***");
|
|
goto cleanFinish;
|
|
}
|
|
|
|
/* ..........elgato */
|
|
_ASSERTE(elgato &&!elgatoSupportsIAMStreamConfig);
|
|
size.cx = outputList[0].maxCX;
|
|
size.cy = outputList[0].maxCY;
|
|
frameInterval = outputList[0].minFrameInterval;
|
|
}
|
|
}
|
|
|
|
renderCX = newCX = size.cx;
|
|
renderCY = newCY = size.cy;
|
|
}
|
|
|
|
|
|
/* elgato always starts off at 720p and changes after. */
|
|
if (elgato && !elgatoSupportsIAMStreamConfig)
|
|
{
|
|
elgatoCX = renderCX;
|
|
elgatoCY = renderCY;
|
|
|
|
renderCX = newCX = 1280;
|
|
renderCY = newCY = 720;
|
|
}
|
|
|
|
if(!renderCX || !renderCY || !frameInterval)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Invalid size/fps specified"));
|
|
goto cleanFinish;
|
|
}
|
|
|
|
preferredOutputType = (data->GetInt(TEXT("usePreferredType")) != 0) ? data->GetInt(TEXT("preferredType")) : -1;
|
|
|
|
bFirstFrame = true;
|
|
|
|
//------------------------------------------------
|
|
// get the closest media output for the settings used
|
|
|
|
MediaOutputInfo *bestOutput = GetBestMediaOutput(outputList, renderCX, renderCY, preferredOutputType, frameInterval);
|
|
if(!bestOutput)
|
|
{
|
|
if (!outputList.Num()) {
|
|
AppWarning(TEXT("DShowPlugin: Could not find appropriate resolution to create device image source"));
|
|
goto cleanFinish;
|
|
} else { /* エルガット=自殺 */
|
|
bestOutput = &outputList[0];
|
|
renderCX = newCX = bestOutput->minCX;
|
|
renderCY = newCY = bestOutput->minCY;
|
|
frameInterval = bestOutput->minFrameInterval;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// log video info
|
|
|
|
{
|
|
String strTest = FormattedString(TEXT(" device: %s,\r\n device id %s,\r\n chosen type: %s, usingFourCC: %s, res: %ux%u - %ux%u, frameIntervals: %llu-%llu\r\n use buffering: %s - %u"),
|
|
strDevice.Array(), strDeviceID.Array(),
|
|
EnumToName[(int)bestOutput->videoType],
|
|
bestOutput->bUsingFourCC ? TEXT("true") : TEXT("false"),
|
|
bestOutput->minCX, bestOutput->minCY, bestOutput->maxCX, bestOutput->maxCY,
|
|
bestOutput->minFrameInterval, bestOutput->maxFrameInterval,
|
|
bUseBuffering ? L"true" : L"false", bufferTime);
|
|
|
|
BITMAPINFOHEADER *bmiHeader = GetVideoBMIHeader(bestOutput->mediaType);
|
|
|
|
char fourcc[5];
|
|
mcpy(fourcc, &bmiHeader->biCompression, 4);
|
|
fourcc[4] = 0;
|
|
|
|
if(bmiHeader->biCompression > 1000)
|
|
strTest << FormattedString(TEXT(", fourCC: '%S'\r\n"), fourcc);
|
|
else
|
|
strTest << FormattedString(TEXT(", fourCC: %08lX\r\n"), bmiHeader->biCompression);
|
|
|
|
if(!bDeviceHasAudio) strTest << FormattedString(TEXT(" audio device: %s,\r\n audio device id %s,\r\n audio time offset %d,\r\n"), strAudioDevice.Array(), strAudioID.Array(), soundTimeOffset);
|
|
|
|
Log(TEXT("------------------------------------------"));
|
|
Log(strTest.Array());
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// set up shaders and video output data
|
|
|
|
expectedMediaType = bestOutput->mediaType->subtype;
|
|
|
|
colorType = DeviceOutputType_RGB;
|
|
if(bestOutput->videoType == VideoOutputType_I420)
|
|
colorType = DeviceOutputType_I420;
|
|
else if(bestOutput->videoType == VideoOutputType_YV12)
|
|
colorType = DeviceOutputType_YV12;
|
|
else if(bestOutput->videoType == VideoOutputType_YVYU)
|
|
colorType = DeviceOutputType_YVYU;
|
|
else if(bestOutput->videoType == VideoOutputType_YUY2)
|
|
colorType = DeviceOutputType_YUY2;
|
|
else if(bestOutput->videoType == VideoOutputType_UYVY)
|
|
colorType = DeviceOutputType_UYVY;
|
|
else if(bestOutput->videoType == VideoOutputType_HDYC)
|
|
{
|
|
colorType = DeviceOutputType_HDYC;
|
|
use709 = true;
|
|
}
|
|
else
|
|
{
|
|
colorType = DeviceOutputType_RGB;
|
|
expectedMediaType = MEDIASUBTYPE_RGB32;
|
|
}
|
|
|
|
strShader = ChooseShader();
|
|
if(strShader.IsValid())
|
|
colorConvertShader = CreatePixelShaderFromFile(strShader);
|
|
|
|
if(colorType != DeviceOutputType_RGB && !colorConvertShader)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Could not create color space conversion pixel shader"));
|
|
goto cleanFinish;
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// set chroma details
|
|
|
|
keyBaseColor = Color4().MakeFromRGBA(keyColor);
|
|
Matrix4x4TransformVect(keyChroma, (colorType == DeviceOutputType_HDYC || colorType == DeviceOutputType_RGB) ? (float*)yuv709Mat : (float*)yuvMat, keyBaseColor);
|
|
keyChroma *= 2.0f;
|
|
|
|
//------------------------------------------------
|
|
// configure video pin
|
|
|
|
AM_MEDIA_TYPE outputMediaType;
|
|
CopyMediaType(&outputMediaType, bestOutput->mediaType);
|
|
|
|
VIDEOINFOHEADER *vih = reinterpret_cast<VIDEOINFOHEADER*>(outputMediaType.pbFormat);
|
|
BITMAPINFOHEADER *bmi = GetVideoBMIHeader(&outputMediaType);
|
|
vih->AvgTimePerFrame = frameInterval;
|
|
bmi->biWidth = renderCX;
|
|
bmi->biHeight = renderCY;
|
|
bmi->biSizeImage = renderCX*renderCY*(bmi->biBitCount>>3);
|
|
|
|
if(FAILED(err = config->SetFormat(&outputMediaType)))
|
|
{
|
|
if(err != E_NOTIMPL)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: SetFormat on device pin failed, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
}
|
|
|
|
FreeMediaType(outputMediaType);
|
|
|
|
//------------------------------------------------
|
|
// get audio pin configuration, optionally configure audio pin to 44100
|
|
|
|
GUID expectedAudioType;
|
|
|
|
if(soundOutputType == 1)
|
|
{
|
|
IAMStreamConfig *audioConfig;
|
|
if(SUCCEEDED(audioPin->QueryInterface(IID_IAMStreamConfig, (void**)&audioConfig)))
|
|
{
|
|
AM_MEDIA_TYPE *audioMediaType;
|
|
if(SUCCEEDED(err = audioConfig->GetFormat(&audioMediaType)))
|
|
{
|
|
SetAudioInfo(audioMediaType, expectedAudioType);
|
|
}
|
|
else if(err == E_NOTIMPL) //elgato probably
|
|
{
|
|
IEnumMediaTypes *audioMediaTypes;
|
|
if(SUCCEEDED(err = audioPin->EnumMediaTypes(&audioMediaTypes)))
|
|
{
|
|
ULONG i = 0;
|
|
if((err = audioMediaTypes->Next(1, &audioMediaType, &i)) == S_OK)
|
|
SetAudioInfo(audioMediaType, expectedAudioType);
|
|
else
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: audioMediaTypes->Next failed, result = %08lX"), err);
|
|
soundOutputType = 0;
|
|
}
|
|
|
|
audioMediaTypes->Release();
|
|
}
|
|
else
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: audioMediaTypes->Next failed, result = %08lX"), err);
|
|
soundOutputType = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Could not get audio format, result = %08lX"), err);
|
|
soundOutputType = 0;
|
|
}
|
|
|
|
audioConfig->Release();
|
|
}
|
|
else {
|
|
soundOutputType = 0;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// add video capture filter if any
|
|
|
|
captureFilter = new CaptureFilter(this, MEDIATYPE_Video, expectedMediaType);
|
|
|
|
if(FAILED(err = graph->AddFilter(captureFilter, NULL)))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to add video capture filter to graph, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
|
|
bAddedVideoCapture = true;
|
|
|
|
//------------------------------------------------
|
|
// add audio capture filter if any
|
|
|
|
if(soundOutputType == 1)
|
|
{
|
|
audioFilter = new CaptureFilter(this, MEDIATYPE_Audio, expectedAudioType);
|
|
if(!audioFilter)
|
|
{
|
|
AppWarning(TEXT("Failed to create audio capture filter"));
|
|
soundOutputType = 0;
|
|
}
|
|
}
|
|
else if(soundOutputType == 2)
|
|
{
|
|
if(bUseAudioRender) {
|
|
if(FAILED(err = CoCreateInstance(CLSID_AudioRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&audioFilter)))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: failed to create WaveOut Audio renderer, result = %08lX"), err);
|
|
soundOutputType = 0;
|
|
}
|
|
}
|
|
else {
|
|
if(FAILED(err = CoCreateInstance(CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&audioFilter)))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: failed to create DirectSound renderer, result = %08lX"), err);
|
|
soundOutputType = 0;
|
|
}
|
|
}
|
|
|
|
IBasicAudio *basicAudio;
|
|
if(SUCCEEDED(audioFilter->QueryInterface(IID_IBasicAudio, (void**)&basicAudio)))
|
|
{
|
|
long lVol = long((double(volume)*NEAR_SILENTf)-NEAR_SILENTf);
|
|
if(lVol <= -NEAR_SILENT)
|
|
lVol = -10000;
|
|
basicAudio->put_Volume(lVol);
|
|
basicAudio->Release();
|
|
}
|
|
}
|
|
|
|
if(soundOutputType != 0)
|
|
{
|
|
if(FAILED(err = graph->AddFilter(audioFilter, NULL)))
|
|
AppWarning(TEXT("DShowPlugin: Failed to add audio capture filter to graph, result = %08lX"), err);
|
|
|
|
bAddedAudioCapture = true;
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// add primary device filter
|
|
|
|
if(FAILED(err = graph->AddFilter(deviceFilter, NULL)))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to add device filter to graph, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
|
|
if(soundOutputType != 0 && !bDeviceHasAudio)
|
|
{
|
|
if(FAILED(err = graph->AddFilter(audioDeviceFilter, NULL)))
|
|
AppWarning(TEXT("DShowPlugin: Failed to add audio device filter to graph, result = %08lX"), err);
|
|
}
|
|
|
|
bAddedDevice = true;
|
|
|
|
//------------------------------------------------
|
|
// change elgato resolution
|
|
if (elgato && !elgatoSupportsIAMStreamConfig)
|
|
{
|
|
/* choose closest matching elgato resolution */
|
|
if (!bUseCustomResolution)
|
|
{
|
|
UINT baseCX, baseCY;
|
|
UINT closest = 0xFFFFFFFF;
|
|
API->GetBaseSize(baseCX, baseCY);
|
|
|
|
const ResSize resolutions[] = {{480, 360}, {640, 480}, {1280, 720}, {1920, 1080}};
|
|
for (const ResSize &res : resolutions) {
|
|
UINT val = (UINT)labs((long)res.cy - (long)baseCY);
|
|
if (val < closest) {
|
|
elgatoCX = res.cx;
|
|
elgatoCY = res.cy;
|
|
closest = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
IElgatoVideoCaptureFilter3 *elgatoFilter = nullptr;
|
|
|
|
if (SUCCEEDED(deviceFilter->QueryInterface(IID_IElgatoVideoCaptureFilter3, (void**)&elgatoFilter)))
|
|
{
|
|
VIDEO_CAPTURE_FILTER_SETTINGS settings;
|
|
if (SUCCEEDED(elgatoFilter->GetSettings(&settings)))
|
|
{
|
|
if (elgatoCY == 1080)
|
|
settings.profile = VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_1080;
|
|
else if (elgatoCY == 480)
|
|
settings.profile = VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_480;
|
|
else if (elgatoCY == 360)
|
|
settings.profile = VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_360;
|
|
else
|
|
settings.profile = VIDEO_CAPTURE_FILTER_VID_ENC_PROFILE_720;
|
|
|
|
elgatoFilter->SetSettings(&settings);
|
|
}
|
|
|
|
elgatoFilter->Release();
|
|
}
|
|
}
|
|
|
|
#if ELGATO_FORCE_BUFFERING
|
|
if (elgato)
|
|
ElgatoCheckBuffering(deviceFilter, bUseBuffering, bufferTime);
|
|
#endif
|
|
|
|
lastSampleCX = renderCX;
|
|
lastSampleCY = renderCY;
|
|
|
|
//------------------------------------------------
|
|
// connect all pins and set up the whole capture thing
|
|
|
|
bool bConnected = false;
|
|
if (elgato && !elgatoCanRenderFromPin)
|
|
{
|
|
bConnected = SUCCEEDED(err = graph->ConnectDirect(devicePin, captureFilter->GetCapturePin(), nullptr));
|
|
if (!bConnected)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to connect the video device pin to the video capture pin, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bConnected = SUCCEEDED(err = capture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, deviceFilter, NULL, captureFilter));
|
|
if(!bConnected)
|
|
{
|
|
if(FAILED(err = graph->Connect(devicePin, captureFilter->GetCapturePin())))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to connect the video device pin to the video capture pin, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(soundOutputType != 0)
|
|
{
|
|
if (elgato && bDeviceHasAudio && !elgatoCanRenderFromPin)
|
|
{
|
|
bConnected = false;
|
|
|
|
IPin *audioPin = GetOutputPin(deviceFilter, &MEDIATYPE_Audio);
|
|
if (audioPin)
|
|
{
|
|
IPin* audioRendererPin = NULL;
|
|
|
|
// FMB NOTE: Connect with first (= the only) pin of audio renderer
|
|
IEnumPins* pIEnum = NULL;
|
|
if (SUCCEEDED(err = audioFilter->EnumPins(&pIEnum)))
|
|
{
|
|
IPin* pIPin = NULL;
|
|
pIEnum->Next(1, &audioRendererPin, NULL);
|
|
SafeRelease(pIEnum);
|
|
}
|
|
|
|
if (audioRendererPin)
|
|
{
|
|
bConnected = SUCCEEDED(err = graph->ConnectDirect(audioPin, audioRendererPin, nullptr));
|
|
audioRendererPin->Release();
|
|
}
|
|
audioPin->Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!bDeviceHasAudio)
|
|
bConnected = SUCCEEDED(err = capture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, audioDeviceFilter, NULL, audioFilter));
|
|
else
|
|
bConnected = SUCCEEDED(err = capture->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, deviceFilter, NULL, audioFilter));
|
|
}
|
|
|
|
if(!bConnected)
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to connect the audio device pin to the audio capture pin, result = %08lX"), err);
|
|
soundOutputType = 0;
|
|
}
|
|
}
|
|
|
|
if(FAILED(err = graph->QueryInterface(IID_IMediaControl, (void**)&control)))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: Failed to get IMediaControl, result = %08lX"), err);
|
|
goto cleanFinish;
|
|
}
|
|
|
|
if (bUseBuffering) {
|
|
if (!(hStopSampleEvent = CreateEvent(NULL, FALSE, FALSE, NULL))) {
|
|
AppWarning(TEXT("DShowPlugin: Failed to create stop event"), err);
|
|
goto cleanFinish;
|
|
}
|
|
|
|
if (!(hSampleThread = OSCreateThread((XTHREAD)SampleThread, this))) {
|
|
AppWarning(TEXT("DShowPlugin: Failed to create sample thread"), err);
|
|
goto cleanFinish;
|
|
}
|
|
}
|
|
|
|
if(soundOutputType == 1)
|
|
{
|
|
audioOut = new DeviceAudioSource;
|
|
audioOut->Initialize(this);
|
|
API->AddAudioSource(audioOut);
|
|
|
|
audioOut->SetAudioOffset(soundTimeOffset);
|
|
audioOut->SetVolume(volume);
|
|
}
|
|
|
|
bSucceeded = true;
|
|
|
|
cleanFinish:
|
|
SafeRelease(config);
|
|
SafeRelease(devicePin);
|
|
SafeRelease(audioPin);
|
|
|
|
for(UINT i=0; i<outputList.Num(); i++)
|
|
outputList[i].FreeData();
|
|
|
|
if(!bSucceeded)
|
|
{
|
|
bCapturing = false;
|
|
|
|
if(bAddedVideoCapture)
|
|
graph->RemoveFilter(captureFilter);
|
|
if(bAddedAudioCapture)
|
|
graph->RemoveFilter(audioFilter);
|
|
|
|
if(bAddedDevice)
|
|
{
|
|
if(!bDeviceHasAudio && audioDeviceFilter)
|
|
graph->RemoveFilter(audioDeviceFilter);
|
|
graph->RemoveFilter(deviceFilter);
|
|
}
|
|
|
|
SafeRelease(audioDeviceFilter);
|
|
SafeRelease(deviceFilter);
|
|
SafeRelease(captureFilter);
|
|
SafeRelease(audioFilter);
|
|
SafeRelease(control);
|
|
|
|
if (hSampleThread) {
|
|
SetEvent(hStopSampleEvent);
|
|
WaitForSingleObject(hSampleThread, INFINITE);
|
|
CloseHandle(hSampleThread);
|
|
hSampleThread = NULL;
|
|
}
|
|
|
|
if (hStopSampleEvent) {
|
|
CloseHandle(hStopSampleEvent);
|
|
hStopSampleEvent = NULL;
|
|
}
|
|
|
|
if(colorConvertShader)
|
|
{
|
|
delete colorConvertShader;
|
|
colorConvertShader = NULL;
|
|
}
|
|
|
|
if(audioOut)
|
|
{
|
|
delete audioOut;
|
|
audioOut = NULL;
|
|
}
|
|
|
|
soundOutputType = 0;
|
|
|
|
if(lpImageBuffer)
|
|
{
|
|
Free(lpImageBuffer);
|
|
lpImageBuffer = NULL;
|
|
}
|
|
|
|
bReadyToDraw = true;
|
|
}
|
|
else
|
|
bReadyToDraw = false;
|
|
|
|
// Updated check to ensure that the source actually turns red instead of
|
|
// screwing up the size when SetFormat fails.
|
|
if (renderCX <= 0 || renderCX >= 8192) { newCX = renderCX = 32; imageCX = renderCX; }
|
|
if (renderCY <= 0 || renderCY >= 8192) { newCY = renderCY = 32; imageCY = renderCY; }
|
|
|
|
ChangeSize(bSucceeded, true);
|
|
|
|
return bSucceeded;
|
|
}
|
|
|
|
void DeviceSource::UnloadFilters()
|
|
{
|
|
if (hSampleThread) {
|
|
SetEvent(hStopSampleEvent);
|
|
WaitForSingleObject(hSampleThread, INFINITE);
|
|
CloseHandle(hSampleThread);
|
|
CloseHandle(hStopSampleEvent);
|
|
|
|
hSampleThread = NULL;
|
|
hStopSampleEvent = NULL;
|
|
}
|
|
|
|
if(texture)
|
|
{
|
|
delete texture;
|
|
texture = NULL;
|
|
}
|
|
if(previousTexture)
|
|
{
|
|
delete previousTexture;
|
|
previousTexture = NULL;
|
|
}
|
|
|
|
KillThreads();
|
|
|
|
if(bFiltersLoaded)
|
|
{
|
|
graph->RemoveFilter(captureFilter);
|
|
graph->RemoveFilter(deviceFilter);
|
|
if(!bDeviceHasAudio) graph->RemoveFilter(audioDeviceFilter);
|
|
|
|
if(audioFilter)
|
|
graph->RemoveFilter(audioFilter);
|
|
|
|
SafeReleaseLogRef(captureFilter);
|
|
SafeReleaseLogRef(deviceFilter);
|
|
SafeReleaseLogRef(audioDeviceFilter);
|
|
SafeReleaseLogRef(audioFilter);
|
|
|
|
bFiltersLoaded = false;
|
|
}
|
|
|
|
if(audioOut)
|
|
{
|
|
API->RemoveAudioSource(audioOut);
|
|
delete audioOut;
|
|
audioOut = NULL;
|
|
}
|
|
|
|
if(colorConvertShader)
|
|
{
|
|
delete colorConvertShader;
|
|
colorConvertShader = NULL;
|
|
}
|
|
|
|
if(lpImageBuffer)
|
|
{
|
|
Free(lpImageBuffer);
|
|
lpImageBuffer = NULL;
|
|
}
|
|
|
|
SafeReleaseLogRef(capture);
|
|
SafeReleaseLogRef(graph);
|
|
|
|
SafeRelease(control);
|
|
}
|
|
|
|
void DeviceSource::Start()
|
|
{
|
|
if(bCapturing || !control)
|
|
return;
|
|
|
|
drawShader = CreatePixelShaderFromFile(TEXT("shaders\\DrawTexture_ColorAdjust.pShader"));
|
|
|
|
HRESULT err;
|
|
if(FAILED(err = control->Run()))
|
|
{
|
|
AppWarning(TEXT("DShowPlugin: control->Run failed, result = %08lX"), err);
|
|
return;
|
|
}
|
|
|
|
/*if (err == S_FALSE)
|
|
AppWarning(L"Ook");*/
|
|
|
|
bCapturing = true;
|
|
}
|
|
|
|
void DeviceSource::Stop()
|
|
{
|
|
delete drawShader;
|
|
drawShader = NULL;
|
|
|
|
if(!bCapturing)
|
|
return;
|
|
|
|
bCapturing = false;
|
|
control->Stop();
|
|
FlushSamples();
|
|
}
|
|
|
|
void DeviceSource::BeginScene()
|
|
{
|
|
Start();
|
|
}
|
|
|
|
void DeviceSource::EndScene()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
void DeviceSource::GlobalSourceLeaveScene()
|
|
{
|
|
if (!enteredSceneCount)
|
|
return;
|
|
if (--enteredSceneCount)
|
|
return;
|
|
|
|
if(soundOutputType == 1) {
|
|
audioOut->SetVolume(0.0f);
|
|
}
|
|
if(soundOutputType == 2) {
|
|
IBasicAudio *basicAudio;
|
|
if(SUCCEEDED(audioFilter->QueryInterface(IID_IBasicAudio, (void**)&basicAudio)))
|
|
{
|
|
long lVol = long((double(0.0)*NEAR_SILENTf)-NEAR_SILENTf);
|
|
if(lVol <= -NEAR_SILENT)
|
|
lVol = -10000;
|
|
basicAudio->put_Volume(lVol);
|
|
basicAudio->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeviceSource::GlobalSourceEnterScene()
|
|
{
|
|
if (enteredSceneCount++)
|
|
return;
|
|
|
|
float sourceVolume = data->GetFloat(TEXT("volume"), 1.0f);
|
|
|
|
if(soundOutputType == 1) {
|
|
audioOut->SetVolume(sourceVolume);
|
|
}
|
|
if(soundOutputType == 2) {
|
|
IBasicAudio *basicAudio;
|
|
if(SUCCEEDED(audioFilter->QueryInterface(IID_IBasicAudio, (void**)&basicAudio)))
|
|
{
|
|
long lVol = long((double(sourceVolume)*NEAR_SILENTf)-NEAR_SILENTf);
|
|
if(lVol <= -NEAR_SILENT)
|
|
lVol = -10000;
|
|
basicAudio->put_Volume(lVol);
|
|
basicAudio->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD DeviceSource::SampleThread(DeviceSource *source)
|
|
{
|
|
HANDLE hSampleMutex = source->hSampleMutex;
|
|
LONGLONG lastTime = GetQPCTime100NS(), bufferTime = 0, frameWait = 0, curBufferTime = source->bufferTime;
|
|
LONGLONG lastSampleTime = 0;
|
|
|
|
bool bFirstFrame = true;
|
|
bool bFirstDelay = true;
|
|
|
|
while (WaitForSingleObject(source->hStopSampleEvent, 2) == WAIT_TIMEOUT) {
|
|
LONGLONG t = GetQPCTime100NS();
|
|
LONGLONG delta = t-lastTime;
|
|
lastTime = t;
|
|
|
|
OSEnterMutex(hSampleMutex);
|
|
|
|
if (source->samples.Num()) {
|
|
if (bFirstFrame) {
|
|
bFirstFrame = false;
|
|
lastSampleTime = source->samples[0]->timestamp;
|
|
}
|
|
|
|
//wait until the requested delay has been buffered before processing packets
|
|
if (bufferTime >= source->bufferTime) {
|
|
frameWait += delta;
|
|
|
|
//if delay time was adjusted downward, remove packets accordingly
|
|
bool bBufferTimeChanged = (curBufferTime != source->bufferTime);
|
|
if (bBufferTimeChanged) {
|
|
if (curBufferTime > source->bufferTime) {
|
|
if (source->audioOut)
|
|
source->audioOut->FlushSamples();
|
|
|
|
LONGLONG lostTime = curBufferTime - source->bufferTime;
|
|
bufferTime -= lostTime;
|
|
|
|
if (source->samples.Num()) {
|
|
LONGLONG startTime = source->samples[0]->timestamp;
|
|
|
|
while (source->samples.Num()) {
|
|
SampleData *sample = source->samples[0];
|
|
|
|
if ((sample->timestamp - startTime) >= lostTime)
|
|
break;
|
|
|
|
lastSampleTime = sample->timestamp;
|
|
|
|
sample->Release();
|
|
source->samples.Remove(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
curBufferTime = source->bufferTime;
|
|
}
|
|
|
|
while (source->samples.Num()) {
|
|
SampleData *sample = source->samples[0];
|
|
|
|
LONGLONG timestamp = sample->timestamp;
|
|
LONGLONG sampleTime = timestamp - lastSampleTime;
|
|
|
|
//sometimes timestamps can go to shit with horrible garbage devices.
|
|
//so, bypass any unusual timestamp offsets.
|
|
if (sampleTime < -10000000 || sampleTime > 10000000) {
|
|
//OSDebugOut(TEXT("sample time: %lld\r\n"), sampleTime);
|
|
sampleTime = 0;
|
|
}
|
|
|
|
if (frameWait < sampleTime)
|
|
break;
|
|
|
|
if (sample->bAudio) {
|
|
if (source->audioOut)
|
|
source->audioOut->ReceiveAudio(sample->lpData, sample->dataLength);
|
|
|
|
sample->Release();
|
|
} else {
|
|
SafeRelease(source->latestVideoSample);
|
|
source->latestVideoSample = sample;
|
|
}
|
|
|
|
source->samples.Remove(0);
|
|
|
|
if (sampleTime > 0)
|
|
frameWait -= sampleTime;
|
|
|
|
lastSampleTime = timestamp;
|
|
}
|
|
}
|
|
}
|
|
|
|
OSLeaveMutex(hSampleMutex);
|
|
|
|
if (!bFirstFrame && bufferTime < source->bufferTime)
|
|
bufferTime += delta;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
UINT DeviceSource::GetSampleInsertIndex(LONGLONG timestamp)
|
|
{
|
|
UINT index;
|
|
for (index=0; index<samples.Num(); index++) {
|
|
if (samples[index]->timestamp > timestamp)
|
|
return index;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
void DeviceSource::KillThreads()
|
|
{
|
|
int numThreads = MAX(OSGetTotalCores()-2, 1);
|
|
for(int i=0; i<numThreads; i++)
|
|
{
|
|
if(hConvertThreads[i])
|
|
{
|
|
convertData[i].bKillThread = true;
|
|
SetEvent(convertData[i].hSignalConvert);
|
|
|
|
OSTerminateThread(hConvertThreads[i], 10000);
|
|
hConvertThreads[i] = NULL;
|
|
}
|
|
|
|
convertData[i].bKillThread = false;
|
|
|
|
if(convertData[i].hSignalConvert)
|
|
{
|
|
CloseHandle(convertData[i].hSignalConvert);
|
|
convertData[i].hSignalConvert = NULL;
|
|
}
|
|
|
|
if(convertData[i].hSignalComplete)
|
|
{
|
|
CloseHandle(convertData[i].hSignalComplete);
|
|
convertData[i].hSignalComplete = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeviceSource::ChangeSize(bool bSucceeded, bool bForce)
|
|
{
|
|
if (!bForce && renderCX == newCX && renderCY == newCY)
|
|
return;
|
|
|
|
renderCX = newCX;
|
|
renderCY = newCY;
|
|
|
|
switch(colorType) {
|
|
case DeviceOutputType_RGB:
|
|
lineSize = renderCX * 4;
|
|
break;
|
|
case DeviceOutputType_I420:
|
|
case DeviceOutputType_YV12:
|
|
lineSize = renderCX; //per plane
|
|
break;
|
|
case DeviceOutputType_YVYU:
|
|
case DeviceOutputType_YUY2:
|
|
case DeviceOutputType_UYVY:
|
|
case DeviceOutputType_HDYC:
|
|
lineSize = (renderCX * 2);
|
|
break;
|
|
}
|
|
|
|
linePitch = lineSize;
|
|
lineShift = 0;
|
|
imageCX = renderCX;
|
|
imageCY = renderCY;
|
|
|
|
deinterlacer.imageCX = renderCX;
|
|
deinterlacer.imageCY = renderCY;
|
|
|
|
if(deinterlacer.doublesFramerate)
|
|
deinterlacer.imageCX *= 2;
|
|
|
|
switch(deinterlacer.type) {
|
|
case DEINTERLACING_DISCARD:
|
|
deinterlacer.imageCY = renderCY/2;
|
|
linePitch = lineSize * 2;
|
|
renderCY /= 2;
|
|
break;
|
|
|
|
case DEINTERLACING_RETRO:
|
|
deinterlacer.imageCY = renderCY/2;
|
|
if(deinterlacer.processor != DEINTERLACING_PROCESSOR_GPU)
|
|
{
|
|
lineSize *= 2;
|
|
linePitch = lineSize;
|
|
renderCY /= 2;
|
|
renderCX *= 2;
|
|
}
|
|
break;
|
|
|
|
case DEINTERLACING__DEBUG:
|
|
deinterlacer.imageCX *= 2;
|
|
deinterlacer.imageCY *= 2;
|
|
case DEINTERLACING_BLEND2x:
|
|
//case DEINTERLACING_MEAN2x:
|
|
case DEINTERLACING_YADIF:
|
|
case DEINTERLACING_YADIF2x:
|
|
deinterlacer.needsPreviousFrame = true;
|
|
break;
|
|
}
|
|
|
|
if(deinterlacer.type != DEINTERLACING_NONE && deinterlacer.processor == DEINTERLACING_PROCESSOR_GPU)
|
|
{
|
|
deinterlacer.vertexShader.reset(CreateVertexShaderFromFile(TEXT("shaders/DrawTexture.vShader")));
|
|
deinterlacer.pixelShader = CreatePixelShaderFromFileAsync(ChooseDeinterlacingShader());
|
|
deinterlacer.isReady = false;
|
|
}
|
|
|
|
KillThreads();
|
|
|
|
int numThreads = MAX(OSGetTotalCores()-2, 1);
|
|
for(int i=0; i<numThreads; i++)
|
|
{
|
|
convertData[i].width = lineSize;
|
|
convertData[i].height = renderCY;
|
|
convertData[i].sample = NULL;
|
|
convertData[i].hSignalConvert = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
convertData[i].hSignalComplete = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
convertData[i].linePitch = linePitch;
|
|
convertData[i].lineShift = lineShift;
|
|
|
|
if(i == 0)
|
|
convertData[i].startY = 0;
|
|
else
|
|
convertData[i].startY = convertData[i-1].endY;
|
|
|
|
if(i == (numThreads-1))
|
|
convertData[i].endY = renderCY;
|
|
else
|
|
convertData[i].endY = ((renderCY/numThreads)*(i+1)) & 0xFFFFFFFE;
|
|
}
|
|
|
|
if(colorType == DeviceOutputType_YV12 || colorType == DeviceOutputType_I420)
|
|
{
|
|
for(int i=0; i<numThreads; i++)
|
|
hConvertThreads[i] = OSCreateThread((XTHREAD)PackPlanarThread, convertData+i);
|
|
}
|
|
|
|
if(texture)
|
|
{
|
|
delete texture;
|
|
texture = NULL;
|
|
}
|
|
if(previousTexture)
|
|
{
|
|
delete previousTexture;
|
|
previousTexture = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------
|
|
// create the texture regardless, will just show up as red to indicate failure
|
|
BYTE *textureData = (BYTE*)Allocate(renderCX*renderCY*4);
|
|
|
|
if(colorType == DeviceOutputType_RGB) //you may be confused, but when directshow outputs RGB, it's actually outputting BGR
|
|
{
|
|
msetd(textureData, 0xFFFF0000, renderCX*renderCY*4);
|
|
texture = CreateTexture(renderCX, renderCY, GS_BGR, textureData, FALSE, FALSE);
|
|
if(bSucceeded && deinterlacer.needsPreviousFrame)
|
|
previousTexture = CreateTexture(renderCX, renderCY, GS_BGR, textureData, FALSE, FALSE);
|
|
if(bSucceeded && deinterlacer.processor == DEINTERLACING_PROCESSOR_GPU)
|
|
deinterlacer.texture.reset(CreateRenderTarget(deinterlacer.imageCX, deinterlacer.imageCY, GS_BGRA, FALSE));
|
|
}
|
|
else //if we're working with planar YUV, we can just use regular RGB textures instead
|
|
{
|
|
msetd(textureData, 0xFF0000FF, renderCX*renderCY*4);
|
|
texture = CreateTexture(renderCX, renderCY, GS_RGB, textureData, FALSE, FALSE);
|
|
if(bSucceeded && deinterlacer.needsPreviousFrame)
|
|
previousTexture = CreateTexture(renderCX, renderCY, GS_RGB, textureData, FALSE, FALSE);
|
|
if(bSucceeded && deinterlacer.processor == DEINTERLACING_PROCESSOR_GPU)
|
|
deinterlacer.texture.reset(CreateRenderTarget(deinterlacer.imageCX, deinterlacer.imageCY, GS_BGRA, FALSE));
|
|
}
|
|
|
|
if(bSucceeded && bUseThreadedConversion)
|
|
{
|
|
if(colorType == DeviceOutputType_I420 || colorType == DeviceOutputType_YV12)
|
|
{
|
|
LPBYTE lpData;
|
|
if(texture->Map(lpData, texturePitch))
|
|
texture->Unmap();
|
|
else
|
|
texturePitch = renderCX*4;
|
|
|
|
lpImageBuffer = (LPBYTE)Allocate(texturePitch*renderCY);
|
|
}
|
|
}
|
|
|
|
Free(textureData);
|
|
|
|
bFiltersLoaded = bSucceeded;
|
|
}
|
|
|
|
void DeviceSource::ReceiveMediaSample(IMediaSample *sample, bool bAudio)
|
|
{
|
|
if (!sample)
|
|
return;
|
|
|
|
if (bCapturing) {
|
|
BYTE *pointer;
|
|
|
|
if (!sample->GetActualDataLength())
|
|
return;
|
|
|
|
if (SUCCEEDED(sample->GetPointer(&pointer))) {
|
|
SampleData *data = NULL;
|
|
AM_MEDIA_TYPE *mt = nullptr;
|
|
|
|
if (sample->GetMediaType(&mt) == S_OK)
|
|
{
|
|
BITMAPINFOHEADER *bih = GetVideoBMIHeader(mt);
|
|
lastSampleCX = bih->biWidth;
|
|
lastSampleCY = bih->biHeight;
|
|
DeleteMediaType(mt);
|
|
}
|
|
|
|
if (bUseBuffering || !bAudio) {
|
|
data = new SampleData;
|
|
data->bAudio = bAudio;
|
|
data->dataLength = sample->GetActualDataLength();
|
|
data->lpData = (LPBYTE)Allocate(data->dataLength);//pointer; //
|
|
data->cx = lastSampleCX;
|
|
data->cy = lastSampleCY;
|
|
/*data->sample = sample;
|
|
sample->AddRef();*/
|
|
|
|
memcpy(data->lpData, pointer, data->dataLength);
|
|
|
|
LONGLONG stopTime;
|
|
sample->GetTime(&stopTime, &data->timestamp);
|
|
}
|
|
|
|
//Log(TEXT("timestamp: %lld, bAudio - %s"), data->timestamp, bAudio ? TEXT("true") : TEXT("false"));
|
|
|
|
OSEnterMutex(hSampleMutex);
|
|
|
|
if (bUseBuffering) {
|
|
UINT id = GetSampleInsertIndex(data->timestamp);
|
|
samples.Insert(id, data);
|
|
} else if (bAudio) {
|
|
if (audioOut)
|
|
audioOut->ReceiveAudio(pointer, sample->GetActualDataLength());
|
|
} else {
|
|
SafeRelease(latestVideoSample);
|
|
latestVideoSample = data;
|
|
}
|
|
|
|
OSLeaveMutex(hSampleMutex);
|
|
}
|
|
}
|
|
}
|
|
|
|
static DWORD STDCALL PackPlanarThread(ConvertData *data)
|
|
{
|
|
do {
|
|
WaitForSingleObject(data->hSignalConvert, INFINITE);
|
|
if(data->bKillThread) break;
|
|
|
|
PackPlanar(data->output, data->input, data->width, data->height, data->pitch, data->startY, data->endY, data->linePitch, data->lineShift);
|
|
data->sample->Release();
|
|
|
|
SetEvent(data->hSignalComplete);
|
|
}while(!data->bKillThread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void DeviceSource::Preprocess()
|
|
{
|
|
if(!bCapturing)
|
|
return;
|
|
|
|
//----------------------------------------
|
|
|
|
if(bRequestVolume)
|
|
{
|
|
if(audioOut)
|
|
audioOut->SetVolume(fNewVol);
|
|
else if(audioFilter)
|
|
{
|
|
IBasicAudio *basicAudio;
|
|
if(SUCCEEDED(audioFilter->QueryInterface(IID_IBasicAudio, (void**)&basicAudio)))
|
|
{
|
|
long lVol = long((double(fNewVol)*NEAR_SILENTf)-NEAR_SILENTf);
|
|
if(lVol <= -NEAR_SILENT)
|
|
lVol = -10000;
|
|
basicAudio->put_Volume(lVol);
|
|
basicAudio->Release();
|
|
}
|
|
}
|
|
bRequestVolume = false;
|
|
}
|
|
|
|
//----------------------------------------
|
|
|
|
SampleData *lastSample = NULL;
|
|
|
|
OSEnterMutex(hSampleMutex);
|
|
|
|
lastSample = latestVideoSample;
|
|
latestVideoSample = NULL;
|
|
|
|
OSLeaveMutex(hSampleMutex);
|
|
|
|
//----------------------------------------
|
|
|
|
int numThreads = MAX(OSGetTotalCores()-2, 1);
|
|
|
|
if(lastSample)
|
|
{
|
|
newCX = lastSample->cx;
|
|
newCY = lastSample->cy;
|
|
/*REFERENCE_TIME refTimeStart, refTimeFinish;
|
|
lastSample->GetTime(&refTimeStart, &refTimeFinish);
|
|
|
|
static REFERENCE_TIME lastRefTime = 0;
|
|
Log(TEXT("refTimeStart: %llu, refTimeFinish: %llu, offset = %llu"), refTimeStart, refTimeFinish, refTimeStart-lastRefTime);
|
|
lastRefTime = refTimeStart;*/
|
|
|
|
if(previousTexture)
|
|
{
|
|
Texture *tmp = texture;
|
|
texture = previousTexture;
|
|
previousTexture = tmp;
|
|
}
|
|
deinterlacer.curField = deinterlacer.processor == DEINTERLACING_PROCESSOR_GPU ? false : (deinterlacer.fieldOrder == FIELD_ORDER_BFF);
|
|
deinterlacer.bNewFrame = true;
|
|
|
|
if(colorType == DeviceOutputType_RGB)
|
|
{
|
|
if(texture)
|
|
{
|
|
ChangeSize();
|
|
|
|
texture->SetImage(lastSample->lpData, GS_IMAGEFORMAT_BGRX, linePitch);
|
|
bReadyToDraw = true;
|
|
}
|
|
}
|
|
else if(colorType == DeviceOutputType_I420 || colorType == DeviceOutputType_YV12)
|
|
{
|
|
if(bUseThreadedConversion)
|
|
{
|
|
if(!bFirstFrame)
|
|
{
|
|
List<HANDLE> events;
|
|
for(int i=0; i<numThreads; i++)
|
|
events << convertData[i].hSignalComplete;
|
|
|
|
WaitForMultipleObjects(numThreads, events.Array(), TRUE, INFINITE);
|
|
texture->SetImage(lpImageBuffer, GS_IMAGEFORMAT_RGBX, texturePitch);
|
|
|
|
bReadyToDraw = true;
|
|
}
|
|
else
|
|
bFirstFrame = false;
|
|
|
|
ChangeSize();
|
|
|
|
for(int i=0; i<numThreads; i++)
|
|
lastSample->AddRef();
|
|
|
|
for(int i=0; i<numThreads; i++)
|
|
{
|
|
convertData[i].input = lastSample->lpData;
|
|
convertData[i].sample = lastSample;
|
|
convertData[i].pitch = texturePitch;
|
|
convertData[i].output = lpImageBuffer;
|
|
convertData[i].linePitch = linePitch;
|
|
convertData[i].lineShift = lineShift;
|
|
SetEvent(convertData[i].hSignalConvert);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LPBYTE lpData;
|
|
UINT pitch;
|
|
|
|
ChangeSize();
|
|
|
|
if(texture->Map(lpData, pitch))
|
|
{
|
|
PackPlanar(lpData, lastSample->lpData, renderCX, renderCY, pitch, 0, renderCY, linePitch, lineShift);
|
|
texture->Unmap();
|
|
}
|
|
|
|
bReadyToDraw = true;
|
|
}
|
|
}
|
|
else if(colorType == DeviceOutputType_YVYU || colorType == DeviceOutputType_YUY2)
|
|
{
|
|
LPBYTE lpData;
|
|
UINT pitch;
|
|
|
|
ChangeSize();
|
|
|
|
if(texture->Map(lpData, pitch))
|
|
{
|
|
Convert422To444(lpData, lastSample->lpData, pitch, true);
|
|
texture->Unmap();
|
|
}
|
|
|
|
bReadyToDraw = true;
|
|
}
|
|
else if(colorType == DeviceOutputType_UYVY || colorType == DeviceOutputType_HDYC)
|
|
{
|
|
LPBYTE lpData;
|
|
UINT pitch;
|
|
|
|
ChangeSize();
|
|
|
|
if(texture->Map(lpData, pitch))
|
|
{
|
|
Convert422To444(lpData, lastSample->lpData, pitch, false);
|
|
texture->Unmap();
|
|
}
|
|
|
|
bReadyToDraw = true;
|
|
}
|
|
|
|
lastSample->Release();
|
|
|
|
if (bReadyToDraw &&
|
|
deinterlacer.type != DEINTERLACING_NONE &&
|
|
deinterlacer.processor == DEINTERLACING_PROCESSOR_GPU &&
|
|
deinterlacer.texture.get() &&
|
|
deinterlacer.pixelShader.Shader())
|
|
{
|
|
SetRenderTarget(deinterlacer.texture.get());
|
|
|
|
Shader *oldVertShader = GetCurrentVertexShader();
|
|
LoadVertexShader(deinterlacer.vertexShader.get());
|
|
|
|
Shader *oldShader = GetCurrentPixelShader();
|
|
LoadPixelShader(deinterlacer.pixelShader.Shader());
|
|
|
|
HANDLE hField = deinterlacer.pixelShader.Shader()->GetParameterByName(TEXT("field_order"));
|
|
if(hField)
|
|
deinterlacer.pixelShader.Shader()->SetBool(hField, deinterlacer.fieldOrder == FIELD_ORDER_BFF);
|
|
|
|
Ortho(0.0f, float(deinterlacer.imageCX), float(deinterlacer.imageCY), 0.0f, -100.0f, 100.0f);
|
|
SetViewport(0.0f, 0.0f, float(deinterlacer.imageCX), float(deinterlacer.imageCY));
|
|
|
|
if(previousTexture)
|
|
LoadTexture(previousTexture, 1);
|
|
|
|
DrawSpriteEx(texture, 0xFFFFFFFF, 0.0f, 0.0f, float(deinterlacer.imageCX), float(deinterlacer.imageCY), 0.0f, 0.0f, 1.0f, 1.0f);
|
|
|
|
if(previousTexture)
|
|
LoadTexture(nullptr, 1);
|
|
|
|
LoadPixelShader(oldShader);
|
|
LoadVertexShader(oldVertShader);
|
|
deinterlacer.isReady = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeviceSource::Render(const Vect2 &pos, const Vect2 &size)
|
|
{
|
|
if(texture && bReadyToDraw && deinterlacer.isReady)
|
|
{
|
|
Shader *oldShader = GetCurrentPixelShader();
|
|
SamplerState *sampler = NULL;
|
|
|
|
gamma = data->GetInt(TEXT("gamma"), 100);
|
|
float fGamma = float(-(gamma-100) + 100) * 0.01f;
|
|
|
|
if(colorConvertShader)
|
|
{
|
|
LoadPixelShader(colorConvertShader);
|
|
if(bUseChromaKey)
|
|
{
|
|
float fSimilarity = float(keySimilarity)/1000.0f;
|
|
float fBlendVal = float(max(keyBlend, 1)/1000.0f);
|
|
float fSpillVal = (float(max(keySpillReduction, 1))/1000.0f);
|
|
|
|
Vect2 pixelSize = 1.0f/GetSize();
|
|
|
|
colorConvertShader->SetColor (colorConvertShader->GetParameterByName(TEXT("keyBaseColor")), Color4(keyBaseColor));
|
|
colorConvertShader->SetColor (colorConvertShader->GetParameterByName(TEXT("chromaKey")), Color4(keyChroma));
|
|
colorConvertShader->SetVector2(colorConvertShader->GetParameterByName(TEXT("pixelSize")), pixelSize);
|
|
colorConvertShader->SetFloat (colorConvertShader->GetParameterByName(TEXT("keySimilarity")), fSimilarity);
|
|
colorConvertShader->SetFloat (colorConvertShader->GetParameterByName(TEXT("keyBlend")), fBlendVal);
|
|
colorConvertShader->SetFloat (colorConvertShader->GetParameterByName(TEXT("keySpill")), fSpillVal);
|
|
}
|
|
colorConvertShader->SetFloat (colorConvertShader->GetParameterByName(TEXT("gamma")), fGamma);
|
|
|
|
float mat[16];
|
|
bool actuallyUse709 = (colorSpace == COLORSPACE_AUTO) ? !!use709 : (colorSpace == COLORSPACE_709);
|
|
|
|
if (actuallyUse709)
|
|
memcpy(mat, yuvToRGB709[fullRange ? 1 : 0], sizeof(float) * 16);
|
|
else
|
|
memcpy(mat, yuvToRGB601[fullRange ? 1 : 0], sizeof(float) * 16);
|
|
|
|
colorConvertShader->SetValue (colorConvertShader->GetParameterByName(TEXT("yuvMat")), mat, sizeof(float) * 16);
|
|
}
|
|
else {
|
|
if(fGamma != 1.0f && bFiltersLoaded) {
|
|
LoadPixelShader(drawShader);
|
|
HANDLE hGamma = drawShader->GetParameterByName(TEXT("gamma"));
|
|
if(hGamma)
|
|
drawShader->SetFloat(hGamma, fGamma);
|
|
}
|
|
}
|
|
|
|
bool bFlip = bFlipVertical;
|
|
|
|
if(colorType != DeviceOutputType_RGB)
|
|
bFlip = !bFlip;
|
|
|
|
float x, x2;
|
|
if(bFlipHorizontal)
|
|
{
|
|
x2 = pos.x;
|
|
x = x2+size.x;
|
|
}
|
|
else
|
|
{
|
|
x = pos.x;
|
|
x2 = x+size.x;
|
|
}
|
|
|
|
float y = pos.y,
|
|
y2 = y+size.y;
|
|
if(!bFlip)
|
|
{
|
|
y2 = pos.y;
|
|
y = y2+size.y;
|
|
}
|
|
|
|
float fOpacity = float(opacity)*0.01f;
|
|
DWORD opacity255 = DWORD(fOpacity*255.0f);
|
|
|
|
if(bUsePointFiltering) {
|
|
SamplerInfo samplerinfo;
|
|
samplerinfo.filter = GS_FILTER_POINT;
|
|
sampler = CreateSamplerState(samplerinfo);
|
|
LoadSamplerState(sampler, 0);
|
|
}
|
|
|
|
|
|
Texture *tex = (deinterlacer.processor == DEINTERLACING_PROCESSOR_GPU && deinterlacer.texture.get()) ? deinterlacer.texture.get() : texture;
|
|
if(deinterlacer.doublesFramerate)
|
|
{
|
|
if(!deinterlacer.curField)
|
|
DrawSpriteEx(tex, (opacity255<<24) | 0xFFFFFF, x, y, x2, y2, 0.f, 0.0f, .5f, 1.f);
|
|
else
|
|
DrawSpriteEx(tex, (opacity255<<24) | 0xFFFFFF, x, y, x2, y2, .5f, 0.0f, 1.f, 1.f);
|
|
}
|
|
else
|
|
DrawSprite(tex, (opacity255<<24) | 0xFFFFFF, x, y, x2, y2);
|
|
if(deinterlacer.bNewFrame)
|
|
{
|
|
deinterlacer.curField = !deinterlacer.curField;
|
|
deinterlacer.bNewFrame = false; //prevent switching from the second field to the first field
|
|
}
|
|
|
|
if(bUsePointFiltering) delete(sampler);
|
|
|
|
if(colorConvertShader || fGamma != 1.0f)
|
|
LoadPixelShader(oldShader);
|
|
}
|
|
}
|
|
|
|
void DeviceSource::UpdateSettings()
|
|
{
|
|
String strNewDevice = data->GetString(TEXT("device"));
|
|
String strNewAudioDevice = data->GetString(TEXT("audioDevice"));
|
|
UINT64 newFrameInterval = data->GetInt(TEXT("frameInterval"));
|
|
UINT newCX = data->GetInt(TEXT("resolutionWidth"));
|
|
UINT newCY = data->GetInt(TEXT("resolutionHeight"));
|
|
BOOL bNewCustom = data->GetInt(TEXT("customResolution"));
|
|
UINT newPreferredType = data->GetInt(TEXT("usePreferredType")) != 0 ? data->GetInt(TEXT("preferredType")) : -1;
|
|
UINT newSoundOutputType = data->GetInt(TEXT("soundOutputType"));
|
|
bool bNewUseBuffering = data->GetInt(TEXT("useBuffering")) != 0;
|
|
bool bNewUseAudioRender = data->GetInt(TEXT("useAudioRender")) != 0;
|
|
UINT newGamma = data->GetInt(TEXT("gamma"), 100);
|
|
|
|
int newDeintType = data->GetInt(TEXT("deinterlacingType"));
|
|
int newDeintFieldOrder = data->GetInt(TEXT("deinterlacingFieldOrder"));
|
|
int newDeintProcessor = data->GetInt(TEXT("deinterlacingProcessor"));
|
|
|
|
UINT64 frameIntervalDiff = 0;
|
|
bool bCheckSoundOutput = true;
|
|
|
|
if(newFrameInterval > frameInterval)
|
|
frameIntervalDiff = newFrameInterval - frameInterval;
|
|
else
|
|
frameIntervalDiff = frameInterval - newFrameInterval;
|
|
|
|
fullRange = data->GetInt(TEXT("fullrange")) != 0;
|
|
colorSpace = data->GetInt(TEXT("colorspace"));
|
|
|
|
if(strNewAudioDevice == "Disable" && strAudioDevice == "Disable")
|
|
bCheckSoundOutput = false;
|
|
|
|
bool elgato = sstri(strNewDevice.Array(), L"elgato") != nullptr;
|
|
|
|
if(elgato || (bNewUseAudioRender != bUseAudioRender && bCheckSoundOutput) ||
|
|
(newSoundOutputType != soundOutputType && bCheckSoundOutput) || imageCX != newCX || imageCY != newCY ||
|
|
frameIntervalDiff >= 10 || newPreferredType != preferredOutputType ||
|
|
!strDevice.CompareI(strNewDevice) || !strAudioDevice.CompareI(strNewAudioDevice) ||
|
|
bNewCustom != bUseCustomResolution || bNewUseBuffering != bUseBuffering ||
|
|
newGamma != gamma || newDeintType != deinterlacer.type ||
|
|
newDeintFieldOrder != deinterlacer.fieldOrder || newDeintProcessor != deinterlacer.processor)
|
|
{
|
|
API->EnterSceneMutex();
|
|
|
|
bool bWasCapturing = bCapturing;
|
|
if(bWasCapturing) Stop();
|
|
|
|
UnloadFilters();
|
|
LoadFilters();
|
|
|
|
if(bWasCapturing) Start();
|
|
|
|
API->LeaveSceneMutex();
|
|
}
|
|
}
|
|
|
|
void DeviceSource::SetInt(CTSTR lpName, int iVal)
|
|
{
|
|
if(bCapturing)
|
|
{
|
|
if(scmpi(lpName, TEXT("useChromaKey")) == 0)
|
|
{
|
|
bool bNewVal = iVal != 0;
|
|
if(bUseChromaKey != bNewVal)
|
|
{
|
|
API->EnterSceneMutex();
|
|
bUseChromaKey = bNewVal;
|
|
|
|
if(colorConvertShader)
|
|
{
|
|
delete colorConvertShader;
|
|
colorConvertShader = NULL;
|
|
}
|
|
|
|
String strShader;
|
|
strShader = ChooseShader();
|
|
|
|
if(strShader.IsValid())
|
|
colorConvertShader = CreatePixelShaderFromFile(strShader);
|
|
|
|
API->LeaveSceneMutex();
|
|
}
|
|
}
|
|
else if(scmpi(lpName, TEXT("flipImage")) == 0)
|
|
{
|
|
bFlipVertical = iVal != 0;
|
|
}
|
|
else if(scmpi(lpName, TEXT("flipImageHorizontal")) == 0)
|
|
{
|
|
bFlipHorizontal = iVal != 0;
|
|
}
|
|
else if(scmpi(lpName, TEXT("usePointFiltering")) == 0)
|
|
{
|
|
bUsePointFiltering = iVal != 0;
|
|
}
|
|
else if(scmpi(lpName, TEXT("keyColor")) == 0)
|
|
{
|
|
keyColor = (DWORD)iVal;
|
|
|
|
keyBaseColor = Color4().MakeFromRGBA(keyColor);
|
|
Matrix4x4TransformVect(keyChroma, (colorType == DeviceOutputType_HDYC || colorType == DeviceOutputType_RGB) ? (float*)yuv709Mat : (float*)yuvMat, keyBaseColor);
|
|
keyChroma *= 2.0f;
|
|
|
|
if(keyBaseColor.x < keyBaseColor.y && keyBaseColor.x < keyBaseColor.z)
|
|
keyBaseColor -= keyBaseColor.x;
|
|
else if(keyBaseColor.y < keyBaseColor.x && keyBaseColor.y < keyBaseColor.z)
|
|
keyBaseColor -= keyBaseColor.y;
|
|
else if(keyBaseColor.z < keyBaseColor.x && keyBaseColor.z < keyBaseColor.y)
|
|
keyBaseColor -= keyBaseColor.z;
|
|
}
|
|
else if(scmpi(lpName, TEXT("keySimilarity")) == 0)
|
|
{
|
|
keySimilarity = iVal;
|
|
}
|
|
else if(scmpi(lpName, TEXT("keyBlend")) == 0)
|
|
{
|
|
keyBlend = iVal;
|
|
}
|
|
else if(scmpi(lpName, TEXT("keySpillReduction")) == 0)
|
|
{
|
|
keySpillReduction = iVal;
|
|
}
|
|
else if(scmpi(lpName, TEXT("opacity")) == 0)
|
|
{
|
|
opacity = iVal;
|
|
}
|
|
else if(scmpi(lpName, TEXT("timeOffset")) == 0)
|
|
{
|
|
if(audioOut)
|
|
audioOut->SetAudioOffset(iVal);
|
|
}
|
|
else if(scmpi(lpName, TEXT("bufferTime")) == 0)
|
|
{
|
|
bufferTime = iVal*10000;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeviceSource::SetFloat(CTSTR lpName, float fValue)
|
|
{
|
|
if(!bCapturing)
|
|
return;
|
|
|
|
if(scmpi(lpName, TEXT("volume")) == 0)
|
|
{
|
|
fNewVol = fValue;
|
|
bRequestVolume = true;
|
|
}
|
|
}
|