/******************************************************************************** Copyright (C) 2012 Hugh Bailey 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" DWORD STDCALL PackPlanarThread(ConvertData *data); bool DeviceSource::Init(XElement *data) { traceIn(DeviceSource::Init); HRESULT err; err = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, (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, (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 sasmple 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; return true; traceOut; } DeviceSource::~DeviceSource() { traceIn(DeviceSource::~DeviceSource); Stop(); UnloadFilters(); SafeRelease(capture); SafeRelease(graph); if(hConvertThreads) Free(hConvertThreads); if(convertData) Free(convertData); if(hSampleMutex) OSCloseMutex(hSampleMutex); traceOut; } String DeviceSource::ChooseShader() { if(colorType == DeviceOutputType_RGB && !bUseChromaKey) return String(); String strShader; strShader << TEXT("plugins/DShowPlugin/shaders/"); 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; } const float yuv709Mat[16] = { 0.2126f, 0.7152f, 0.0722f, 0.0625f, -0.1150f, -0.3850f, 0.5000f, 0.50f, 0.5000f, -0.4540f, -0.0460f, 0.50f, 0.0f, 0.0f, 0.0f, 1.0f}; const float yuvMat[16] = { 0.257f, 0.504f, 0.098f, 0.0625f, -0.148f, -0.291f, 0.439f, 0.50f, 0.439f, -0.368f, -0.071f, 0.50f, 0.0f, 0.0f, 0.0f, 1.0f}; bool DeviceSource::LoadFilters() { traceIn(DeviceSource::LoadFilters); if(bCapturing || bFiltersLoaded) return false; bool bSucceeded = false; List outputList; IAMStreamConfig *config = NULL; bool bAddedCapture = false, bAddedDevice = false; GUID expectedMediaType; IPin *devicePin = NULL; HRESULT err; String strShader; bUseThreadedConversion = API->UseMultithreadedOptimizations() && (OSGetTotalCores() > 1); //------------------------------------------------ bUseCustomResolution = data->GetInt(TEXT("customResolution")); strDevice = data->GetString(TEXT("device")); bFlipVertical = data->GetInt(TEXT("flipImage")) != 0; //------------------------------------------------ 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); 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; //------------------------------------------------ if(!strDevice.IsValid()) { AppWarning(TEXT("DShowPlugin: Invalid device specified")); goto cleanFinish; } deviceFilter = GetDeviceByName(strDevice); if(!deviceFilter) { AppWarning(TEXT("DShowPlugin: Could not create device filter")); goto cleanFinish; } devicePin = GetOutputPin(deviceFilter); if(!devicePin) { AppWarning(TEXT("DShowPlugin: Could not create device ping")); goto cleanFinish; } GetOutputList(devicePin, outputList); //------------------------------------------------ renderCX = renderCY = 0; frameInterval = 0; if(bUseCustomResolution) { renderCX = data->GetInt(TEXT("resolutionWidth")); renderCY = data->GetInt(TEXT("resolutionHeight")); frameInterval = data->GetInt(TEXT("frameInterval")); } else { SIZE size; GetClosestResolution(outputList, size, frameInterval); renderCX = size.cx; renderCY = size.cy; } if(!renderCX || !renderCY || !frameInterval) { AppWarning(TEXT("DShowPlugin: Invalid size/fps specified")); goto cleanFinish; } preferredOutputType = (data->GetInt(TEXT("usePreferredType")) != 0) ? data->GetInt(TEXT("preferredType")) : -1; int numThreads = MAX(OSGetTotalCores()-2, 1); for(int i=0; i(bestOutput->mediaType->pbFormat); String strTest = FormattedString(TEXT(" device: %s, chosen type: %s, usingFourCC: %s, res: %ux%u - %ux%u, fps: %g-%g"), strDevice.Array(), EnumToName[(int)bestOutput->videoType], bestOutput->bUsingFourCC ? TEXT("true") : TEXT("false"), bestOutput->minCX, bestOutput->minCY, bestOutput->maxCX, bestOutput->maxCY, 10000000.0/double(bestOutput->maxFrameInterval), 10000000.0/double(bestOutput->minFrameInterval)); char fourcc[5]; mcpy(fourcc, &pVih->bmiHeader.biCompression, 4); fourcc[4] = 0; if(pVih->bmiHeader.biCompression > 1000) strTest << FormattedString(TEXT(", fourCC: '%S'\r\n"), fourcc); else strTest << FormattedString(TEXT(", fourCC: %08lX\r\n"), pVih->bmiHeader.biCompression); Log(strTest.Array()); } 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; 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; } if(colorType == DeviceOutputType_YV12 || colorType == DeviceOutputType_I420) { for(int i=0; iQueryInterface(IID_IAMStreamConfig, (void**)&config))) { AppWarning(TEXT("DShowPlugin: Could not get IAMStreamConfig for device pin, result = %08lX"), err); goto cleanFinish; } AM_MEDIA_TYPE outputMediaType; CopyMediaType(&outputMediaType, bestOutput->mediaType); VIDEOINFOHEADER *vih = reinterpret_cast(outputMediaType.pbFormat); vih->AvgTimePerFrame = frameInterval; vih->bmiHeader.biWidth = renderCX; vih->bmiHeader.biHeight = renderCY; vih->bmiHeader.biSizeImage = renderCX*renderCY*(vih->bmiHeader.biBitCount>>3); if(FAILED(err = config->SetFormat(&outputMediaType))) { AppWarning(TEXT("DShowPlugin: SetFormat on device pin failed, result = %08lX"), err); goto cleanFinish; } FreeMediaType(outputMediaType); //------------------------------------------------ captureFilter = new CaptureFilter(this, expectedMediaType); if(FAILED(err = graph->AddFilter(captureFilter, NULL))) { AppWarning(TEXT("DShowPlugin: Failed to add capture filter to graph, result = %08lX"), err); goto cleanFinish; } bAddedCapture = true; if(FAILED(err = graph->AddFilter(deviceFilter, NULL))) { AppWarning(TEXT("DShowPlugin: Failed to add device filter to graph, result = %08lX"), err); goto cleanFinish; } bAddedDevice = true; //------------------------------------------------ //THANK THE NINE DIVINES I FINALLY GOT IT WORKING bool 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 device pin to the capture pin, result = %08lX"), err); goto cleanFinish; } } if(FAILED(err = graph->QueryInterface(IID_IMediaControl, (void**)&control))) { AppWarning(TEXT("DShowPlugin: Failed to get IMediaControl, result = %08lX"), err); goto cleanFinish; } bSucceeded = true; cleanFinish: SafeRelease(config); SafeRelease(devicePin); for(UINT i=0; iRemoveFilter(captureFilter); if(bAddedDevice) graph->RemoveFilter(deviceFilter); SafeRelease(deviceFilter); SafeRelease(captureFilter); SafeRelease(control); if(colorConvertShader) { delete colorConvertShader; colorConvertShader = NULL; } if(lpImageBuffer) { Free(lpImageBuffer); lpImageBuffer = NULL; } bReadyToDraw = true; } else bReadyToDraw = false; //----------------------------------------------------- // 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); } 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 && 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; return bSucceeded; traceOut; } void DeviceSource::UnloadFilters() { traceIn(DeviceSource::UnloadFilters); if(texture) { delete texture; texture = NULL; } int numThreads = MAX(OSGetTotalCores()-2, 1); for(int i=0; iRemoveFilter(captureFilter); graph->RemoveFilter(deviceFilter); SafeReleaseLogRef(captureFilter); SafeReleaseLogRef(deviceFilter); bFiltersLoaded = false; } if(colorConvertShader) { delete colorConvertShader; colorConvertShader = NULL; } if(lpImageBuffer) { Free(lpImageBuffer); lpImageBuffer = NULL; } SafeRelease(control); traceOut; } void DeviceSource::Start() { traceIn(DeviceSource::Start); if(bCapturing || !control) return; HRESULT err; if(FAILED(err = control->Run())) { AppWarning(TEXT("DShowPlugin: control->Run failed, result = %08lX"), err); return; } bCapturing = true; traceOut; } void DeviceSource::Stop() { traceIn(DeviceSource::Stop); if(!bCapturing) return; bCapturing = false; control->Stop(); FlushSamples(); traceOut; } void DeviceSource::BeginScene() { traceIn(DeviceSource::BeginScene); Start(); traceOut; } void DeviceSource::EndScene() { traceIn(DeviceSource::EndScene); Stop(); traceOut; } void DeviceSource::Receive(IMediaSample *sample) { if(bCapturing) { OSEnterMutex(hSampleMutex); SafeRelease(curSample); curSample = sample; curSample->AddRef(); OSLeaveMutex(hSampleMutex); } } DWORD STDCALL PackPlanarThread(ConvertData *data) { do { WaitForSingleObject(data->hSignalConvert, INFINITE); if(data->bKillThread) break; IMediaSample *sample = data->sample; PackPlanar(data->output, data->input, data->width, data->height, data->pitch, data->startY, data->endY); sample->Release(); SetEvent(data->hSignalComplete); }while(!data->bKillThread); return 0; } void DeviceSource::Preprocess() { traceIn(DeviceSource::Preprocess); if(!bCapturing) return; IMediaSample *lastSample = NULL; OSEnterMutex(hSampleMutex); if(curSample) { lastSample = curSample; curSample = NULL; } OSLeaveMutex(hSampleMutex); int numThreads = MAX(OSGetTotalCores()-2, 1); if(lastSample) { BYTE *lpImage = NULL; if(colorType == DeviceOutputType_RGB) { if(texture) { if(SUCCEEDED(lastSample->GetPointer(&lpImage))) texture->SetImage(lpImage, GS_IMAGEFORMAT_BGRX, renderCX*4); bReadyToDraw = true; } } else if(colorType == DeviceOutputType_I420 || colorType == DeviceOutputType_YV12) { if(bUseThreadedConversion) { if(!bFirstFrame) { List events; for(int i=0; iSetImage(lpImageBuffer, GS_IMAGEFORMAT_RGBX, texturePitch); bReadyToDraw = true; } else bFirstFrame = false; if(SUCCEEDED(lastSample->GetPointer(&lpImage))) { for(int i=0; iAddRef(); for(int i=0; iGetPointer(&lpImage))) { LPBYTE lpData; UINT pitch; if(texture->Map(lpData, pitch)) { PackPlanar(lpData, lpImage, renderCX, renderCY, pitch, 0, renderCY); texture->Unmap(); } } bReadyToDraw = true; } } else if(colorType == DeviceOutputType_YVYU || colorType == DeviceOutputType_YUY2) { if(SUCCEEDED(lastSample->GetPointer(&lpImage))) { LPBYTE lpData; UINT pitch; if(texture->Map(lpData, pitch)) { Convert422To444(lpData, lpImage, pitch, true); texture->Unmap(); } } bReadyToDraw = true; } else if(colorType == DeviceOutputType_UYVY || colorType == DeviceOutputType_HDYC) { if(SUCCEEDED(lastSample->GetPointer(&lpImage))) { LPBYTE lpData; UINT pitch; if(texture->Map(lpData, pitch)) { Convert422To444(lpData, lpImage, pitch, false); texture->Unmap(); } } bReadyToDraw = true; } lastSample->Release(); } traceOut; } void DeviceSource::Render(const Vect2 &pos, const Vect2 &size) { traceIn(DeviceSource::Render); if(texture && bReadyToDraw) { Shader *oldShader = GetCurrentPixelShader(); 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); } } bool bFlip = bFlipVertical; if(colorType != DeviceOutputType_RGB) bFlip = !bFlip; if(bFlip) DrawSprite(texture, 0xFFFFFFFF, pos.x, pos.y, pos.x+size.x, pos.y+size.y); else DrawSprite(texture, 0xFFFFFFFF, pos.x, pos.y+size.y, pos.x+size.x, pos.y); if(colorConvertShader) LoadPixelShader(oldShader); } traceOut; } void DeviceSource::UpdateSettings() { traceIn(DeviceSource::UpdateSettings); String strNewDevice = data->GetString(TEXT("device")); 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; if(renderCX != newCX || renderCY != newCY || frameInterval != newFrameInterval || newPreferredType != preferredOutputType || !strDevice.CompareI(strNewDevice) || bNewCustom != bUseCustomResolution) { API->EnterSceneMutex(); bool bWasCapturing = bCapturing; if(bWasCapturing) Stop(); UnloadFilters(); LoadFilters(); if(bWasCapturing) Start(); API->LeaveSceneMutex(); } traceOut; } 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("keyColor")) == 0) { keyColor = (DWORD)iVal; keyBaseColor = Color4().MakeFromRGBA(keyColor); Matrix4x4TransformVect(keyChroma, (colorType == DeviceOutputType_HDYC) ? (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; } } }