I'm new to WIA. I somehow managed to get a device listing, select my device and enumerate this device for an image item. But now, when I try to transfer an image, I'm stuck. If I use the root item for data transfer pWiaDataTransfer->idtGetData returns a HRESULT of 0x8000FFFF (E_UNEXPECTED), if I use the image item (there are only two items 'root' and 'top' on my device) it fails when I obtain the storage interface. I thought maybe I needed to obtain the storage interface from 'root' and the data transfer interface from 'top' but that also fails (when I try to obtain the data transfer interface). I then get 0x80004002 (E_NOINTERFACE - No such interface supported.). Here is the code:
HRESULT TransferWiaItem( IWiaItem *pWiaItem, IWiaItem *pWiaItem2 )
{
IWiaPropertyStorage *pWiaPropertyStorage = NULL;
HRESULT hr = pWiaItem->QueryInterface( IID_IWiaPropertyStorage, (void**)&pWiaPropertyStorage );
if( SUCCEEDED( hr ) )
{
PROPSPEC PropSpec[2] = {0};
PROPVARIANT PropVariant[2] = {0};
const ULONG c_nPropCount = sizeof(PropVariant)/sizeof(PropVariant[0]);
GUID guidOutputFormat = WiaImgFmt_BMP;
PropSpec[0].ulKind = PRSPEC_PROPID;
PropSpec[0].propid = WIA_IPA_FORMAT;
PropSpec[1].ulKind = PRSPEC_PROPID;
PropSpec[1].propid = WIA_IPA_TYMED;
PropVariant[0].vt = VT_CLSID;
PropVariant[0].puuid = &guidOutputFormat;
PropVariant[1].vt = VT_I4;
PropVariant[1].lVal = TYMED_FILE;
hr = pWiaPropertyStorage->WriteMultiple( c_nPropCount, PropSpec, PropVariant, WIA_IPA_FIRST );
if( SUCCEEDED( hr ) )
{
IWiaDataTransfer *pWiaDataTransfer = NULL;
hr = pWiaItem2->QueryInterface( IID_IWiaDataTransfer, (void**)&pWiaDataTransfer );
if( SUCCEEDED( hr ) )
{
CWiaDataCallback *pCallback = new CWiaDataCallback;
if( pCallback )
{
IWiaDataCallback *pWiaDataCallback = NULL;
hr = pCallback->QueryInterface( IID_IWiaDataCallback, (void**)&pWiaDataCallback );
if( SUCCEEDED( hr ) )
{
STGMEDIUM stgMedium = {0};
stgMedium.tymed = TYMED_FILE;
hr = pWiaDataTransfer->idtGetData( &stgMedium, pWiaDataCallback );
...
Where pWiaItem is the 'root' item and pWiaItem2 is the 'top' item.
Anybody have a clue what is going on here?
The solution is to enumerate the items of the 'root' WiaItem in the local context again. I did this in an init method and stored the pointer to the 'top', somehow it seemed so that it got invalidated and the HRESULT did not reflect that. After I changed the code, so that the 'top' WiaItem gets identified right before I use it, everything went fine.
Related
I'm trying to change the keyword property (under the Summary Information property set) of a folder.
I am aware that I can accomplish this using the StgCreatePropSetStg() function, which takes an IStorage object that will contain the property sets. I can then get an IStorage using StgCreateStorageEx().
One thing that bothers me is that I don't know how to make the generated IStorage object point to the path of the folder that I want to change the property.
I've tried to modify the sample in the documentation and ended up with this:
#include <stdio.h>
#include <windows.h>
#include <ole2.h>
int main() {
HRESULT hr = S_OK;
IPropertySetStorage *pPropSetStg = NULL;
IPropertyStorage *pPropStg = NULL;
WCHAR *pwszError = L"";
PROPSPEC propspec;
PROPVARIANT propvarWrite;
PROPVARIANT propvarRead;
try
{
// Create a file and a property set within it.
// ~~~~~ I`m not sure how to make an IStorage object point to a folder path
hr = StgCreateStorageEx( L"WriteRead.stg",
STGM_CREATE|STGM_SHARE_EXCLUSIVE|STGM_READWRITE,
STGFMT_STORAGE,
// STGFMT_STORAGE => Structured Storage
// property sets
// STGFMT_FILE => NTFS file system
// property sets
0, NULL, NULL,
IID_IPropertySetStorage,
reinterpret_cast<void**>(&pPropSetStg) );
if( FAILED(hr) ) throw L"Failed StgCreateStorageEx";
hr = pPropSetStg->Create( fmtid, NULL, PROPSETFLAG_DEFAULT,
STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE,
&pPropStg );
if( FAILED(hr) ) throw L"Failed IPropertySetStorage::Create";
// Write a Unicode string property to the property set
propspec.ulKind = PRSPEC_LPWSTR;
propspec.lpwstr = L"PIDSI_KEYWORDS";
propvarWrite.vt = VT_LPWSTR;
propvarWrite.pwszVal = L"Testing Tag";
hr = pPropStg->WriteMultiple( 1, &propspec, &propvarWrite,
PID_FIRST_USABLE );
if( FAILED(hr) )
throw L"Failed IPropertyStorage::WriteMultiple";
// Commit changes to the property set.
hr = pPropStg->Commit(STGC_DEFAULT);
if( FAILED(hr) )
throw L"Failed IPropertyStorage::Commit";
// Close and reopen everything.
pPropStg->Release(); pPropStg = NULL;
pPropSetStg->Release(); pPropSetStg = NULL;
}
catch( const WCHAR *pwszError )
{
wprintf( L"Error: %s (hr=%08x)\n", pwszError, hr );
}
PropVariantClear( &propvarRead );
if( pPropStg ) pPropStg->Release();
if( pPropSetStg ) pPropSetStg->Release();
}
Having said all of that,
My questions is: How to make the generated IStorage object point to a folder path?
Also, if I have some misconception about how IStorage works, please correct me.
I am looking for a reliable method to map a DirectShow capture device GUID to its corresponding waveID value.
I found the following project by Chris_P:
The solution works great, and it relies on an a rather obscure IKsPropertySet interface to retrieve the mapping.
Unfortunately, if I attempt the same technique from a C++/CLI library, the code fails with E_NOTIMPL (this behavior has been described on this question, - see the answer by Vladimir Hmelyoff)
So, I figured that I could write a simple console-based auxiliary app to retrieve the mappings and print them. My library could then launch this auxiliary app and parse the redirected output to obtain the mappings.
The console program runs fine, however, the GUIDs that are being passed to the enumeration callback are completely different to the ones passed by Chris_P's solution.
In fact they all share the same basic structure:
lpGuid = 0x02ad0808 {BDF35A00-B9AC-11D0-A619-00AA00A7C000}
The only variation occurs in the last digits of the GUID, where coincidentally, they match the mapped waveId value.
Another weird thing is that the capture device description is truncated to 31 characters, as if the enumeration was being performed using WaveIn APIs!
It would almost seem that some DirectSound facade is now wrapping the WaveIn API.
Any pointers on what could be happening?, Can I disable this behavior and enumerate the same GUIDS that the WIN32 app is enumerating?
Here is the code for the console application:
#include "stdafx.h"
#include <mmreg.h>
#include <initguid.h>
#include <Dsound.h>
#include <dsconf.h>
static BOOL CALLBACK DSEnumCallback(
LPGUID lpGuid,
LPCTSTR lpcstrDescription,
LPCTSTR lpcstrModule,
LPVOID lpContext
);
static BOOL GetInfoFromDSoundGUID(GUID i_sGUID, DWORD &dwWaveID);
static HRESULT DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * ppKsPropertySet);
typedef WINUSERAPI HRESULT(WINAPI *LPFNDLLGETCLASSOBJECT) (const CLSID &, const IID &, void **);
BOOL GetInfoFromDSoundGUID(GUID i_sGUID, DWORD &dwWaveID) {
LPKSPROPERTYSET pKsPropertySet = NULL;
HRESULT hr;
BOOL retval = FALSE;
PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA psDirectSoundDeviceDescription = NULL;
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA sDirectSoundDeviceDescription;
memset(&sDirectSoundDeviceDescription, 0, sizeof(sDirectSoundDeviceDescription));
hr = DirectSoundPrivateCreate(&pKsPropertySet);
if( SUCCEEDED(hr) ) {
ULONG ulBytesReturned = 0;
sDirectSoundDeviceDescription.DeviceId = i_sGUID;
// On the first call the final size is unknown so pass the size of the struct in order to receive
// "Type" and "DataFlow" values, ulBytesReturned will be populated with bytes required for struct+strings.
hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice,
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION,
NULL,
0,
&sDirectSoundDeviceDescription,
sizeof(sDirectSoundDeviceDescription),
&ulBytesReturned
);
if( ulBytesReturned ) {
// On the first call it notifies us of the required amount of memory in order to receive the strings.
// Allocate the required memory, the strings will be pointed to the memory space directly after the struct.
psDirectSoundDeviceDescription = (PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA)new BYTE[ulBytesReturned];
*psDirectSoundDeviceDescription = sDirectSoundDeviceDescription;
hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice,
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION,
NULL,
0,
psDirectSoundDeviceDescription,
ulBytesReturned,
&ulBytesReturned
);
dwWaveID = psDirectSoundDeviceDescription->WaveDeviceId;
delete[] psDirectSoundDeviceDescription;
retval = TRUE;
}
pKsPropertySet->Release();
}
return retval;
}
HRESULT DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * ppKsPropertySet) {
HMODULE hLibDsound = NULL;
LPFNDLLGETCLASSOBJECT pfnDllGetClassObject = NULL;
LPCLASSFACTORY pClassFactory = NULL;
LPKSPROPERTYSET pKsPropertySet = NULL;
HRESULT hr = DS_OK;
// Load dsound.dll
hLibDsound = LoadLibrary(TEXT("dsound.dll"));
if( !hLibDsound ) {
hr = DSERR_GENERIC;
}
// Find DllGetClassObject
if( SUCCEEDED(hr) ) {
pfnDllGetClassObject =
(LPFNDLLGETCLASSOBJECT)GetProcAddress(hLibDsound, "DllGetClassObject");
if( !pfnDllGetClassObject ) {
hr = DSERR_GENERIC;
}
}
// Create a class factory object
if( SUCCEEDED(hr) ) {
hr = pfnDllGetClassObject(CLSID_DirectSoundPrivate, IID_IClassFactory, (LPVOID *)&pClassFactory);
}
// Create the DirectSoundPrivate object and query for an IKsPropertySet
// interface
if( SUCCEEDED(hr) ) {
hr = pClassFactory->CreateInstance(NULL, IID_IKsPropertySet, (LPVOID *)&pKsPropertySet);
}
// Release the class factory
if( pClassFactory ) {
pClassFactory->Release();
}
// Handle final success or failure
if( SUCCEEDED(hr) ) {
*ppKsPropertySet = pKsPropertySet;
} else if( pKsPropertySet ) {
pKsPropertySet->Release();
}
FreeLibrary(hLibDsound);
return hr;
}
BOOL CALLBACK DSEnumCallback(
LPGUID lpGuid,
LPCTSTR lpcstrDescription,
LPCTSTR lpcstrModule,
LPVOID lpContext
) {
LPWSTR psz = NULL;
StringFromCLSID(*lpGuid, &psz);
DWORD WaveID = 0xFFFFFFFF;
if( lpGuid ) {
GUID i_guid = *lpGuid;
GetInfoFromDSoundGUID(i_guid, WaveID);
}
if( WaveID != 0xFFFFFFFF )
wprintf(_T("%d %s\r\n"), WaveID, psz);
if( psz ) {
CoTaskMemFree(psz);
}
return TRUE;
}
int main()
{
DirectSoundCaptureEnumerate(DSEnumCallback, NULL);
Sleep(10000);
return 0;
}
It turns out I was not initializing COM.
I added the following snippet at the beginning of my main() procedure and the program retrieved the expected GUIDs:
HRESULT hr = NULL;
hr = CoInitialize(NULL);
if( FAILED(hr) ) {
printf("Failed to initialize COM");
return -1;
}
So I guess that if COM is not initialized, the DirectSound engine falls back to the WaveIn API (creating a DirectShow facade around it).
Does anyone know how to get hold of the Excel.Application IDispatch* pointer associated with an excel process into which an dll has been loaded?
A key thing here is that the process is excel.exe, and the pointer I need must belong to that process. Using the Running Object Table will not fly since Excel only registers its first instance with that.
I'm hoping there is some low-level COM trickery, but I'm not an expert in that field.
EDITED II Code is under the WTFPL license version 2.
EDITED: Add PID parameter to allow filtering when several Excel processes are currently running, as per comment suggestion from #EricBrown.
I managed to get a working IDispatch* to an Excel "Application" object without using the ROT. The trick is to use MSAA. My code works as a stand alone console application, but I think that if the code is executed in an Excel process, via DLL Injection, it MAY works fine. You may have to be in a dedicated thread. Let me know if you want me to push the expriment to the DLL injection level.
Tested OK on Window7 64b, with a UNICODE builds (32 bits and 64 bits).
Excel version 2010 64 bits (version "14")
I get the IDispatch via the "application" property from an "Worksheet" object. Consequence: there must be an opened worksheet. In order to find the good MSSA Window, I need the class name of the Top Level Excel Frame Window. In Excel 2010, it's "XLMAIN". The class name for worksheets is "EXCEL7" and that seems to be a "standard".
I was not able to directly get a working IDispatch* from the main Excel Window, but have not tried very hard. That may involve #import with a automation DLL from Excel, in order to QueryInterface the IDispatch that MSAA gives for the main Window (that IDispatch is NOT for an Application object)
#include <atlbase.h>
#pragma comment( lib, "Oleacc.lib" )
HRESULT GetExcelAppDispatch( CComPtr<IDispatch> & spIDispatchExcelApp, DWORD dwExcelPID ) {
struct ew {
struct ep {
_TCHAR* pszClassName;
DWORD dwPID;
HWND hWnd;
};
static BOOL CALLBACK ewp( HWND hWnd, LPARAM lParam ) {
TCHAR szClassName[ 64 ];
if ( GetClassName( hWnd, szClassName, 64 ) ) {
ep* pep = reinterpret_cast<ep*>( lParam );
if ( _tcscmp( szClassName, pep->pszClassName ) == 0 ) {
if ( pep->dwPID == 0 ) {
pep->hWnd = hWnd;
return FALSE;
} else {
DWORD dwPID;
if ( GetWindowThreadProcessId( hWnd, &dwPID ) ) {
if ( dwPID == pep->dwPID ) {
pep->hWnd = hWnd;
return FALSE;
}
}
}
}
}
return TRUE;
}
};
ew::ep ep;
ep.pszClassName = _TEXT( "XLMAIN" );
ep.dwPID = dwExcelPID;
ep.hWnd = NULL;
EnumWindows( ew::ewp, reinterpret_cast<LPARAM>( &ep ) );
HWND hWndExcel = ep.hWnd;
if ( ep.hWnd == NULL ) {
printf( "Can't Find Main Excel Window with EnumWindows\n" );
return -1;
}
ep.pszClassName = _TEXT( "EXCEL7" );
ep.dwPID = 0;
ep.hWnd = NULL;
EnumChildWindows( hWndExcel, ew::ewp, reinterpret_cast<LPARAM>( &ep ) );
HWND hWndWorkSheet = ep.hWnd;
if ( hWndWorkSheet == NULL ) {
printf( "Can't Find a WorkSheet with EnumChildWindows\n" );
return -1;
}
CComPtr<IDispatch> spIDispatchWorkSheet;
HRESULT hr = AccessibleObjectFromWindow( hWndWorkSheet, OBJID_NATIVEOM, IID_IDispatch,
reinterpret_cast<void**>( &spIDispatchWorkSheet ) );
if ( FAILED( hr ) || ( spIDispatchWorkSheet == 0 ) ) {
printf( "AccessibleObjectFromWindow Failed\n" );
return hr;
}
CComVariant vExcelApp;
hr = spIDispatchWorkSheet.GetPropertyByName( CComBSTR( "Application" ), &vExcelApp );
if ( SUCCEEDED( hr ) && ( vExcelApp.vt == VT_DISPATCH ) ) {
spIDispatchExcelApp = vExcelApp.pdispVal;
return S_OK;
}
return hr;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwExcelPID = 0;
if ( argc > 1 ) dwExcelPID = _ttol( argv[ 1 ] );
HRESULT hr = CoInitialize( NULL );
bool bCoUnInitializeTodo = false;
if ( SUCCEEDED( hr ) ) {
bCoUnInitializeTodo = true;
CComPtr<IDispatch> spDispatchExcelApp;
hr = GetExcelAppDispatch( spDispatchExcelApp, dwExcelPID );
if ( SUCCEEDED( hr ) && spDispatchExcelApp ) {
CComVariant vExcelVer;
hr = spDispatchExcelApp.GetPropertyByName( CComBSTR( "Version" ), &vExcelVer );
if ( SUCCEEDED( hr ) && ( vExcelVer.vt == VT_BSTR ) ) {
wprintf( L"Excel Version is %s\n", vExcelVer.bstrVal );
}
}
}
if ( bCoUnInitializeTodo ) CoUninitialize();
return 0;
}
You should be able to find out how to do this by reviewing the code in ExcelDNA. This project contains code that hooks back into Excel from the extension library. The code is likely to be more elaborate that you need, but will implement the reference you require.
This is how I do it: (acknowledge #manuell). dispatch_wrapper is a class, here is the constructor to set m_disp_application:
dispatch_wrapper(void)
{
DWORD target_process_id = ::GetProcessId(::GetCurrentProcess());
if (getProcessName() == "excel.exe"){
HWND hwnd = ::FindWindowEx(0, 0, "XLMAIN", NULL);
while (hwnd){
DWORD process_id;
::GetWindowThreadProcessId(hwnd, &process_id);
if (process_id == target_process_id){
HWND hwnd_desk = ::FindWindowEx(hwnd, 0, "XLDESK", NULL);
HWND hwnd_7 = ::FindWindowEx(hwnd_desk, 0, "EXCEL7", NULL);
IDispatch* p = nullptr;
if (SUCCEEDED(::AccessibleObjectFromWindow(hwnd_7, OBJID_NATIVEOM, IID_IDispatch, (void**)&p))){
LPOLESTR name[1] = {L"Application"};
DISPID dispid;
if (SUCCEEDED(p->GetIDsOfNames(IID_NULL, name, 1U, LOCALE_SYSTEM_DEFAULT, &dispid))){
CComVariant v;
DISPPARAMS dp;
::memset(&dp, NULL, sizeof(DISPPARAMS));
EXCEPINFO ei;
::memset(&ei, NULL, sizeof(EXCEPINFO));
if (SUCCEEDED(p->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dp, &v, &ei, NULL))){
if (v.vt == VT_DISPATCH){
m_disp_application = v.pdispVal;
m_disp_application->AddRef();
return;
}
}
}
}
}
hwnd = ::FindWindowEx(0, hwnd, "XLMAIN", NULL);
}
}
m_disp_application = nullptr;
}
getProcessName() returns lower case.
Because Office applications register their documents in the ROT, you can attach to instances beside the first one (which is already in the ROT) by getting IDispatch for documents in the ROT, then you can use document.Application.hwnd (this is VBA, you need to translate to IDispatch::GetIDsOfNames and IDispatch::Invoke with DISPATCH_PROPERTYGET) to get the window handles of all Excel instances.
Now you have a mapping between IDispatch and Windows handles of all Excel instances, it is time to find your own Excel instance. You can call GetWindowThreadProcessId on the window handles to get the process ids, then compare to your own process id returned by GetCurrentProcessId to see which excel window belongs to your current process, and look up in the HWND to IDispatch mapping to find your current Excel application's IDispatch interface.
I have a DirectShow graph with a "Microsoft DVBT Network Provider", "AVerMedia BDA DVBT Tuner", "AVerMEdia BDA Digital Capture", "Sample Grabber" and "NULL Renderer".
These filters are connected.
Beside that I also have an "MPEG-2 Demultiplexer" and a "BDA MPEG2 Transport Information Filter", but these two filters are NOT connected! It seems like they have to be here in order to run the graph.
When I start the graph, I'm receiving TS data, but no matter what I do, I'm not able to put the tuning request. I can only capture the MUX data from the last tuned frequency with some other application like Windows Media Center.
Here is the code for putting the tune request:
// creating tuning space
CComPtr<IDVBTuningSpace> pDVBTuningSpace;<br>
hr = pDVBTuningSpace.CoCreateInstance( __uuidof( DVBTuningSpace ) );
WCHAR szFriendlyName[ 64 ] = L"Local DVB-T Digital Antenna";<br> BSTR bstrFriendlyName = SysAllocString( szFriendlyName );
hr = pDVBTuningSpace->put_UniqueName( bstrFriendlyName );<br>
hr = pDVBTuningSpace->put_FriendlyName( bstrFriendlyName );
SysFreeString( bstrFriendlyName );
CComBSTR clsid_dvbt = ("{216C62DF-6D7F-4e9a-8571-05F14EDB766A}");<br>
hr = pDVBTuningSpace->put_NetworkType( clsid_dvbt );<br>
hr = pDVBTuningSpace->put_SystemType( DVB_Terrestrial );<br>
// creating tune request<br>
CComPtr<ITuneRequest> pTuneRequest;
hr = pDVBTuningSpace->CreateTuneRequest( &pTuneRequest );
CComQIPtr<IDVBTuneRequest> pDVBTuneRequest( pTuneRequest );
hr = pDVBTuneRequest->put_ONID( -1 );<br>
hr = pDVBTuneRequest->put_TSID( -1 );<br>
hr = pDVBTuneRequest->put_SID( -1 );
// locator<br>
CComPtr<IDVBTLocator> pDVBTLocator;
hr = pDVBTLocator.CoCreateInstance( __uuidof( DVBTLocator ) );<br>
hr = pDVBTLocator->put_Bandwidth( 8 );<br>
hr = pDVBTLocator->put_CarrierFrequency( 506000 );
hr = pDVBTuneRequest->put_Locator( pDVBTLocator );
CComQIPtr<ITuner> pTuner( pNetworkProvider_ );
hr = pTuner->put_TuneRequest( pDVBTuneRequest );
This is executed immediately after adding the "Microsoft DVBT Network Provider" filter in the graph.
All "hr" values from the above code are S_OK.
What am I doing wrong? Or, did I miss something big in this "tune request" thing.
(Bandwidth and frequency values are correct)
I think put_Bandwidth( 8 ) is wrong, it should be a bandwidth in Hz. Anyway, I show you some code I use. Maybe it helps.
HRESULT hr;
CComBSTR TuningName;
hr = pDVBTuningSpace2.CoCreateInstance(CLSID_DVBTuningSpace);
hr = pDVBTuningSpace2->put_SystemType(DVB_Terrestrial);
TuningName = L"My DVB-T";
hr = pDVBTuningSpace2->put__NetworkType(CLSID_DVBTNetworkProvider);
CComPtr <IDVBTLocator> pDVBTLocator;
hr = pDVBTLocator.CoCreateInstance(CLSID_DVBTLocator);
hr = pDVBTLocator->put_CarrierFrequency(config->GetFreq());
hr = pDVBTLocator->put_Bandwidth(config->GetSymbolRate());
hr = pDVBTuningSpace2->put_DefaultLocator(pDVBTLocator);
hr = pDVBTuningSpace2->put_UniqueName(TuningName);
hr = pDVBTuningSpace2->put_FriendlyName(TuningName);
hr = pDVBTuningSpace2->put_FrequencyMapping(L"");
CComPtr <ITuningSpaceContainer> pTuningSpaceContainer;
hr = pTuningSpaceContainer.CoCreateInstance(CLSID_SystemTuningSpaces);
VARIANT tiIndex;
hr = pTuningSpaceContainer->Add(pDVBTuningSpace2,&tiIndex);
if (!SUCCEEDED(hr)) {
// Get the enumerator for the collection.
CComPtr<IEnumTuningSpaces> pTuningSpaceEnum;
hr = pTuningSpaceContainer->get_EnumTuningSpaces(&pTuningSpaceEnum);
if (SUCCEEDED(hr)) {
// Loop through the collection.
CComPtr<ITuningSpace> pTuningSpace;
//ITuningSpace *pTuningSpace;
tiIndex.intVal=0;
while (S_OK == pTuningSpaceEnum->Next(1, &pTuningSpace, NULL)) {
USES_CONVERSION;
BSTR Name;
hr = pTuningSpace->get_UniqueName(&Name);
if (SUCCEEDED(hr)) {
if (wcscmp(OLE2W(Name), TuningName) == 0) {
hr = pTuningSpaceContainer->put_Item(tiIndex,pDVBTuningSpace2);
}
SysFreeString(Name);
}
tiIndex.intVal++;
//pTuningSpace->Release();
pTuningSpace.Release();
}
}
}
CComPtr<ITuneRequest> pTuneRequest;
hr = pDVBTuningSpace2->CreateTuneRequest(&pTuneRequest);
CComQIPtr<IDVBTuneRequest> pDVBTuneRequest(pTuneRequest);
if(pDVBTuneRequest) {
hr = pDVBTuneRequest->put_SID(config->GetSid());
hr = pDVBTuneRequest->put_TSID(config->GetTsid());
hr = pDVBTuneRequest->put_ONID(config->GetOnid());
}
GUID CLSIDNetworkType;
hr = pDVBTuningSpace2->get__NetworkType(&CLSIDNetworkType);
hr = CoCreateInstance(CLSIDNetworkType, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (void **) &pNetworkProvider);
hr = graph->AddFilter(pNetworkProvider,L"Network Provider");
// Query for ITuner.
CComQIPtr<ITuner> pTuner(pNetworkProvider);
if (pTuner) {
// Submit the tune request to the network provider.
hr = pTuner->put_TuneRequest(pTuneRequest);
}
hr = graph->AddFilter(pBdaNetworkTuner,L"BDA Source");
hr = ConnectFilters(pNetworkProvider,pBdaNetworkTuner);
CComPtr<IBaseFilter> pBdaReceiver;
hr = FindDevice(KSCATEGORY_BDA_RECEIVER_COMPONENT, &pBdaReceiver, 0, 0, 0);
hr = graph->AddFilter(pBdaReceiver,L"BDA Receiver");
hr = ConnectFilters(pBdaNetworkTuner,pBdaReceiver);
CComPtr<IBaseFilter> pMpegDemux;
hr = pMpegDemux.CoCreateInstance(CLSID_MPEG2Demultiplexer);
hr = graph->AddFilter(pMpegDemux,L"MPEG Demux");
hr = ConnectFilters(pBdaReceiver,pMpegDemux);
You are doing some things in a different order, but I'm not sure if it matters.
I found a bit of code that gets me access to the raw pixel data from my webcam. However I need to know the image width, height, pixel format and preferably the data stride(pitch, memory padding or whatever you want to call it) if its ever gonna be something other than the width * bytes per pixel
#include <windows.h>
#include <dshow.h>
#pragma comment(lib,"Strmiids.lib")
#define DsHook(a,b,c) if (!c##_) { INT_PTR* p=b+*(INT_PTR**)a; VirtualProtect(&c##_,4,PAGE_EXECUTE_READWRITE,&no);\
*(INT_PTR*)&c##_=*p; VirtualProtect(p, 4,PAGE_EXECUTE_READWRITE,&no); *p=(INT_PTR)c; }
// Here you get image video data in buf / len. Process it before calling Receive_ because renderer dealocates it.
HRESULT ( __stdcall * Receive_ ) ( void* inst, IMediaSample *smp ) ;
HRESULT __stdcall Receive ( void* inst, IMediaSample *smp ) {
BYTE* buf; smp->GetPointer(&buf); DWORD len = smp->GetActualDataLength();
//AM_MEDIA_TYPE* info;
//smp->GetMediaType(&info);
HRESULT ret = Receive_ ( inst, smp );
return ret;
}
int WINAPI WinMain(HINSTANCE inst,HINSTANCE prev,LPSTR cmd,int show){
HRESULT hr = CoInitialize(0); MSG msg={0}; DWORD no;
IGraphBuilder* graph= 0; hr = CoCreateInstance( CLSID_FilterGraph, 0, CLSCTX_INPROC,IID_IGraphBuilder, (void **)&graph );
IMediaControl* ctrl = 0; hr = graph->QueryInterface( IID_IMediaControl, (void **)&ctrl );
ICreateDevEnum* devs = 0; hr = CoCreateInstance (CLSID_SystemDeviceEnum, 0, CLSCTX_INPROC, IID_ICreateDevEnum, (void **) &devs);
IEnumMoniker* cams = 0; hr = devs?devs->CreateClassEnumerator (CLSID_VideoInputDeviceCategory, &cams, 0):0;
IMoniker* mon = 0; hr = cams->Next (1,&mon,0); // get first found capture device (webcam?)
IBaseFilter* cam = 0; hr = mon->BindToObject(0,0,IID_IBaseFilter, (void**)&cam);
hr = graph->AddFilter(cam, L"Capture Source"); // add web cam to graph as source
IEnumPins* pins = 0; hr = cam?cam->EnumPins(&pins):0; // we need output pin to autogenerate rest of the graph
IPin* pin = 0; hr = pins?pins->Next(1,&pin, 0):0; // via graph->Render
hr = graph->Render(pin); // graph builder now builds whole filter chain including MJPG decompression on some webcams
IEnumFilters* fil = 0; hr = graph->EnumFilters(&fil); // from all newly added filters
IBaseFilter* rnd = 0; hr = fil->Next(1,&rnd,0); // we find last one (renderer)
hr = rnd->EnumPins(&pins); // because data we are intersted in are pumped to renderers input pin
hr = pins->Next(1,&pin, 0); // via Receive member of IMemInputPin interface
IMemInputPin* mem = 0; hr = pin->QueryInterface(IID_IMemInputPin,(void**)&mem);
DsHook(mem,6,Receive); // so we redirect it to our own proc to grab image data
hr = ctrl->Run();
while ( GetMessage( &msg, 0, 0, 0 ) ) {
TranslateMessage( &msg );
DispatchMessage( &msg );
}
};
Bonus points if you can tell me how get this thing not to render a window but still get me access to the image data.
That's really ugly. Please don't do that. Insert a pass-through filter like the sample grabber instead (as I replied to your other post on the same topic). Connecting the sample grabber to the null renderer gets you the bits in a clean, safe way without rendering the image.
To get the stride, you need to get the media type, either through ISampleGrabber or IPin::ConnectionMediaType. The format block will be either a VIDEOINFOHEADER or a VIDEOINFOHEADER2 (check the format GUID). The bitmapinfo header biWidth and biHeight defines the bitmap dimensions (and hence stride). If the RECT is not empty, then that defines the relevant image area within the bitmap.
I'm going to have to wash my hands now after touching this post.
I am sorry for you. When the interface was created there were probably not the best programmer to it.
// Here you get image video data in buf / len. Process it before calling Receive_ because renderer dealocates it.
BITMAPINFOHEADER bmpInfo; // current bitmap header info
int stride;
HRESULT ( __stdcall * Receive_ ) ( void* inst, IMediaSample *smp ) ;
HRESULT __stdcall Receive ( void* inst, IMediaSample *smp )
{
BYTE* buf; smp->GetPointer(&buf); DWORD len = smp->GetActualDataLength();
HRESULT ret = Receive_ ( inst, smp );
AM_MEDIA_TYPE* info;
HRESULT hr = smp->GetMediaType(&info);
if ( hr != S_OK )
{ //TODO: error }
else
{
if ( type->formattype == FORMAT_VideoInfo )
{
const VIDEOINFOHEADER * vi = reinterpret_cast<VIDEOINFOHEADER*>( type->pbFormat );
const BITMAPINFOHEADER & bmiHeader = vi->bmiHeader;
//! now the bmiHeader.biWidth contains the data stride
stride = bmiHeader.biWidth;
bmpInfo = bmiHeader;
int width = ( vi->rcTarget.right - vi->rcTarget.left );
//! replace the data stride be the actual width
if ( width != 0 )
bmpInfo.biWidth = width;
}
else
{ // unsupported format }
}
DeleteMediaType( info );
return ret;
}
Here's how to add the Null Renderer that suppresses the rendering window. Add directly after creating the IGraphBuilder*
//create null renderer and add null renderer to graph
IBaseFilter *m_pNULLRenderer; hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **)&m_pNULLRenderer);
hr = graph->AddFilter(m_pNULLRenderer, L"Null Renderer");
That dshook hack is the only elegant directshow code of which I am aware.
In my experience, the DirectShow API is a convoluted nightmare, requiring hundreds of lines of code to do even the simplest operation, and adapting a whole programming paradigm in order to access your web camera. So if this code does the job for you, as it did for me, use it and enjoy fewer lines of code to maintain.