//////////////////////////////////////////////////////// // // videoDS - Graphics Environment for Multimedia // // daniel@bogusfront.org // zmoelnig@iem.at // // Implementation file // // Copyright (c) 2003 Daniel Heckenberg. // Copyright (c) 2010-2011 IOhannes m zmölnig. forum::für::umläute. IEM. zmoelnig@iem.at // For information on usage and redistribution, and for a DISCLAIMER OF ALL // WARRANTIES, see the file, "GEM.LICENSE.TERMS" in this distribution. // ///////////////////////////////////////////////////////// #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "videoDS.h" using namespace gem::plugins; #include "Gem/RTE.h" #include "plugins/PluginFactory.h" #ifdef HAVE_DIRECTSHOW REGISTER_VIDEOFACTORY("DS", videoDS); # include # include # include # include # include "DSgrabber.h" # include # include #define COMRELEASE(x) { if (x) x->Release(); x = NULL; } //#define REGISTER_FILTERGRAPH 1 // Utility functions void SetupCaptureDevice(ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase); void dialogSource (ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase); void dialogFormat (ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase); void dialogDisplay (ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase); void dialogCrossbar (ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase); HRESULT FindCaptureDevice(int device, IBaseFilter ** ppSrcFilter); HRESULT ConnectFilters(IGraphBuilder *pGraph, IBaseFilter *pFirst, IBaseFilter *pSecond); void GetBitmapInfoHdr(AM_MEDIA_TYPE* pmt, BITMAPINFOHEADER** ppbmih); HRESULT GetPin(IBaseFilter *, PIN_DIRECTION, IPin **); HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister); HRESULT SetAviOptions(IBaseFilter *ppf, InterleavingMode INTERLEAVE_MODE); void RemoveGraphFromRot(DWORD pdwRegister); ///////////////////////////////////////////////////////// // // videoDS // ///////////////////////////////////////////////////////// // Constructor // ///////////////////////////////////////////////////////// videoDS :: videoDS(void) : videoBase("directshow", 0), m_readIdx (0), m_lastreadIdx (0), m_writeIdx(0), m_lastwriteIdx(0), m_format(GL_BGR_EXT), #ifdef USE_RECORDING m_recording(false), #endif m_pGB(NULL), m_pMC(NULL), m_pME(NULL), m_pMF(NULL), m_pMS(NULL), m_pMP(NULL), SampleFilter(NULL), NullFilter(NULL), FileFilter(NULL), SampleGrabber(NULL), #ifdef DIRECTSHOW_LOGGING LogFileHandle(NULL), #endif m_pCDbase(NULL), m_pCG(NULL), m_GraphRegister(0) { // Initialize COM if(FAILED(CoInitialize(NULL))) { throw("could not initialise COM."); } m_width=720; m_height=576; // Initialize the input buffers for (int i = 0; i <= 2; i++) { m_pixBlockBuf[i].image.xsize=m_width; m_pixBlockBuf[i].image.ysize=m_height; m_pixBlockBuf[i].image.setCsizeByFormat(GL_RGBA); m_pixBlockBuf[i].image.reallocate(); m_pixBlockBuf[i].newimage = 0; m_nPixDataSize[i] = m_pixBlockBuf[i].image.xsize* m_pixBlockBuf[i].image.ysize* m_pixBlockBuf[i].image.csize; } m_image.image.xsize=m_width; m_image.image.ysize=m_height; m_image.image.setCsizeByFormat(GL_RGBA); m_image.image.reallocate(); #ifdef USE_RECORDING memset(m_filename, 0, MAXPDSTRING); #endif provide("dv"); provide("iidc"); provide("analog"); } //////////////////////////////////////////////////////// // Destructor // ///////////////////////////////////////////////////////// videoDS :: ~videoDS(void) { // Clean up the movie close(); for (int i = 0; i <= 2; i++) { m_pixBlockBuf[i].image.allocate(0); m_pixBlockBuf[i].newimage = 0; m_nPixDataSize[i] = 0; } // Finished with COM CoUninitialize(); } ///////////////////////////////////////////////////////// // open message // ///////////////////////////////////////////////////////// bool videoDS :: openDevice(gem::Properties&props) { HRESULT hr; AM_MEDIA_TYPE MediaType; int device = m_devicenum; do { // Get the interface for DirectShow's GraphBuilder if (FAILED(hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&m_pGB))) { error("Could not get DShow GraphBuilder, hr 0x%X", hr); break; } #ifdef DIRECTSHOW_LOGGING OFSTRUCT OFStruct; LogFileHandle = OpenFile("DirectShow.log", &OFStruct, OF_CREATE); if (LogFileHandle != NULL) { m_pGB->SetLogFile(LogFileHandle); } #endif // Get the interface for DirectShow's CaptureGraphBuilder2 which allows the use of capture devices instead of file sources if ( FAILED(hr = (CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void **)&m_pCG))) || FAILED(hr = m_pCG->SetFiltergraph(m_pGB))){ error("Could not get DShow GraphBuilder, hr 0x%X", hr); break; } // Create the capture device. if (FAILED(hr = FindCaptureDevice(device, &m_pCDbase))) { error("Could not open device: %d\n", device); break; } // Create the SampleGrabber filter hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&SampleFilter); if (hr != S_OK || NULL == SampleFilter) { error("Unable to create SampleFilter interface %d", hr); return false; } // Query for the SampleGrabber interface in the SampleGrabber filter // Needed to grab the buffer using GetCurrentBuffer() hr = SampleFilter->QueryInterface(IID_ISampleGrabber, (void **)&SampleGrabber); if (hr != S_OK || NULL == SampleGrabber) { error("Unable to create SampleGrabber interface %d", hr); return false; } // Set the media type that the SampleGrabber wants. // MEDIATYPE_Video selects only video and not interleaved audio and video // MEDIASUBTYPE_RGB24 is the colorspace and format to deliver frames // MediaType.formattype is GUID_NULL since it is handled later to get file info memset(&MediaType, 0, sizeof(AM_MEDIA_TYPE)); MediaType.majortype = MEDIATYPE_Video; MediaType.subtype = MEDIASUBTYPE_RGB24; MediaType.formattype = GUID_NULL; hr = SampleGrabber->SetMediaType(&MediaType); // Set the SampleGrabber to return continuous frames hr = SampleGrabber->SetOneShot(FALSE); if (hr != S_OK) { error("Unable to setup sample grabber %d", hr); return false; } // Set the SampleGrabber to copy the data to a buffer. This only set to FALSE when a // callback is used. hr = SampleGrabber->SetBufferSamples(TRUE); if (hr != S_OK) { error("Unable to setup sample grabber %d", hr); return false; } // Create the Null Renderer hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&NullFilter); if (hr != S_OK || NULL == NullFilter) { error("Unable to create NullFilter interface %d", hr); return false; } // add the filters to the graph if (FAILED(hr = m_pGB->AddFilter(m_pCDbase, L"Capture Device")) || FAILED(hr = m_pGB->AddFilter(SampleFilter, L"Sample Grabber")) || FAILED(hr = m_pGB->AddFilter(NullFilter, L"Null Renderer"))) { error("Could not add the filters to the graph, hr 0x%X", hr); break; } // Automatically connect the Device filter to the NullFilter through the SampleFilter. // Additional filters may be added. // Try Interleaved Audio and Video first for DV input if (FAILED(hr = m_pCG->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, m_pCDbase, SampleFilter, NullFilter))) { //try Video only for devices with no audio if (FAILED(hr = m_pCG->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, m_pCDbase, SampleFilter, NullFilter))) { error("Unable to connect to SampleGrabber."); return false; } } // QueryInterface for DirectShow interfaces if (FAILED(hr = (m_pGB->QueryInterface(IID_IMediaFilter, (void **)&m_pMF)))) { error("Could not get media filter interface, hr 0x%X", hr); break; } //MediaControl is used for Run, Stop, Pause and running state queries if (FAILED(hr = (m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC)))) { error("Could not get media control interface, hr 0x%X", hr); break; } //not used right now if (FAILED(hr = (m_pGB->QueryInterface(IID_IMediaEvent, (void **)&m_pME)))) { error("Could not get media event interface, hr 0x%X", hr); break; } //MediaSeeking for the end of a clip. not really used here if (FAILED(hr = (m_pGB->QueryInterface(IID_IMediaSeeking, (void **)&m_pMS)))){ error("Could not get media seeking interface, hr 0x%X", hr); break; } //for the position of a clip. not really used for device capture if (FAILED(hr = (m_pGB->QueryInterface(IID_IMediaPosition, (void **)&m_pMP)))){ error("Could not get media position interface, hr 0x%X", hr); break; } // Expose the filter graph so we can view it using GraphEdit #ifdef REGISTER_FILTERGRAPH if (FAILED(hr = AddGraphToRot(m_pGB, &m_GraphRegister))){ error("failed to register filter graph with ROT! hr=0x%X", hr); m_GraphRegister = 0; } #endif // THIS KILLS FILE WRITING!! May improve latency on video preview/playback. // Turn off the reference clock. // if (FAILED(hr = m_pMF->SetSyncSource(NULL))) // { // error("failed to turn off the reference clock hr=0x%X", hr); // break; // } // Indicate that the video is ready. #if USE_RECORDING // JMZ ??? stopCapture(); #endif return true; } while (0); closeDevice(); return false; } //////////////////////////////////////////////////////// // close message // ///////////////////////////////////////////////////////// void videoDS :: closeDevice(void) { #ifdef DIRECTSHOW_LOGGING m_pGB->SetLogFile(NULL); CloseHandle((HANDLE)LogFileHandle); #endif // Release the DirectShow interfaces COMRELEASE(m_pGB); COMRELEASE(m_pMC); COMRELEASE(m_pME); COMRELEASE(m_pMF); COMRELEASE(m_pMS); COMRELEASE(m_pMP); COMRELEASE(SampleFilter); COMRELEASE(NullFilter); COMRELEASE(FileFilter); COMRELEASE(m_pCDbase); COMRELEASE(m_pCG); #ifdef REGISTER_FILTERGRAPH if (m_GraphRegister) { HRESULT hr; RemoveGraphFromRot(m_GraphRegister); m_GraphRegister = 0; } #endif } std::vectorvideoDS :: dialogs(void) { std::vectorresult; result.push_back("source"); result.push_back("format"); result.push_back("display"); result.push_back("crossbar"); return result; } //////////////////////////////////////////////////////// // enumerate message // ///////////////////////////////////////////////////////// std::vectorvideoDS :: enumerate(void) { std::vectorresult; HRESULT hr; IBaseFilter * pSrc = NULL; IMoniker* pMoniker =NULL; ULONG cFetched; ICreateDevEnum* pDevEnum =NULL; IEnumMoniker* pClassEnum = NULL; do { // Create the system device enumerator hr = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void ** ) &pDevEnum); if (FAILED(hr)) { error("Couldn't create system enumerator!"); break; } // Create an enumerator for the video capture devices hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, &pClassEnum, 0); if (FAILED(hr)) { error("Couldn't create class enumerator!"); break; } // If there are no enumerators for the requested type, then // CreateClassEnumerator will succeed, but pClassEnum will be NULL. if (pClassEnum == NULL) { error("No video capture devices found!"); break; } // Use the first video capture device on the device list. // Note that if the Next() call succeeds but there are no monikers, // it will return S_FALSE (which is not a failure). Therefore, we // check that the return code is S_OK instead of using SUCCEEDED() macro. int devIndex = 0; while (S_OK == (pClassEnum->Next (1, &pMoniker, &cFetched))) { IPropertyBag *pPropBag; if (SUCCEEDED(hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag))) { // To retrieve the friendly name of the filter, do the following: VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { std::string s=_bstr_t(varName.bstrVal); result.push_back(s); //post("Dev %d: %S", devIndex, varName.bstrVal); } VariantClear(&varName); COMRELEASE(pPropBag); } COMRELEASE(pMoniker); devIndex++; } } while (0); // Copy the found filter pointer to the output parameter. // Do NOT Release() the reference, since it will still be used // by the calling function. COMRELEASE(pDevEnum); COMRELEASE(pClassEnum); return result; } //////////////////////////////////////////////////////// // render // ///////////////////////////////////////////////////////// pixBlock* videoDS :: getFrame(void) { // Copy the buffer from the camera buffer to the texture buffer copyBuffer(); m_readIdx = m_lastwriteIdx; if (m_nPixDataSize[m_readIdx]){ m_image.newimage=m_pixBlockBuf[m_readIdx].newimage; m_image.image.xsize=m_pixBlockBuf[m_readIdx].image.xsize; m_image.image.ysize=m_pixBlockBuf[m_readIdx].image.ysize; switch (m_pixBlockBuf[m_readIdx].image.format){ case GL_BGR_EXT: default: m_image.image.fromBGR(m_pixBlockBuf[m_readIdx].image.data); } m_image.newimage=true; return &m_image; } return NULL; } /* copies the image from DS into the current m_pixBlockBuf */ void videoDS :: copyBuffer(void) { HRESULT hr; long SampleSize; // Get the media type AM_MEDIA_TYPE pmt; int readIdx = m_readIdx; if ((m_writeIdx = (m_lastwriteIdx + 1) % 3) == readIdx) m_writeIdx = (readIdx + 1) % 3; // Get the current buffer size from the SampleGrabber. if (SampleGrabber != NULL) { hr = SampleGrabber->GetCurrentBuffer(&SampleSize, NULL); if (hr != S_OK) return; } // Check for a format change. if (NULL == SampleGrabber || FAILED(hr = SampleGrabber->GetConnectedMediaType(&pmt))) { error("could not get sample media type."); close(); return; } if (S_OK == hr) { BITMAPINFOHEADER* pbmih; GetBitmapInfoHdr(&pmt, &pbmih); m_width = pbmih->biWidth; m_height = pbmih->biHeight; m_format = GL_BGR_EXT; FreeMediaType(pmt); // is this necessary?! } m_pixBlockBuf[m_writeIdx].image.xsize = m_width; m_pixBlockBuf[m_writeIdx].image.ysize = m_height; m_pixBlockBuf[m_writeIdx].image.setCsizeByFormat(m_format); m_pixBlockBuf[m_writeIdx].image.reallocate(); m_pixBlockBuf[m_writeIdx].image.reallocate(SampleSize); m_nPixDataSize[m_writeIdx] = SampleSize; // Get the current buffer from the SampleGrabber. if (SampleGrabber != NULL) { hr = SampleGrabber->GetCurrentBuffer(&SampleSize, (long *)m_pixBlockBuf[m_readIdx].image.data); if (hr != S_OK) return; m_pixBlockBuf[m_writeIdx].newimage = 1; } m_lastwriteIdx = m_writeIdx; } //////////////////////////////////////////////////////// // postrender // ///////////////////////////////////////////////////////// void videoDS :: releaseFrame(void) { if (!m_haveVideo || !m_capturing)return; m_pixBlockBuf[m_readIdx].newimage = 0; m_lastreadIdx = m_readIdx; } //////////////////////////////////////////////////////// // startTransfer // ///////////////////////////////////////////////////////// bool videoDS :: startTransfer(void) { HRESULT hr; m_readIdx = 0; m_lastreadIdx = 0; m_writeIdx = 1; m_lastwriteIdx = 0; // Get the stream characteristics AM_MEDIA_TYPE mt; BITMAPINFOHEADER* pbmih; if (NULL == SampleGrabber || FAILED(hr = SampleGrabber->GetConnectedMediaType(&mt))) { error("Could not get connect media type, hr 0x%X", hr); return false; } GetBitmapInfoHdr(&mt, &pbmih); m_width = pbmih->biWidth; m_height = pbmih->biHeight; m_format = GL_BGR_EXT; //starts the graph rendering if (FAILED(hr = m_pMC->Run())) { error("Could not start graph playback, hr 0x%X", hr); return false; } return true; } ///////////////////////////////////////////////////////// // stopTransfer // ///////////////////////////////////////////////////////// bool videoDS :: stopTransfer(void) { HRESULT hr; if (FAILED(hr = m_pMC->Stop())) { error("Could not stop graph playback, hr 0x%X", hr); return false; } return true; } //////////////////////////////////////////////////////// // dvMess // ///////////////////////////////////////////////////////// bool videoDS :: setDimen(int x, int y, int leftmargin, int rightmargin, int topmargin, int bottommargin) { /* norm NTSC PAL DVRESOLUTION_FULL 720 x 480 720 x 576 DVRESOLUTION_HALF 360 x 240 360 x 288 DVRESOLUTION_QUARTER 180 x 120 180 x 144 DVRESOLUTION_DC 88 x 60 88 x 72 */ // 5*yPAL = 6*yNTSC bool pal=true; // how to know this? float y_fac = pal?1.:(5./6.); int resolution=DVRESOLUTION_FULL; if ((x<= 88) && (y<=( 72*y_fac)))resolution=DVRESOLUTION_DC; else if((x<=180) && (y<=(144*y_fac)))resolution=DVRESOLUTION_QUARTER; else if((x<=360) && (y<=(288*y_fac)))resolution=DVRESOLUTION_HALF; else resolution=DVRESOLUTION_FULL; HRESULT hr = S_OK; if(stop()) { IIPDVDec *pDV=NULL; if (SUCCEEDED(hr = (m_pCG->FindInterface(NULL, NULL, m_pCDbase, IID_IIPDVDec, (void **)&pDV)))) { hr = pDV->put_IPDisplay(resolution); if (FAILED(hr)) { error("Could not set decoder resolution."); } } if(pDV) pDV->Release(); start(); } return true; } //////////////////////////////////////////////////////// // colorspace message // ///////////////////////////////////////////////////////// bool videoDS :: setColor(int format) { if(format)m_image.image.setCsizeByFormat(format); return true; } //////////////////////////////////////////////////// // dialogMess // ///////////////////////////////////////////////////////// bool videoDS :: dialog(std::vectordlg) { HRESULT hr; if (!m_haveVideo) return true; bool running=stop(); if(dlg.empty()) { SetupCaptureDevice(m_pCG, m_pCDbase); } else { int i; for(i=0; iSetOutputFileName(&MEDIASUBTYPE_Avi, WideFileName, &FileFilter, NULL))) { error("Unable to set output filename."); return; } // Set AVI output option for interleaving. if (FAILED(hr = SetAviOptions(FileFilter, INTERLEAVE_NONE))) { error("Unable to set avi options."); return; } // Connect the Capture Device filter to the File Writer filter. Try using // MEDIATYPE_Interleaved first, else default to MEDIATYPE_Video. if (FAILED(hr = m_pCG->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, m_pCDbase, NULL, FileFilter))) { if (FAILED(hr = m_pCG->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, m_pCDbase, NULL, FileFilter))) { error("Unable to record to avi."); return; } } m_recording = true; } } void videoDS :: stopCapture(void) { HRESULT RetVal; if (m_recording && m_haveVideo) { IBaseFilter *Filter; // Remove the File Writer filter, if available. //This is probably where DS releases the written AVI file RetVal = m_pGB->FindFilterByName(L"File Writer", &Filter); if (S_OK == RetVal) { m_pGB->RemoveFilter(Filter); } // Remove the AVI Mux filter, if available. RetVal = m_pGB->FindFilterByName(L"Mux", &Filter); if (S_OK == RetVal) { m_pGB->RemoveFilter(Filter); } m_recording = false; } } void videoDS :: fileMess(t_symbol *filename) { _snprintf(m_filename, MAXPDSTRING, "%s", findFile(filename->s_name).c_str()); } void videoDS :: recordMess(int state) { if (NULL==m_filename || 0 == m_filename[0]) { error("No filename passed"); return; } stopTransfer(); if (state) startCapture(); else stopCapture(); startTransfer(); } #endif /* recording */ #if 0 "device" -> openMess "open" -> openMess "close" -> closeMess "dv" -> dvMess "record" -> recordMess "file" -> fileMess float -> captureOnOff #endif // From Microsoft sample: // // Let's talk about UI for a minute. There are many programmatic interfaces // you can use to program a capture filter or related filter to capture the // way you want it to.... eg: IAMStreamConfig, IAMVideoCompression, // IAMCrossbar, IAMTVTuner, IAMTVAudio, IAMAnalogVideoDecoder, IAMCameraControl, // IAMVideoProcAmp, etc. // // But you probably want some UI to let the user play with all these settings. // For new WDM-style capture devices, we offer some default UI you can use. // The code below shows how to bring up all of the dialog boxes supported // by any capture filters. // // The following code shows you how you can bring up all of the // dialogs supported by a particular object at once on a big page with lots // of thumb tabs. You do this by starting with an interface on the object that // you want, and using ISpecifyPropertyPages to get the whole list, and // OleCreatePropertyFrame to bring them all up. This way you will get custom // property pages a filter has, too, that are not one of the standard pages that // you know about. There are at least 9 objects that may have property pages. // Your app already has 2 of the object pointers, the video capture filter and // the audio capture filter (let's call them pVCap and pACap) // 1. The video capture filter - pVCap // 2. The video capture filter's capture pin - get this by calling // FindInterface(&PIN_CATEGORY_CAPTURE, pVCap, IID_IPin, &pX); // 3. The video capture filter's preview pin - get this by calling // FindInterface(&PIN_CATEGORY_PREVIEW, pVCap, IID_IPin, &pX); // 4. The audio capture filter - pACap // 5. The audio capture filter's capture pin - get this by calling // FindInterface(&PIN_CATEGORY_CAPTURE, pACap, IID_IPin, &pX); // 6. The crossbar connected to the video capture filter - get this by calling // FindInterface(NULL, pVCap, IID_IAMCrossbar, &pX); // 7. There is a possible second crossbar to control audio - get this by // looking upstream of the first crossbar like this: // FindInterface(&LOOK_UPSTREAM_ONLY, pX, IID_IAMCrossbar, &pX2); // 8. The TV Tuner connected to the video capture filter - get this by calling // FindInterface(NULL, pVCap, IID_IAMTVTuner, &pX); // 9. The TV Audio connected to the audio capture filter - get this by calling // FindInterface(NULL, pACap, IID_IAMTVAudio, &pX); // 10. We have a helper class, CCrossbar, which makes the crossbar issue less // confusing. In fact, although not supported here, there may be more than // two crossbars, arranged in many different ways. An application may not // wish to have separate dialogs for each crossbar, but instead hide the // complexity and simply offer the user a list of inputs that can be chosen. // This list represents all the unique inputs from all the crossbars. // The crossbar helper class does this and offers that list as #10. It is // expected that an application will either provide the crossbar dialogs // above (#6 and #7) OR provide the input list (this #10), but not both. // That would be confusing because if you select an input using dialog 6 or // 7 the input list here in #10 won't know about your choice. // // Your last choice for UI is to make your own pages, and use the results of // your custom page to call the interfaces programmatically. void SetupCaptureDevice(ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase) { HRESULT hr; // Check for old style VFW dialogs IAMVfwCaptureDialogs *pDlg; if (SUCCEEDED(hr = (pCG->FindInterface(NULL, NULL, pCDbase, IID_IAMVfwCaptureDialogs, (void **)&pDlg)))) { if (S_OK == (pDlg->HasDialog(VfwCaptureDialog_Source))) if (FAILED(hr = pDlg->ShowDialog(VfwCaptureDialog_Source, NULL))) error("Could not show VFW Capture Source Dialog"); if (S_OK == (pDlg->HasDialog(VfwCaptureDialog_Format))) if (FAILED(hr = pDlg->ShowDialog(VfwCaptureDialog_Format, NULL))) error("Could not show VFW Capture Format Dialog"); if (S_OK == (pDlg->HasDialog(VfwCaptureDialog_Display))) if (FAILED(hr = pDlg->ShowDialog(VfwCaptureDialog_Display, NULL))) error("Could not show VFW Capture Display Dialog"); pDlg->Release(); } ISpecifyPropertyPages *pSpec; CAUUID cauuid; // 1. the video capture filter itself hr = pCDbase->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if (hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pCDbase, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } // 2. The video capture capture pin IAMStreamConfig *pSC; hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, pCDbase, IID_IAMStreamConfig, (void **)&pSC); if(hr != S_OK) hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCDbase, IID_IAMStreamConfig, (void **)&pSC); if(hr == S_OK) { hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if(hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pSC, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pSC->Release(); } // 4 & 5. The video crossbar, and a possible second crossbar IAMCrossbar *pX, *pX2; IBaseFilter *pXF; hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, pCDbase, IID_IAMCrossbar, (void **)&pX); if(hr != S_OK) hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCDbase, IID_IAMCrossbar, (void **)&pX); if(hr == S_OK) { hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF); if(hr == S_OK) { hr = pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if(hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pX, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } hr = pCG->FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pXF, IID_IAMCrossbar, (void **)&pX2); if(hr == S_OK) { hr = pX2->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if(hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pX2, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pX2->Release(); } pXF->Release(); } pX->Release(); } } void dialogSource(ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase) { HRESULT hr; ISpecifyPropertyPages *pSpec; CAUUID cauuid; IAMVfwCaptureDialogs *pDlg = NULL; hr = pCG->FindInterface(NULL, NULL, pCDbase, IID_IAMVfwCaptureDialogs, (void **)&pDlg); if (pDlg) { if (S_OK == (pDlg->HasDialog(VfwCaptureDialog_Source))) { if (FAILED(hr = pDlg->ShowDialog(VfwCaptureDialog_Source, NULL))) { error("Could not show VFW Capture Source Dialog"); } } } else { hr = pCDbase->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if (hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pCDbase, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } } } void dialogFormat(ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase) { HRESULT hr; ISpecifyPropertyPages *pSpec; CAUUID cauuid; IAMVfwCaptureDialogs *pDlg = NULL; hr = pCG->FindInterface(NULL, NULL, pCDbase, IID_IAMVfwCaptureDialogs, (void **)&pDlg); if (pDlg) { if (S_OK == (pDlg->HasDialog(VfwCaptureDialog_Format))) if (FAILED(hr = pDlg->ShowDialog(VfwCaptureDialog_Format, NULL))) { error("Could not show VFW Capture Format Dialog"); } } else { IAMStreamConfig *pSC; hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, pCDbase, IID_IAMStreamConfig, (void **)&pSC); if(hr != S_OK) hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCDbase, IID_IAMStreamConfig, (void **)&pSC); if(hr == S_OK) { hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if(hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pSC, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pSC->Release(); } } } void dialogDisplay(ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase) { HRESULT hr; ISpecifyPropertyPages *pSpec; CAUUID cauuid; IAMVfwCaptureDialogs *pDlg = NULL; hr = pCG->FindInterface(NULL, NULL, pCDbase, IID_IAMVfwCaptureDialogs, (void **)&pDlg); if (pDlg) { if (S_OK == (pDlg->HasDialog(VfwCaptureDialog_Display))) { if (FAILED((hr = pDlg->ShowDialog(VfwCaptureDialog_Display, NULL)))) { error("Could not show VFW Capture Display Dialog"); } } else error("No display dialog for this device"); } } void dialogCrossbar(ICaptureGraphBuilder2* pCG, IBaseFilter * pCDbase) { HRESULT hr; ISpecifyPropertyPages *pSpec=NULL; CAUUID cauuid; IAMCrossbar *pX=NULL, *pX2=NULL; IBaseFilter *pXF=NULL; hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, pCDbase, IID_IAMCrossbar, (void **)&pX); if(hr != S_OK) hr = pCG->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCDbase, IID_IAMCrossbar, (void **)&pX); if(hr == S_OK) { hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF); if(hr == S_OK) { hr = pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if(hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pX, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } hr = pCG->FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pXF, IID_IAMCrossbar, (void **)&pX2); if(hr == S_OK) { hr = pX2->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); if(hr == S_OK && cauuid.cElems > 0) { hr = OleCreatePropertyFrame(NULL, 30, 30, NULL, 1, (IUnknown **)&pX2, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pX2->Release(); } pXF->Release(); } pX->Release(); } } HRESULT FindCaptureDevice(int device, IBaseFilter ** ppSrcFilter) { HRESULT hr; IBaseFilter * pSrc = NULL; IMoniker* pMoniker =NULL; ULONG cFetched; ICreateDevEnum* pDevEnum =NULL; IEnumMoniker* pClassEnum = NULL; do { // Create the system device enumerator hr = CoCreateInstance (CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void ** ) &pDevEnum); if (FAILED(hr)) { error("Couldn't create system enumerator!"); break; } // Create an enumerator for the video capture devices hr = pDevEnum->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, &pClassEnum, 0); if (FAILED(hr)){ error("Couldn't create class enumerator!"); break; } // If there are no enumerators for the requested type, then // CreateClassEnumerator will succeed, but pClassEnum will be NULL. if (pClassEnum == NULL) { error("No video capture devices found!"); hr = E_FAIL; break; } // Use the first video capture device on the device list. // Note that if the Next() call succeeds but there are no monikers, // it will return S_FALSE (which is not a failure). Therefore, we // check that the return code is S_OK instead of using SUCCEEDED() macro. int devIndex = 0; while( (S_OK == (pClassEnum->Next (1, &pMoniker, &cFetched))) && (devIndex <= device)) { if (devIndex == device) { // Bind Moniker to a filter object hr = pMoniker->BindToObject(0,0,IID_IBaseFilter, (void**)&pSrc); if (FAILED(hr)) { error("Couldn't bind moniker to filter object!"); } } COMRELEASE(pMoniker); devIndex++; } } while (0); // Copy the found filter pointer to the output parameter. // Do NOT Release() the reference, since it will still be used // by the calling function. COMRELEASE(pDevEnum); COMRELEASE(pClassEnum); *ppSrcFilter = pSrc; return hr; } HRESULT ConnectFilters(IGraphBuilder *pGraph, IBaseFilter *pFirst, IBaseFilter *pSecond) { IPin *pOut = NULL, *pIn = NULL; HRESULT hr = GetPin(pFirst, PINDIR_OUTPUT, &pOut); if (FAILED(hr)) return hr; hr = GetPin(pSecond, PINDIR_INPUT, &pIn); if (FAILED(hr)) { pOut->Release(); return E_FAIL; } hr = pGraph->Connect(pOut, pIn); pIn->Release(); pOut->Release(); return hr; } void GetBitmapInfoHdr(AM_MEDIA_TYPE* pmt, BITMAPINFOHEADER** ppbmih) { *ppbmih = NULL; if (IsEqualGUID(pmt->formattype, FORMAT_VideoInfo) || IsEqualGUID(pmt->formattype, FORMAT_MPEGVideo)) { VIDEOINFOHEADER * pVideoFormat = (VIDEOINFOHEADER *) pmt->pbFormat; *ppbmih = &(((VIDEOINFOHEADER *) pmt->pbFormat)->bmiHeader); } else if ( IsEqualGUID(pmt->formattype, FORMAT_MPEG2Video) || IsEqualGUID(pmt->formattype, FORMAT_VideoInfo2)) { *ppbmih = &(((VIDEOINFOHEADER2 *) pmt->pbFormat)->bmiHeader); } else { error("Unknown media format"); return; } } HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin) { IEnumPins *pEnum; IPin *pPin; pFilter->EnumPins(&pEnum); while(pEnum->Next(1, &pPin, 0) == S_OK) { PIN_DIRECTION PinDirThis; pPin->QueryDirection(&PinDirThis); if (PinDir == PinDirThis) { pEnum->Release(); *ppPin = pPin; return S_OK; } pPin->Release(); } pEnum->Release(); return E_FAIL; } HRESULT SetAviOptions(IBaseFilter *ppf, InterleavingMode INTERLEAVE_MODE) { HRESULT hr; CComPtr pMux = NULL; CComPtr pInterleaving = NULL; ASSERT(ppf); if (!ppf) return E_POINTER; // QI for interface AVI Muxer if (FAILED(hr = ppf->QueryInterface(IID_IConfigAviMux, reinterpret_cast(&pMux)))) { error("IConfigAviMux failed."); } if (FAILED(hr = pMux->SetOutputCompatibilityIndex(TRUE))) { error("SetOutputCompatibilityIndex failed."); } // QI for interface Interleaving if (FAILED(hr = ppf->QueryInterface(IID_IConfigInterleaving, reinterpret_cast(&pInterleaving)))) { error("IConfigInterleaving failed."); } // put the interleaving mode (full, none, half) if (FAILED(pInterleaving->put_Mode(INTERLEAVE_MODE))) { error("put_Mode failed."); } return hr; } HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister) { IMoniker * pMoniker; IRunningObjectTable *pROT; if (FAILED(GetRunningObjectTable(0, &pROT))) { return E_FAIL; } WCHAR wsz[128]; StringCchPrintfW(wsz, 128, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId()); HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker); if (SUCCEEDED(hr)) { hr = pROT->Register(0, pUnkGraph, pMoniker, pdwRegister); pMoniker->Release(); } pROT->Release(); return hr; } void RemoveGraphFromRot(DWORD pdwRegister) { IRunningObjectTable *pROT; if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) { pROT->Revoke(pdwRegister); pROT->Release(); } } #else /* !HAVE_DIRECTSHOW */ videoDS :: videoDS() : videoBase("") {} videoDS :: ~videoDS() {} #endif