obs/DShowPlugin/DeviceSource.cpp
jp9000 65f5bd10cf DShowPlugin: Only dynamically change size in Preprocess
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.
2015-11-23 14:48:18 -08:00

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;
}
}