I'm trying to add audio capability to a capture source filter in order to make a virtual cam with audio. Beginning with the TMH's and rdp's code I extended it with another pin, called "Audio":
CUnknown * WINAPI CVCam::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
ASSERT(phr);
CUnknown *punk = new CVCam(lpunk, phr);
return punk;
}
CVCam::CVCam(LPUNKNOWN lpunk, HRESULT *phr) : CSource(LPCSTR(FILTER_NAME), lpunk, CLSID_VirtualCam)
{
ASSERT(phr);
CAutoLock cAutoLock(&m_cStateLock);
m_paStreams = (CSourceStream **) new CVCamStream*[2];
m_paStreams[0] = new CVCamStream(phr, this, L"Video");
m_paStreams[1] = new CVAudioStream(phr, this, L"Audio");
}
HRESULT CVCam::QueryInterface(REFIID riid, void **ppv)
{
if (riid == _uuidof(IAMStreamConfig) || riid == _uuidof(IKsPropertySet))
{
HRESULT hr;
hr = m_paStreams[0]->QueryInterface(riid, ppv);
if (hr != S_OK) return hr;
hr = m_paStreams[1]->QueryInterface(riid, ppv);
if (hr != S_OK) return hr;
}
else return CSource::QueryInterface(riid, ppv);
return S_OK;
}
CVAudioStream::CVAudioStream(HRESULT *phr, CVCam *pParent, LPCWSTR pPinName) : CSourceStream(LPCSTR(pPinName), phr, pParent, pPinName), m_pParent(pParent)
{
GetMediaType(0, &m_mt);
}
CVAudioStream::~CVAudioStream()
{
}
HRESULT CVAudioStream::QueryInterface(REFIID riid, void **ppv)
{
if (riid == _uuidof(IAMStreamConfig)) *ppv = (IAMStreamConfig*)this;
else if (riid == _uuidof(IKsPropertySet)) *ppv = (IKsPropertySet*)this;
else if (riid == _uuidof(IAMBufferNegotiation)) *ppv = (IAMBufferNegotiation*)this;
else return CSourceStream::QueryInterface(riid, ppv);
AddRef();
return S_OK;
}
HRESULT CVAudioStream::FillBuffer(IMediaSample *pms)
{
// fill buffer with Windows audio samples
return NOERROR;
}
STDMETHODIMP CVAudioStream::Notify(IBaseFilter * pSender, Quality q)
{
return E_NOTIMPL;
}
HRESULT CVAudioStream::SetMediaType(const CMediaType *pmt)
{
HRESULT hr = CSourceStream::SetMediaType(pmt);
return hr;
}
HRESULT setupPwfex(WAVEFORMATEX *pwfex, AM_MEDIA_TYPE *pmt) {
pwfex->wFormatTag = WAVE_FORMAT_PCM;
pwfex->cbSize = 0;
pwfex->nChannels = 2;
HRESULT hr;
pwfex->nSamplesPerSec = 11025;
pwfex->wBitsPerSample = 16;
pwfex->nBlockAlign = (WORD)((pwfex->wBitsPerSample * pwfex->nChannels) / 8);
pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
hr = ::CreateAudioMediaType(pwfex, pmt, FALSE);
return hr;
}
/*HRESULT CVAudioStream::setAsNormal(CMediaType *pmt)
{
WAVEFORMATEX *pwfex;
pwfex = (WAVEFORMATEX *)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
ZeroMemory(pwfex, sizeof(WAVEFORMATEX));
if (NULL == pwfex) return E_OUTOFMEMORY;
return setupPwfex(pwfex, pmt);
}*/
HRESULT CVAudioStream::GetMediaType(int iPosition, CMediaType *pmt)
{
if (iPosition < 0) return E_INVALIDARG;
if (iPosition > 0) return VFW_S_NO_MORE_ITEMS;
if (iPosition == 0)
{
*pmt = m_mt;
return S_OK;
}
WAVEFORMATEX *pwfex = (WAVEFORMATEX *)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
setupPwfex(pwfex, pmt);
return S_OK;
}
HRESULT CVAudioStream::CheckMediaType(const CMediaType *pMediaType)
{
int cbFormat = pMediaType->cbFormat;
if (*pMediaType != m_mt) return E_INVALIDARG;
return S_OK;
}
const int WaveBufferChunkSize = 16 * 1024;
HRESULT CVAudioStream::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties)
{
CheckPointer(pAlloc, E_POINTER);
CheckPointer(pProperties, E_POINTER);
WAVEFORMATEX *pwfexCurrent = (WAVEFORMATEX*)m_mt.Format();
pProperties->cBuffers = 1;
pProperties->cbBuffer = expectedMaxBufferSize;
ALLOCATOR_PROPERTIES Actual;
HRESULT hr = pAlloc->SetProperties(pProperties, &Actual);
if (FAILED(hr)) return hr;
if (Actual.cbBuffer < pProperties->cbBuffer) return E_FAIL;
return NOERROR;
}
HRESULT CVAudioStream::OnThreadCreate()
{
//GetMediaType(0, &m_mt);
//HRESULT hr = LoopbackCaptureSetup();
//if (FAILED(hr)) return hr;
return NOERROR;
}
HRESULT STDMETHODCALLTYPE CVAudioStream::SetFormat(AM_MEDIA_TYPE *pmt)
{
if (!pmt) return S_OK;
if (CheckMediaType((CMediaType *)pmt) != S_OK) return E_FAIL;
m_mt = *pmt;
IPin* pin;
ConnectedTo(&pin);
if (pin)
{
IFilterGraph *pGraph = m_pParent->GetGraph();
pGraph->Reconnect(this);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE CVAudioStream::GetFormat(AM_MEDIA_TYPE **ppmt)
{
*ppmt = CreateMediaType(&m_mt);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CVAudioStream::GetNumberOfCapabilities(int *piCount, int *piSize)
{
*piCount = 1;
*piSize = sizeof(AUDIO_STREAM_CONFIG_CAPS);
return S_OK;
}
HRESULT STDMETHODCALLTYPE CVAudioStream::GetStreamCaps(int iIndex, AM_MEDIA_TYPE **pmt, BYTE *pSCC)
{
if (iIndex < 0) return E_INVALIDARG;
if (iIndex > 0) return S_FALSE;
if (pSCC == NULL) return E_POINTER;
*pmt = CreateMediaType(&m_mt);
if (*pmt == NULL) return E_OUTOFMEMORY;
DECLARE_PTR(WAVEFORMATEX, pAudioFormat, (*pmt)->pbFormat);
AM_MEDIA_TYPE * pm = *pmt;
setupPwfex(pAudioFormat, pm);
AUDIO_STREAM_CONFIG_CAPS* pASCC = (AUDIO_STREAM_CONFIG_CAPS*)pSCC;
ZeroMemory(pSCC, sizeof(AUDIO_STREAM_CONFIG_CAPS));
pASCC->guid = MEDIATYPE_Audio;
pASCC->MaximumChannels = pAudioFormat->nChannels;
pASCC->MinimumChannels = pAudioFormat->nChannels;
pASCC->ChannelsGranularity = 1; // doesn't matter
pASCC->MaximumSampleFrequency = pAudioFormat->nSamplesPerSec;
pASCC->MinimumSampleFrequency = pAudioFormat->nSamplesPerSec;
pASCC->SampleFrequencyGranularity = 11025; // doesn't matter
pASCC->MaximumBitsPerSample = pAudioFormat->wBitsPerSample;
pASCC->MinimumBitsPerSample = pAudioFormat->wBitsPerSample;
pASCC->BitsPerSampleGranularity = 16; // doesn't matter
return S_OK;
}
HRESULT CVAudioStream::Set(REFGUID guidPropSet, DWORD dwID, void *pInstanceData, DWORD cbInstanceData, void *pPropData, DWORD cbPropData)
{
return E_NOTIMPL;
}
HRESULT CVAudioStream::Get(
REFGUID guidPropSet,
DWORD dwPropID,
void *pInstanceData,
DWORD cbInstanceData,
void *pPropData,
DWORD cbPropData,
DWORD *pcbReturned
)
{
if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED;
if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED;
if (pPropData == NULL && pcbReturned == NULL) return E_POINTER;
if (pcbReturned) *pcbReturned = sizeof(GUID);
if (pPropData == NULL) return S_OK;
if (cbPropData < sizeof(GUID)) return E_UNEXPECTED;
*(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
return S_OK;
}
HRESULT CVAudioStream::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
if (guidPropSet != AMPROPSETID_Pin) return E_PROP_SET_UNSUPPORTED;
if (dwPropID != AMPROPERTY_PIN_CATEGORY) return E_PROP_ID_UNSUPPORTED;
if (pTypeSupport) *pTypeSupport = KSPROPERTY_SUPPORT_GET;
return S_OK;
}
My first issue is when I insert the filter in GraphStudioNext and open its properties page. The Audio pin shows the following (incorrect) information:
majorType = GUID_NULL
subType = GUID_NULL
formattype = GUID_NULL
Of course I cannot connect nothing to that pin because is not valid.
I was expecting something like MEDIATYPE_Audio because I set up it:
DEFINE_GUID(CLSID_VirtualCam, 0x8e14549a, 0xdb61, 0x4309, 0xaf, 0xa1, 0x35, 0x78, 0xe9, 0x27, 0xe9, 0x33);
const AMOVIESETUP_MEDIATYPE AMSMediaTypesVideo =
{
&MEDIATYPE_Video,
&MEDIASUBTYPE_NULL
};
const AMOVIESETUP_MEDIATYPE AMSMediaTypesAudio =
{
&MEDIATYPE_Audio,
&MEDIASUBTYPE_NULL
};
const AMOVIESETUP_PIN AMSPinVCam[] =
{
{
L"Video", // Pin string name
FALSE, // Is it rendered
TRUE, // Is it an output
FALSE, // Can we have none
FALSE, // Can we have many
&CLSID_NULL, // Connects to filter
NULL, // Connects to pin
1, // Number of types
&AMSMediaTypesVideo // Pin Media types
},
{
L"Audio", // Pin string name
FALSE, // Is it rendered
TRUE, // Is it an output
FALSE, // Can we have none
FALSE, // Can we have many
&CLSID_NULL, // Connects to filter
NULL, // Connects to pin
1, // Number of types
&AMSMediaTypesAudio // Pin Media types
}
};
const AMOVIESETUP_FILTER AMSFilterVCam =
{
&CLSID_VirtualCam, // Filter CLSID
FILTER_NAME, // String name
MERIT_DO_NOT_USE, // Filter merit
2, // Number pins
AMSPinVCam // Pin details
};
CFactoryTemplate g_Templates[] =
{
{
FILTER_NAME,
&CLSID_VirtualCam,
CVCam::CreateInstance,
NULL,
&AMSFilterVCam
},
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
STDAPI RegisterFilters( BOOL bRegister )
{
HRESULT hr = NOERROR;
WCHAR achFileName[MAX_PATH];
char achTemp[MAX_PATH];
ASSERT(g_hInst != 0);
if( 0 == GetModuleFileNameA(g_hInst, achTemp, sizeof(achTemp))) return AmHresultFromWin32(GetLastError());
MultiByteToWideChar(CP_ACP, 0L, achTemp, lstrlenA(achTemp) + 1, achFileName, NUMELMS(achFileName));
hr = CoInitialize(0);
if(bRegister)
{
hr = AMovieSetupRegisterServer(CLSID_VirtualCam, FILTER_NAME, achFileName, L"Both", L"InprocServer32");
}
if( SUCCEEDED(hr) )
{
IFilterMapper2 *fm = 0;
hr = CreateComObject( CLSID_FilterMapper2, IID_IFilterMapper2, fm );
if( SUCCEEDED(hr) )
{
if(bRegister)
{
IMoniker *pMoniker = 0;
REGFILTER2 rf2;
rf2.dwVersion = 1;
rf2.dwMerit = MERIT_DO_NOT_USE;
rf2.cPins = 2;
rf2.rgPins = AMSPinVCam;
hr = fm->RegisterFilter(CLSID_VirtualCam, FILTER_NAME, &pMoniker, &CLSID_VideoInputDeviceCategory, NULL, &rf2);
}
else
{
hr = fm->UnregisterFilter(&CLSID_VideoInputDeviceCategory, 0, CLSID_VirtualCam);
}
}
if(fm) fm->Release();
}
if( SUCCEEDED(hr) && !bRegister ) hr = AMovieSetupUnregisterServer( CLSID_VirtualCam );
CoFreeUnusedLibraries();
CoUninitialize();
return hr;
}
Second issue: there's also a "Latency" tab but when I click on it GraphStudioNext hangs forever and the VS debugger (which is attached to that process) says nothing. What piece of code control this tab?
UPDATE
Solved first issue:
HRESULT CVAudioStream::GetMediaType(int iPosition, CMediaType *pmt)
{
if (iPosition < 0) return E_INVALIDARG;
if (iPosition > 0) return VFW_S_NO_MORE_ITEMS;
WAVEFORMATEX *pwfex = (WAVEFORMATEX *)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
setupPwfex(pwfex, pmt);
pmt->SetType(&MEDIATYPE_Audio);
pmt->SetFormatType(&FORMAT_WaveFormatEx);
pmt->SetTemporalCompression(FALSE);
pmt->SetSubtype(&MEDIASUBTYPE_PCM);
pmt->SetSampleSize(pwfex->nBlockAlign);
return S_OK;
}
Short version: Microsoft does not really offer an API to supply virtual audio device so that it's nicely accepted by the applications as if it is a real audio capture device.
If virtual video capture filters often work for historical reasons, it is not the case with audio. A kernel level driver that implements an audio device is the way to add an audio device that applications would recognize.
Latency tab shows up because you pretended that you are implementing IAMBufferNegotiation interface:
if (riid == _uuidof(IAMBufferNegotiation)) *ppv = (IAMBufferNegotiation*)this;
The implementation is likely to be incorrect, which results in certain unexpected behavior (freeze, crash etc).
Adding audio pin on the same filter is possible but might be not the best idea, if you expect the stream to be picked as an artificial source. It makes sense in general but real devices almost never expose audio streams like this.
Long story short, the only application which could utilize audio stream like this is the one you develop yourself: no well known application attempts to locate audio pin on the video source filter. For this reason implementation of
IAMStreamConfig and especially IKsPropertySet on such pin is useless.
You will not be able to register the filter under Audio Capture Sources category because you register a filter, and this filter exposes video output pin first, and only then there is some secondary audio. If you target an application that consumes audio via DirectShow (which is already pretty rare for the reasons beyond the scope of this question), you should rather develop a separate source filter. You of course can have the two filters talk to each other behind the scenes to deliver certain feed collaboratively, but in terms of DirectShow it is typical that the filters appear as independent.
...also real webcams expose two different filters and this is why in application like Skype we have to select both under video and audio devices.
Should it be better to create two completely different projects and filters: one for video and one for audio?
Real and typical camera:
Since "real" physical cameras are typically provided with kernel level drivers, their presence in DirectShow takes place through WDM Video Capture Filter which acts as a proxy and enumerates "DirectShow wrappers" of camera drivers under the same category Video Capture Sources where you would register virtual cameras.
That is, such design enables you to mix real and virtual cameras in the list of available devices, which DirectShow based application use when it comes to video capture. This approach has its limitations, which I described earlier e.g. in this question and referenced post Applicability of Virtual DirectShow Sources.
As DirectShow's successor Media Foundation have not had good reception in general, and in addition Media Foundation offers neither good backward compatibility nor video capture extensibility, a multitude of applications including Microsoft's own are still consuming video capture via DirectShow. Vice versa those who look into video capture API for Windows are also often interested in DirectShow and not the "current" API because of availability of samples and related information, API extensibility, application integration options.
It is not the case with audio, however. DirectShow audio capture was not top-notch already at the time DirectShow development stopped. Windows Vista introduces new API for audio WASAPI and DirectShow did not receive a respective connection to the new API, neither for audio capture nor for playback. Audio is simpler itself, and WASAPI was powerful and developer friendly, so developers started switching to the new API for audio related tasks. Much fewer applications use DirectShow for audio capture and your implementing virtual audio source is likely to be a miss: your device will remain "invisible" for applications consuming audio capture via WASAPI. Even if an application has a fallback code patch for Windows XP to do audio capture via DirectShow, it will hardly be a relief for you in newer OSes.
Follow up reading on audio on StackOverflow:
Directshow.net don't detect all mics in Windows 7
Write an audio source filter for use as Lync microphone
Windows Audio and Video Capture Software Paradigm
Also, you don't have to have separate projects for video and audio filters. You can mix them in the same project, they can just be independent filters registered separately.
Related
If I go to Settings on a Windows 10 (1803) computer, I have access to a page ("App Volume and Device Preferences") that lets me set the default input and output device for a running application.
How can I set these options programmatically?
Related:
Set audio endpoint devices application specific (programmatically)Seems to refer to a more specific problem, and is unanswered.
Controlling “App volume and device preferences” menu, from settings, with python 3.6, or any language(Windows). Automatic App audio device switchingToo broad, wrong site.
IAudioSessionControl2 and related familyAllows reading the device and setting volume and mute, but doesn't seem to allow changing the device
GitHub issue page for SoundSwitch also looking for APIImplies API is undocumented by design (default device being controlled by user)
Here you can enumerate all the playback devices
#include <windows.h>
#include <mmsystem.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "Winmm.lib")
int main()
{
int nSoundCardCount = waveOutGetNumDevs();
for (int i = 0; i < nSoundCardCount; i++)
{
WAVEOUTCAPS woc;
waveOutGetDevCaps(i, &woc, sizeof(woc));
cout << woc.szPname << endl;
}
system("pause");
return 0;
}
Here you need to use PolicyConfig.h and SetDefaultAudioPlaybackDevice to add .h files and interfaces. Refer to this project
1.Add the header file PolicyConfig.h
2.Add the head file and interface.
#include "Mmdeviceapi.h"
#include "PolicyConfig.h"
#include "Propidl.h"
#include "Functiondiscoverykeys_devpkey.h"
HRESULT SetDefaultAudioPlaybackDevice( LPCWSTR devID )
{
IPolicyConfigVista *pPolicyConfig;
ERole reserved = eConsole;
HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient),
NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID *)&pPolicyConfig);
if (SUCCEEDED(hr))
{
hr = pPolicyConfig->SetDefaultEndpoint(devID, reserved);
pPolicyConfig->Release();
}
return hr;
}
3.Use the above interface to write a function to set the default output device.
It's MFC Project. Maybe you need to change.
Which output device needs to be set, you can modify the content of the macro yourself.
I get the name of output device using waveOutGetDevCaps()
//Set the default audio playback device
#define DEF_AUDIO_NAME _T("Speakers (2- Logitech USB Heads") //modify it, my device is Speakers (2- Logitech USB Heads
void InitDefaultAudioDevice()
{
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
IMMDeviceEnumerator *pEnum = NULL;
// Create a multimedia device enumerator.
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnum);
if (SUCCEEDED(hr))
{
//Determine if it is the default audio device
bool bExit = false;
IMMDevice *pDefDevice = NULL;
hr = pEnum->GetDefaultAudioEndpoint(eRender, eMultimedia,&pDefDevice);
if (SUCCEEDED(hr))
{
IPropertyStore *pStore;
hr = pDefDevice->OpenPropertyStore(STGM_READ, &pStore);
if (SUCCEEDED(hr))
{
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
hr = pStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
if (SUCCEEDED(hr))
{
CString strTmp = friendlyName.pwszVal;
if (strTmp.Find(DEF_AUDIO_NAME) != -1)
{
bExit = true;
}
PropVariantClear(&friendlyName);
}
pStore->Release();
}
pDefDevice->Release();
}
if (bExit)
{
pEnum->Release();
return;
}
IMMDeviceCollection *pDevices;
// Enumerate the output devices.
hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices);
if (SUCCEEDED(hr))
{
UINT count;
pDevices->GetCount(&count);
if (SUCCEEDED(hr))
{
for (int i = 0; i < count; i++)
{
bool bFind = false;
IMMDevice *pDevice;
hr = pDevices->Item(i, &pDevice);
if (SUCCEEDED(hr))
{
LPWSTR wstrID = NULL;
hr = pDevice->GetId(&wstrID);
if (SUCCEEDED(hr))
{
IPropertyStore *pStore;
hr = pDevice->OpenPropertyStore(STGM_READ, &pStore);
if (SUCCEEDED(hr))
{
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
hr = pStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
if (SUCCEEDED(hr))
{
// if no options, print the device
// otherwise, find the selected device and set it to be default
CString strTmp = friendlyName.pwszVal;
if (strTmp.Find(DEF_AUDIO_NAME) != -1)
{
SetDefaultAudioPlaybackDevice(wstrID);
bFind = true;
}
PropVariantClear(&friendlyName);
}
pStore->Release();
}
}
pDevice->Release();
}
if (bFind)
{
break;
}
}
}
pDevices->Release();
}
pEnum->Release();
}
}
CoUninitialize();
}
This sample can only change the output of Master volume. I don't know whether it can meet your requirements? If you need to change other apps, you have to explore for a while.
So I have been using SoundVolumeView for a while that let me mute and unmute my mic for meeting with a command line and I have discovered recently (because of OBS and monitoring audio) that it can also change device for an app or global default device
http://www.nirsoft.net/utils/sound_volume_view.html
And using /SetDefault and /SetAppDefault as shown in the doc example to the bottom of the page
I have put that in a batch script and bind a macro to my keyboard and it's doing a good job so far :)
I have searched a lot and have got some findings on how to get extended FA, but they are in C# using the language's own built-in APIs. I am trying to find the author name for a file in Windows, but my requirement is in Go/Python/C/Batch (order of priority).
In python, the third party packages (exifread and hachoir_metadata) are not working (not giving any result for a sample doc/xlsx file. Maybe the package I am installing via pip-install is erroneous).
Is there any other way or any user-level MSDN API available?
Please let me know if any experience on this. Thanks.
In C, C++ or other language, you get file properties with IPropertyStore interface
For example, for a .jpg file (test on Windows 10, VS 2015) =>
I get for Author :
System.Author(Auteurs) = Test Auteur
PIDLIST_ABSOLUTE pidl = ILCreateFromPath(L"E:\\icon_rose.jpg");
if (pidl != NULL)
{
IPropertyStore *pps;
HRESULT hr = SHGetPropertyStoreFromIDList(pidl, GPS_DEFAULT, IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr))
{
DWORD dwCount;
hr = pps->GetCount(&dwCount);
PROPERTYKEY propKey;
for (DWORD i = 0; i < dwCount; ++i)
{
hr = pps->GetAt(i, &propKey);
if (SUCCEEDED(hr))
{
PWSTR pszCanonicalName = NULL;
hr = PSGetNameFromPropertyKey(propKey, &pszCanonicalName);
PWSTR pszDescriptionName = NULL;
IPropertyDescription *ppd;
hr = PSGetPropertyDescription(propKey, IID_PPV_ARGS(&ppd));
if (SUCCEEDED(hr))
{
hr = ppd->GetDisplayName(&pszDescriptionName);
ppd->Release();
}
PROPVARIANT propvarValue = { 0 };
HRESULT hr = pps->GetValue(propKey, &propvarValue);
if (SUCCEEDED(hr))
{
PWSTR pszDisplayValue = NULL;
hr = PSFormatForDisplayAlloc(propKey, propvarValue, PDFF_DEFAULT, &pszDisplayValue);
if (SUCCEEDED(hr))
{
WCHAR wsBuffer[255];
wsprintf(wsBuffer, L"%s(%s) = %s\n", pszCanonicalName, (pszDescriptionName==NULL?L"Unknown":pszDescriptionName), pszDisplayValue);
OutputDebugString(wsBuffer);
CoTaskMemFree(pszDisplayValue);
}
PropVariantClear(&propvarValue);
}
if (pszCanonicalName != NULL)
CoTaskMemFree(pszCanonicalName);
if (pszDescriptionName != NULL)
CoTaskMemFree(pszDescriptionName);;
}
}
pps->Release();
}
ILFree(pidl);
}
I'm a newbie in the DirectShow world, and I just studied the simple "playcap" sample provided by Microsoft SDK Samples. With this little program I've been able to have a window with my webcam stream.
How can I take two shots from my webcam and compare them (even without saving them on the hard disk) to find which pixels are different?
I easily did this job using Win32API capture windows, but it was very slow, and I need it to be fast.
Thank you in advance, it is very important for my project.
You'd better search here for answer or look in samples for Sample Grabber Filter.
For more details you can write me directly here.
Add the Sample Grabber filter to the graph.
IBaseFilter *pSG_Filter;
hr = CoCreateInstance(
CLSID_SampleGrabber,
NULL,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
(void**)&pSG_Filter
);
hr = pGraph->AddFilter(pSG_Filter, L"SampleGrab");
Add the Null Renderer filter to the graph.
IBaseFilter *pNull;
hr = CoCreateInstance(
CLSID_NullRenderer,
NULL,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
(void**)&pNull
);
hr = pGraph->AddFilter(pNull, L"NullRender");
Now you can use the ICaptureGraphBuilder2::RenderStream method to connect all three filters in one method call, going from the still pin to the Sample Grabber, and from the Sample Grabber to the Null Renderer:
hr = pBuild->RenderStream(
&PIN_CATEGORY_STILL, // Connect this pin ...
&MEDIATYPE_Video, // with this media type ...
pCap, // on this filter ...
pSG_Filter, // to the Sample Grabber ...
pNull); // ... and finally to the Null Renderer.
Now use the ISampleGrabber interface to configure the Sample Grabber so that it buffers samples:
ISampleGrabber *pSG = NULL;
hr = pSG_Filter->QueryInterface(IID_ISampleGrabber, (void**)&pSG);
if (SUCCEEDED(hr))
{
hr = pSG->SetOneShot(FALSE);
hr = pSG->SetBufferSamples(TRUE);
...
Now, you should look at method ISampleGrabber::SetOneShot and maybe set TRUE there.
Set the callback interface with a pointer to your callback object:
hr = pSG->SetCallback(&g_StillCapCB, 0); // 0 = Use the SampleCB callback method.
Get the media type that the still pin used to connect with the Sample Grabber:
// Store the media type for later use.
AM_MEDIA_TYPE g_StillMediaType;
hr = pSG->GetConnectedMediaType(&g_StillMediaType);
pSG->Release();
This media type will contain the BITMAPINFOHEADER structure that defines the format of the still image.
What follows is an example of the callback class. Note that the class implements IUnknown, which it inherits through the ISampleGrabber interface, but it does not keep a reference count. This is safe because the application creates the object on the stack, and the object remains in scope throughout the lifetime of the filter graph.
All of the work happens in the BufferCB method, which is called by the Sample Grabber whenever it gets a new sample. In the following example, the method writes the bitmap to a file:
class SampleGrabberCallback : public ISampleGrabberCB
{
public:
// Fake referance counting.
STDMETHODIMP_(ULONG) AddRef() { return 1; }
STDMETHODIMP_(ULONG) Release() { return 2; }
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
{
if (NULL == ppvObject) return E_POINTER;
if (riid == __uuidof(IUnknown))
{
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
if (riid == __uuidof(ISampleGrabberCB))
{
*ppvObject = static_cast<ISampleGrabberCB*>(this);
return S_OK;
}
return E_NOTIMPL;
}
STDMETHODIMP SampleCB(double Time, IMediaSample *pSample)
{
return E_NOTIMPL;
}
STDMETHODIMP BufferCB(double Time, BYTE *pBuffer, long BufferLen)
{
if ((g_StillMediaType.majortype != MEDIATYPE_Video) ||
(g_StillMediaType.formattype != FORMAT_VideoInfo) ||
(g_StillMediaType.cbFormat < sizeof(VIDEOINFOHEADER)) ||
(g_StillMediaType.pbFormat == NULL))
{
return VFW_E_INVALIDMEDIATYPE;
}
HANDLE hf = CreateFile("C:\\Example.bmp", GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
if (hf == INVALID_HANDLE_VALUE)
{
return E_FAIL;
}
long cbBitmapInfoSize = g_StillMediaType.cbFormat - SIZE_PREHEADER;
VIDEOINFOHEADER *pVideoHeader =
(VIDEOINFOHEADER*)g_StillMediaType.pbFormat;
BITMAPFILEHEADER bfh;
ZeroMemory(&bfh, sizeof(bfh));
bfh.bfType = 'MB'; // Little-endian for "BM".
bfh.bfSize = sizeof( bfh ) + BufferLen + cbBitmapInfoSize;
bfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + cbBitmapInfoSize;
// Write the file header.
DWORD dwWritten = 0;
WriteFile( hf, &bfh, sizeof( bfh ), &dwWritten, NULL );
WriteFile(hf, HEADER(pVideoHeader), cbBitmapInfoSize, &dwWritten, NULL);
WriteFile( hf, pBuffer, BufferLen, &dwWritten, NULL );
CloseHandle( hf );
return S_OK;
}
};
Free the media type before the application exits:
// On exit, remember to release the media type.
FreeMediaType(g_StillMediaType);
I would like to record the microphone and the system sound(Youtube music, Media Player movie sound, keyboard type warning sound...etc.), so that publishing it to the server by Flash Player.
I found the DirectShow sample at GitHub: virtual-audio-capture-grabber-device
I register its DLL successfully.
And the filter name of this sample is "virtual-audio-capturer".
C:\Users\user\Desktop\virtual-audio-capture-grabber-device-master\virtual-aud
io-capture-grabber-device-master\source_code\Release>regsvr32 audio_sniffer.dll
I used virtual-audio-capturer filter of Audio Capture Source, WavDest Filter and File Writer Filter to record audio into the WAVE file by GraphEdt.exe. The graph is workable. And the result file is correct.
There are Microphone (VIA High Definition Audio) and Stereo Mix (VIA High Definition Audio) in the microphone list of Flash Player, but no virtual-audio-capture-grabber-device. The result is same with Action Script 3 project. But I can see the virtual-audio-capture-grabber-device at Adobe Flash Media Live Encoder 3.2. (http://img.bbs.csdn.net/upload/201311/12/1384239370_718116.png)
var deviceArray:Array = Microphone.names;
trace("Available sound input devices:");
for (var i:int = 0; i < deviceArray.length; i++) {
trace(" " + deviceArray[i]);
}
The virtual-audio-capturer filter has only one output pin, and no input pin.
const AMOVIESETUP_MEDIATYPE AMSMediaTypesVCam =
{ &MEDIATYPE_Audio, // clsMajorType
&MEDIASUBTYPE_NULL // clsMinorType
};
setup.cpp
#define CreateComObject(clsid, iid, var) CoCreateInstance( clsid, NULL, CLSCTX_INPROC_SERVER, iid, (void **)&var);
STDAPI AMovieSetupRegisterServer( CLSID clsServer, LPCWSTR szDescription, LPCWSTR szFileName, LPCWSTR szThreadingModel = L"Both", LPCWSTR szServerType = L"InprocServer32" );
STDAPI AMovieSetupUnregisterServer( CLSID clsServer );
#ifdef _WIN64
DEFINE_GUID(CLSID_VirtualCam,
0x8e146464, 0xdb61, 0x4309, 0xaf, 0xa1, 0x35, 0x78, 0xe9, 0x27, 0xe9, 0x35);
#else
DEFINE_GUID(CLSID_VirtualCam,
0x8e14549b, 0xdb61, 0x4309, 0xaf, 0xa1, 0x35, 0x78, 0xe9, 0x27, 0xe9, 0x35);
#endif
const AMOVIESETUP_MEDIATYPE AMSMediaTypesVCam = { &MEDIATYPE_Audio, &MEDIASUBTYPE_NULL };
const AMOVIESETUP_PIN AMSPinVCam = {
L"Output", // Pin string name
FALSE, // Is it rendered
TRUE, // Is it an output
FALSE, // Can we have none
FALSE, // Can we have many
&CLSID_NULL, // Connects to filter
NULL, // Connects to pin
1, // Number of types
&AMSMediaTypesVCam };
const AMOVIESETUP_FILTER AMSFilterVCam = {
&CLSID_VirtualCam, // Filter CLSID
L"virtual-audio-capturer", // String name
MERIT_DO_NOT_USE, // Filter merit
1, // Number pins
&AMSPinVCam };
CFactoryTemplate g_Templates[] = {
{
L"virtual-audio-capturer",
&CLSID_VirtualCam,
CVCam::CreateInstance,
NULL,
&AMSFilterVCam
}, };
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
STDAPI RegisterFilters( BOOL bRegister ) {
HRESULT hr = NOERROR;
WCHAR achFileName[MAX_PATH];
char achTemp[MAX_PATH];
ASSERT(g_hInst != 0);
if( 0 == GetModuleFileNameA(g_hInst, achTemp, sizeof(achTemp)))
return AmHresultFromWin32(GetLastError());
MultiByteToWideChar(CP_ACP, 0L, achTemp, lstrlenA(achTemp) + 1,
achFileName, NUMELMS(achFileName));
hr = CoInitialize(0);
if(bRegister) {
hr = AMovieSetupRegisterServer(CLSID_VirtualCam, L"virtual-audio-capturer", achFileName, L"Both", L"InprocServer32");
}
if( SUCCEEDED(hr) ) {
IFilterMapper2 *fm = 0;
hr = CreateComObject( CLSID_FilterMapper2, IID_IFilterMapper2, fm );
if( SUCCEEDED(hr) )
{
if(bRegister)
{
IMoniker *pMoniker = 0;
REGFILTER2 rf2;
rf2.dwVersion = 1;
rf2.dwMerit = MERIT_DO_NOT_USE;
rf2.cPins = 1;
rf2.rgPins = &AMSPinVCam;
hr = fm->RegisterFilter(CLSID_VirtualCam, L"virtual-audio-capturer", &pMoniker, &CLSID_AudioInputDeviceCategory, NULL, &rf2);
}
else
{
hr = fm->UnregisterFilter(&CLSID_AudioInputDeviceCategory, 0, CLSID_VirtualCam);
}
}
// release interface
if(fm)
fm->Release();
}
if( SUCCEEDED(hr) && !bRegister )
hr = AMovieSetupUnregisterServer( CLSID_VirtualCam );
CoFreeUnusedLibraries();
CoUninitialize();
return hr; }
#include <stdio.h>
STDAPI RegisterFilters( BOOL bRegister );
STDAPI DllRegisterServer() {
printf("hello there"); // we actually never see this...
return RegisterFilters(TRUE); }
STDAPI DllUnregisterServer() {
return RegisterFilters(FALSE); }
STDAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
extern "C" BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) {
return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
Please click the link to view the full DirectShow sample code.
Is the DirectShow virtual audio device available with Flash Player?
Should I add the Analog Audio input pin?
Should I implement the IAMAudioInputMixer interface?
Should I use WinDDK to make the virtual audio device first?
Win7 SP1 64Bit System;
VIA High Definition Audio;
Intel Audio;
Adobe Flash Media Live Encoder 3.2
Adobe Flash Player 11,8,800,94
Adobe Flash Builder 4.6
Microsoft Visual C++ 2010
Microsoft Windows SDK 7.1
I cannot know for sure, but Flash probably does not use DirectShow API for audio, but instead another of Windows' many audio APIs, probably DirectSound. You'll have to write an audio driver.
I am porting the famous packet capture software -- WinPcap from NDIS 5.0 to NDIS 6.x. I tried to translate every NDIS 5.0 function to its 6.0 version. In WinPcap sourcecode function NdisOpenAdapter is called by NPF_OpenAdapter in Openclos.c. I translated it to NdisOpenAdapterEx for NDIS 6.0. But I cannot find a way to set the 4th parameter BindContext.
The delcaration of NdisOpenAdapterEx can be found here:
http://msdn.microsoft.com/en-us/library/windows/hardware/ff563715(v=vs.85).aspx
Also MS said "A protocol driver must call NdisOpenAdapterEx from its ProtocolBindAdapterEx function. NDIS fails any attempt to call NdisOpenAdapterEx outside the context of ProtocolBindAdapterEx.". So it seems that NdisOpenAdapterEx cannot be called in NPF_OpenAdapter. it must be called in the NPF_BindAdapterEx function. I substituted the driver npf.sys with my own version, started Wireshark (a packet capture frontend), set breakpoints in NPF_BindAdapterEx and found NPF_BindAdapterEx was never called before NPF_OpenAdapter. So it is impposible for me to get the BindContext paramters before calling NdisOpenAdapterEx.
I just want to imgrate WinPcap to NDIS 6.0 with modifications as small as possible. And how to solve this problem?
Here is the code of Openclos.c
/*
* Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy)
* Copyright (c) 2005 - 2010 CACE Technologies, Davis (California)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Politecnico di Torino, CACE Technologies
* nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "stdafx.h"
#include <ntddk.h>
#include <ndis.h>
#include "debug.h"
#include "packet.h"
#include "..\..\Common\WpcapNames.h"
static
VOID NPF_ReleaseOpenInstanceResources(POPEN_INSTANCE pOpen);
static NDIS_MEDIUM MediumArray[] =
{
NdisMedium802_3,
// NdisMediumWan,
NdisMediumFddi, NdisMediumArcnet878_2, NdisMediumAtm, NdisMedium802_5
};
#define NUM_NDIS_MEDIA (sizeof MediumArray / sizeof MediumArray[0])
//Itoa. Replaces the buggy RtlIntegerToUnicodeString
// void PacketItoa(UINT n, PUCHAR buf)
// {
// int i;
// for(i=0;i<20;i+=2){
// buf[18-i]=(n%10)+48;
// buf[19-i]=0;
// n/=10;
// }
// }
/// Global start time. Used as an absolute reference for timestamp conversion.
struct time_conv G_Start_Time =
{
0, {0, 0},
};
ULONG g_NumOpenedInstances = 0;
BOOLEAN NPF_StartUsingBinding(IN POPEN_INSTANCE pOpen)
{
ASSERT(pOpen != NULL);
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
NdisAcquireSpinLock(&pOpen->AdapterHandleLock);
if (pOpen->AdapterBindingStatus != ADAPTER_BOUND)
{
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
return FALSE;
}
pOpen->AdapterHandleUsageCounter++;
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
return TRUE;
}
VOID NPF_StopUsingBinding(IN POPEN_INSTANCE pOpen)
{
ASSERT(pOpen != NULL);
//
// There is no risk in calling this function from abobe passive level
// (i.e. DISPATCH, in this driver) as we acquire a spinlock and decrement a
// counter.
//
// ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
NdisAcquireSpinLock(&pOpen->AdapterHandleLock);
ASSERT(pOpen->AdapterHandleUsageCounter > 0);
ASSERT(pOpen->AdapterBindingStatus == ADAPTER_BOUND);
pOpen->AdapterHandleUsageCounter--;
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
}
VOID NPF_CloseBinding(IN POPEN_INSTANCE pOpen)
{
NDIS_EVENT Event;
NDIS_STATUS Status;
ASSERT(pOpen != NULL);
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
NdisInitializeEvent(&Event);
NdisResetEvent(&Event);
NdisAcquireSpinLock(&pOpen->AdapterHandleLock);
while (pOpen->AdapterHandleUsageCounter > 0)
{
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
NdisWaitEvent(&Event, 1);
NdisAcquireSpinLock(&pOpen->AdapterHandleLock);
}
//
// now the UsageCounter is 0
//
while (pOpen->AdapterBindingStatus == ADAPTER_UNBINDING)
{
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
NdisWaitEvent(&Event, 1);
NdisAcquireSpinLock(&pOpen->AdapterHandleLock);
}
//
// now the binding status is either bound or unbound
//
if (pOpen->AdapterBindingStatus == ADAPTER_UNBOUND)
{
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
return;
}
ASSERT(pOpen->AdapterBindingStatus == ADAPTER_BOUND);
pOpen->AdapterBindingStatus = ADAPTER_UNBINDING;
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
//
// do the release procedure
//
NdisResetEvent(&pOpen->NdisOpenCloseCompleteEvent);
// Close the adapter
Status = NdisCloseAdapterEx(pOpen->AdapterHandle);
if (Status == NDIS_STATUS_PENDING)
{
TRACE_MESSAGE(PACKET_DEBUG_LOUD, "Pending NdisCloseAdapter");
NdisWaitEvent(&pOpen->NdisOpenCloseCompleteEvent, 0);
}
else
{
TRACE_MESSAGE(PACKET_DEBUG_LOUD, "Not Pending NdisCloseAdapter");
}
NdisAcquireSpinLock(&pOpen->AdapterHandleLock);
pOpen->AdapterBindingStatus = ADAPTER_UNBOUND;
NdisReleaseSpinLock(&pOpen->AdapterHandleLock);
}
//-------------------------------------------------------------------
NTSTATUS NPF_OpenAdapter(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PDEVICE_EXTENSION DeviceExtension;
POPEN_INSTANCE Open;
PIO_STACK_LOCATION IrpSp;
NDIS_STATUS Status;
NDIS_STATUS ErrorStatus;
UINT i;
PUCHAR tpointer;
PLIST_ENTRY PacketListEntry;
NTSTATUS returnStatus;
NET_BUFFER_LIST_POOL_PARAMETERS PoolParameters;
NDIS_OPEN_PARAMETERS OpenParameters;
NET_FRAME_TYPE FrameTypeArray[2] =
{
NDIS_ETH_TYPE_802_1X, NDIS_ETH_TYPE_802_1Q
};
//
// Old registry based WinPcap names
//
// WCHAR EventPrefix[MAX_WINPCAP_KEY_CHARS];
// UINT RegStrLen;
TRACE_ENTER();
DeviceExtension = DeviceObject->DeviceExtension;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
// allocate some memory for the open structure
Open = ExAllocatePoolWithTag(NonPagedPool, sizeof(OPEN_INSTANCE), '0OWA');
if (Open == NULL)
{
// no memory
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory(Open, sizeof(OPEN_INSTANCE));
//
// Old registry based WinPcap names
//
// //
// // Get the Event names base from the registry
// //
// RegStrLen = sizeof(EventPrefix)/sizeof(EventPrefix[0]);
//
// NPF_QueryWinpcapRegistryString(NPF_EVENTS_NAMES_REG_KEY_WC,
// EventPrefix,
// RegStrLen,
// NPF_EVENTS_NAMES_WIDECHAR);
//
Open->DeviceExtension = DeviceExtension;
NdisZeroMemory(&PoolParameters, sizeof(NET_BUFFER_LIST_POOL_PARAMETERS));
PoolParameters.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
PoolParameters.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;
PoolParameters.Header.Size = sizeof(PoolParameters);
PoolParameters.ProtocolId = NDIS_PROTOCOL_ID_TCP_IP;
PoolParameters.ContextSize = 0;
PoolParameters.fAllocateNetBuffer = TRUE;
PoolParameters.PoolTag = NPCAP_ALLOC_TAG;
Open->PacketPool = NdisAllocateNetBufferListPool(NULL, &PoolParameters);
if (Open->PacketPool == NULL)
{
TRACE_MESSAGE(PACKET_DEBUG_LOUD, "Failed to allocate packet pool");
ExFreePool(Open);
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INSUFFICIENT_RESOURCES;
}
// // Allocate a packet pool for our xmit and receive packets
// NdisAllocatePacketPool(
// &Status,
// &Open->PacketPool,
// TRANSMIT_PACKETS,
// sizeof(PACKET_RESERVED));
//
// if (Status != NDIS_STATUS_SUCCESS) {
//
// TRACE_MESSAGE(PACKET_DEBUG_LOUD, "Failed to allocate packet pool");
//
// ExFreePool(Open);
// Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
// IoCompleteRequest(Irp, IO_NO_INCREMENT);
// return STATUS_INSUFFICIENT_RESOURCES;
// }
NdisInitializeEvent(&Open->WriteEvent);
NdisInitializeEvent(&Open->NdisRequestEvent);
NdisInitializeEvent(&Open->NdisWriteCompleteEvent);
NdisInitializeEvent(&Open->DumpEvent);
NdisAllocateSpinLock(&Open->MachineLock);
NdisAllocateSpinLock(&Open->WriteLock);
Open->WriteInProgress = FALSE;
for (i = 0; i < g_NCpu; i++)
{
NdisAllocateSpinLock(&Open->CpuData[i].BufferLock);
}
NdisInitializeEvent(&Open->NdisOpenCloseCompleteEvent);
// list to hold irp's want to reset the adapter
InitializeListHead(&Open->ResetIrpList);
// Initialize the request list
KeInitializeSpinLock(&Open->RequestSpinLock);
InitializeListHead(&Open->RequestList);
//
// Initialize the open instance
//
//Open->BindContext = NULL;
Open->bpfprogram = NULL; //reset the filter
Open->mode = MODE_CAPT;
Open->Nbytes.QuadPart = 0;
Open->Npackets.QuadPart = 0;
Open->Nwrites = 1;
Open->Multiple_Write_Counter = 0;
Open->MinToCopy = 0;
Open->TimeOut.QuadPart = (LONGLONG)1;
Open->DumpFileName.Buffer = NULL;
Open->DumpFileHandle = NULL;
#ifdef HAVE_BUGGY_TME_SUPPORT
Open->tme.active = TME_NONE_ACTIVE;
#endif // HAVE_BUGGY_TME_SUPPORT
Open->DumpLimitReached = FALSE;
Open->MaxFrameSize = 0;
Open->WriterSN = 0;
Open->ReaderSN = 0;
Open->Size = 0;
Open->SkipSentPackets = FALSE;
Open->ReadEvent = NULL;
//
// we need to keep a counter of the pending IRPs
// so that when the IRP_MJ_CLEANUP dispatcher gets called,
// we can wait for those IRPs to be completed
//
Open->NumPendingIrps = 0;
Open->ClosePending = FALSE;
NdisAllocateSpinLock(&Open->OpenInUseLock);
//
//allocate the spinlock for the statistic counters
//
NdisAllocateSpinLock(&Open->CountersLock);
//
// link up the request stored in our open block
//
for (i = 0 ; i < MAX_REQUESTS ; i++)
{
NdisInitializeEvent(&Open->Requests[i].InternalRequestCompletedEvent);
ExInterlockedInsertTailList(&Open->RequestList, &Open->Requests[i].ListElement, &Open->RequestSpinLock);
}
NdisResetEvent(&Open->NdisOpenCloseCompleteEvent);
//
// set the proper binding flags before trying to open the MAC
//
Open->AdapterBindingStatus = ADAPTER_BOUND;
Open->AdapterHandleUsageCounter = 0;
NdisAllocateSpinLock(&Open->AdapterHandleLock);
//
// Try to open the MAC
//
TRACE_MESSAGE2(PACKET_DEBUG_LOUD, "Opening the device %ws, BindingContext=%p", DeviceExtension->AdapterName.Buffer, Open);
returnStatus = STATUS_SUCCESS;
NdisZeroMemory(&OpenParameters, sizeof(NDIS_OPEN_PARAMETERS));
OpenParameters.Header.Type = NDIS_OBJECT_TYPE_OPEN_PARAMETERS;
OpenParameters.Header.Revision = NDIS_OPEN_PARAMETERS_REVISION_1;
OpenParameters.Header.Size = sizeof(NDIS_OPEN_PARAMETERS);
OpenParameters.AdapterName = &DeviceExtension->AdapterName;
OpenParameters.MediumArray = MediumArray;
OpenParameters.MediumArraySize = sizeof(MediumArray) / sizeof(NDIS_MEDIUM);
OpenParameters.SelectedMediumIndex = &Open->Medium;
OpenParameters.FrameTypeArray = NULL;
OpenParameters.FrameTypeArraySize = 0;
//OpenParameters.FrameTypeArray = &FrameTypeArray[0];
//OpenParameters.FrameTypeArraySize = sizeof(FrameTypeArray) / sizeof(NET_FRAME_TYPE);
NDIS_DECLARE_PROTOCOL_OPEN_CONTEXT(OPEN_INSTANCE);
Status = NdisOpenAdapterEx(g_NdisProtocolHandle, (NDIS_HANDLE)Open, &OpenParameters, NULL, &Open->AdapterHandle);
// NdisOpenAdapter(
// &Status,
// &ErrorStatus,
// &Open->AdapterHandle,
// &Open->Medium,
// MediumArray,
// NUM_NDIS_MEDIA,
// g_NdisProtocolHandle,
// Open,
// &DeviceExtension->AdapterName,
// 0,
// NULL);
TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "Opened the device, Status=%x", Status);
if (Status == NDIS_STATUS_PENDING)
{
NdisWaitEvent(&Open->NdisOpenCloseCompleteEvent, 0);
if (!NT_SUCCESS(Open->OpenCloseStatus))
{
returnStatus = Open->OpenCloseStatus;
}
else
{
returnStatus = STATUS_SUCCESS;
}
}
else
{
//
// request not pending, we know the result, and OpenComplete has not been called.
//
if (Status == NDIS_STATUS_SUCCESS)
{
returnStatus = STATUS_SUCCESS;
}
else
{
//
// this is not completely correct, as we are converting an NDIS_STATUS to a NTSTATUS
//
returnStatus = Status;
}
}
if (returnStatus == STATUS_SUCCESS)
{
ULONG localNumOpenedInstances;
//
// complete the open
//
localNumOpenedInstances = InterlockedIncrement(&g_NumOpenedInstances);
TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "Opened Instances: %u", localNumOpenedInstances);
// Get the absolute value of the system boot time.
// This is used for timestamp conversion.
TIME_SYNCHRONIZE(&G_Start_Time);
returnStatus = NPF_GetDeviceMTU(Open, Irp, &Open->MaxFrameSize);
if (!NT_SUCCESS(returnStatus))
{
//
// Close the binding
//
NPF_CloseBinding(Open);
}
}
if (!NT_SUCCESS(returnStatus))
{
NPF_ReleaseOpenInstanceResources(Open);
//
// Free the open instance itself
//
ExFreePool(Open);
}
else
{
// Save or open here
IrpSp->FileObject->FsContext = Open;
}
Irp->IoStatus.Status = returnStatus;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
TRACE_EXIT();
return returnStatus;
}
BOOLEAN NPF_StartUsingOpenInstance(IN POPEN_INSTANCE pOpen)
{
BOOLEAN returnStatus;
NdisAcquireSpinLock(&pOpen->OpenInUseLock);
if (pOpen->ClosePending)
{
returnStatus = FALSE;
}
else
{
returnStatus = TRUE;
pOpen->NumPendingIrps ++;
}
NdisReleaseSpinLock(&pOpen->OpenInUseLock);
return returnStatus;
}
VOID NPF_StopUsingOpenInstance(IN POPEN_INSTANCE pOpen)
{
NdisAcquireSpinLock(&pOpen->OpenInUseLock);
ASSERT(pOpen->NumPendingIrps > 0);
pOpen->NumPendingIrps --;
NdisReleaseSpinLock(&pOpen->OpenInUseLock);
}
VOID NPF_CloseOpenInstance(IN POPEN_INSTANCE pOpen)
{
ULONG i = 0;
NDIS_EVENT Event;
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
NdisInitializeEvent(&Event);
NdisResetEvent(&Event);
NdisAcquireSpinLock(&pOpen->OpenInUseLock);
pOpen->ClosePending = TRUE;
while (pOpen->NumPendingIrps > 0)
{
NdisReleaseSpinLock(&pOpen->OpenInUseLock);
NdisWaitEvent(&Event, 1);
NdisAcquireSpinLock(&pOpen->OpenInUseLock);
}
NdisReleaseSpinLock(&pOpen->OpenInUseLock);
}
VOID NPF_ReleaseOpenInstanceResources(POPEN_INSTANCE pOpen)
{
PKEVENT pEvent;
UINT i;
TRACE_ENTER();
ASSERT(pOpen != NULL);
ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "Open= %p", pOpen);
//NdisFreePacketPool(pOpen->PacketPool);
NdisFreeNetBufferListPool(pOpen->PacketPool);
//
// Free the filter if it's present
//
if (pOpen->bpfprogram != NULL)
ExFreePool(pOpen->bpfprogram);
//
// Jitted filters are supported on x86 (32bit) only
//
#ifdef _X86_
// Free the jitted filter if it's present
if (pOpen->Filter != NULL)
BPF_Destroy_JIT_Filter(pOpen->Filter);
#endif //_X86_
//
// Dereference the read event.
//
if (pOpen->ReadEvent != NULL)
ObDereferenceObject(pOpen->ReadEvent);
//
// free the buffer
// NOTE: the buffer is fragmented among the various CPUs, but the base pointer of the
// allocated chunk of memory is stored in the first slot (pOpen->CpuData[0])
//
if (pOpen->Size > 0)
ExFreePool(pOpen->CpuData[0].Buffer);
//
// free the per CPU spinlocks
//
for (i = 0; i < g_NCpu; i++)
{
NdisFreeSpinLock(&pOpen->CpuData[i].BufferLock);
}
//
// Free the string with the name of the dump file
//
if (pOpen->DumpFileName.Buffer != NULL)
ExFreePool(pOpen->DumpFileName.Buffer);
TRACE_EXIT();
}
//-------------------------------------------------------------------
VOID NPF_OpenAdapterCompleteEx(IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_STATUS Status)
{
POPEN_INSTANCE Open;
PLIST_ENTRY RequestListEntry;
PINTERNAL_REQUEST MaxSizeReq;
NDIS_STATUS ReqStatus;
TRACE_ENTER();
Open = (POPEN_INSTANCE)ProtocolBindingContext;
ASSERT(Open != NULL);
if (Status != NDIS_STATUS_SUCCESS)
{
//
// this is not completely correct, as we are converting an NDIS_STATUS to a NTSTATUS
//
Open->OpenCloseStatus = Status;
}
else
{
Open->OpenCloseStatus = STATUS_SUCCESS;
}
//
// wake up the caller of NdisOpen, that is NPF_Open
//
NdisSetEvent(&Open->NdisOpenCloseCompleteEvent);
TRACE_EXIT();
}
NTSTATUS NPF_GetDeviceMTU(IN POPEN_INSTANCE pOpen, IN PIRP pIrp, OUT PUINT pMtu)
{
PLIST_ENTRY RequestListEntry;
PINTERNAL_REQUEST MaxSizeReq;
NDIS_STATUS ReqStatus;
TRACE_ENTER();
ASSERT(pOpen != NULL);
ASSERT(pIrp != NULL);
ASSERT(pMtu != NULL);
// Extract a request from the list of free ones
RequestListEntry = ExInterlockedRemoveHeadList(&pOpen->RequestList, &pOpen->RequestSpinLock);
if (RequestListEntry == NULL)
{
//
// THIS IS WRONG
//
//
// Assume Ethernet
//
*pMtu = 1514;
TRACE_EXIT();
return STATUS_SUCCESS;
}
MaxSizeReq = CONTAINING_RECORD(RequestListEntry, INTERNAL_REQUEST, ListElement);
MaxSizeReq->Request.RequestType = NdisRequestQueryInformation;
MaxSizeReq->Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_MAXIMUM_TOTAL_SIZE;
MaxSizeReq->Request.DATA.QUERY_INFORMATION.InformationBuffer = pMtu;
MaxSizeReq->Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(*pMtu);
NdisResetEvent(&MaxSizeReq->InternalRequestCompletedEvent);
// submit the request
ReqStatus = NdisOidRequest(pOpen->AdapterHandle, &MaxSizeReq->Request);
if (ReqStatus == NDIS_STATUS_PENDING)
{
NdisWaitEvent(&MaxSizeReq->InternalRequestCompletedEvent, 0);
ReqStatus = MaxSizeReq->RequestStatus;
}
//
// Put the request in the list of the free ones
//
ExInterlockedInsertTailList(&pOpen->RequestList, &MaxSizeReq->ListElement, &pOpen->RequestSpinLock);
if (ReqStatus == NDIS_STATUS_SUCCESS)
{
TRACE_EXIT();
return STATUS_SUCCESS;
}
else
{
//
// THIS IS WRONG
//
//
// Assume Ethernet
//
*pMtu = 1514;
TRACE_EXIT();
return STATUS_SUCCESS;
// return ReqStatus;
}
}
//-------------------------------------------------------------------
NTSTATUS NPF_CloseAdapter(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
POPEN_INSTANCE pOpen;
PIO_STACK_LOCATION IrpSp;
TRACE_ENTER();
IrpSp = IoGetCurrentIrpStackLocation(Irp);
pOpen = IrpSp->FileObject->FsContext;
ASSERT(pOpen != NULL);
//
// Free the open instance itself
//
ExFreePool(pOpen);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
TRACE_EXIT();
return STATUS_SUCCESS;
}
//-------------------------------------------------------------------
NTSTATUS NPF_Cleanup(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
POPEN_INSTANCE Open;
NDIS_STATUS Status;
PIO_STACK_LOCATION IrpSp;
LARGE_INTEGER ThreadDelay;
ULONG localNumOpenInstances;
TRACE_ENTER();
IrpSp = IoGetCurrentIrpStackLocation(Irp);
Open = IrpSp->FileObject->FsContext;
TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "Open = %p\n", Open);
ASSERT(Open != NULL);
NPF_CloseOpenInstance(Open);
if (Open->ReadEvent != NULL)
KeSetEvent(Open->ReadEvent, 0, FALSE);
NPF_CloseBinding(Open);
// NOTE:
// code commented out because the kernel dump feature is disabled
//
//if (AdapterAlreadyClosing == FALSE)
//{
//
// Unfreeze the consumer
//
// if(Open->mode & MODE_DUMP)
// NdisSetEvent(&Open->DumpEvent);
// else
// KeSetEvent(Open->ReadEvent,0,FALSE);
// //
// // If this instance is in dump mode, complete the dump and close the file
// //
// if((Open->mode & MODE_DUMP) && Open->DumpFileHandle != NULL)
// {
// NTSTATUS wres;
// ThreadDelay.QuadPart = -50000000;
// //
// // Wait the completion of the thread
// //
// wres = KeWaitForSingleObject(Open->DumpThreadObject,
// UserRequest,
// KernelMode,
// TRUE,
// &ThreadDelay);
// ObDereferenceObject(Open->DumpThreadObject);
// //
// // Flush and close the dump file
// //
// NPF_CloseDumpFile(Open);
// }
//}
//
// release all the resources
//
NPF_ReleaseOpenInstanceResources(Open);
// IrpSp->FileObject->FsContext = NULL;
//
// Decrease the counter of open instances
//
localNumOpenInstances = InterlockedDecrement(&g_NumOpenedInstances);
TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "Opened Instances: %u", localNumOpenInstances);
if (localNumOpenInstances == 0)
{
//
// Force a synchronization at the next NPF_Open().
// This hopefully avoids the synchronization issues caused by hibernation or standby.
//
TIME_DESYNCHRONIZE(&G_Start_Time);
}
//
// and complete the IRP with status success
//
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
TRACE_EXIT();
return(STATUS_SUCCESS);
}
//-------------------------------------------------------------------
VOID NPF_CloseAdapterCompleteEx(IN NDIS_HANDLE ProtocolBindingContext)
{
POPEN_INSTANCE Open;
PIRP Irp;
TRACE_ENTER();
Open = (POPEN_INSTANCE)ProtocolBindingContext;
ASSERT(Open != NULL);
TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "Open= %p", Open);
NdisSetEvent(&Open->NdisOpenCloseCompleteEvent);
TRACE_EXIT();
return;
}
//-------------------------------------------------------------------
NDIS_STATUS NPF_NetPowerChange(IN NDIS_HANDLE ProtocolBindingContext, IN PNET_PNP_EVENT_NOTIFICATION pNetPnPEvent)
{
TRACE_ENTER();
TIME_DESYNCHRONIZE(&G_Start_Time);
TIME_SYNCHRONIZE(&G_Start_Time);
TRACE_EXIT();
return STATUS_SUCCESS;
}
//------------------------------------------------------------------
NDIS_STATUS NPF_BindAdapterEx(IN NDIS_HANDLE ProtocolDriverContext, IN NDIS_HANDLE BindContext, IN PNDIS_BIND_PARAMETERS BindParameters)
{
NTSTATUS ntStatus = NDIS_STATUS_SUCCESS;
int a = 1;
a ++;
TRACE_ENTER();
TRACE_EXIT();
return ntStatus;
}
//-------------------------------------------------------------------
NDIS_STATUS NPF_UnbindAdapterEx(IN NDIS_HANDLE UnbindContext, IN NDIS_HANDLE ProtocolBindingContext)
{
NTSTATUS Status;
POPEN_INSTANCE Open = (POPEN_INSTANCE)ProtocolBindingContext;
TRACE_ENTER();
ASSERT(Open != NULL);
//
// The following code has been disabled bcause the kernel dump feature has been disabled.
//
////
//// Awake a possible pending read on this instance
//// TODO should be ok.
////
// if(Open->mode & MODE_DUMP)
// NdisSetEvent(&Open->DumpEvent);
// else
if (Open->ReadEvent != NULL)
KeSetEvent(Open->ReadEvent, 0, FALSE);
//
// The following code has been disabled bcause the kernel dump feature has been disabled.
//
////
//// If this instance is in dump mode, complete the dump and close the file
//// TODO needs to be checked again.
////
// if((Open->mode & MODE_DUMP) && Open->DumpFileHandle != NULL)
// NPF_CloseDumpFile(Open);
Status = NDIS_STATUS_SUCCESS;
NPF_CloseBinding(Open);
TRACE_EXIT();
return Status;
}
//-------------------------------------------------------------------
VOID NPF_ResetComplete(IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_STATUS Status)
{
POPEN_INSTANCE Open;
PIRP Irp;
PLIST_ENTRY ResetListEntry;
TRACE_ENTER();
Open = (POPEN_INSTANCE)ProtocolBindingContext;
//
// remove the reset IRP from the list
//
ResetListEntry = ExInterlockedRemoveHeadList(&Open->ResetIrpList, &Open->RequestSpinLock);
Irp = CONTAINING_RECORD(ResetListEntry, IRP, Tail.Overlay.ListEntry);
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
TRACE_EXIT();
return;
}
You've definitely hit across an interesting problem with WinPcap. Its protocol driver (NPF) expects to be able to open an adapter whenever it wants. When paired with Wireshark, it will do this frequently — it's typical to see NPF open and close the same adapter dozens of times just while the Wireshark GUI is loading. It's even possible to see NPF have multiple bindings to the same adapter simultaneously.
The rough equivalent of this in NDIS 6.x is the NdisReEnumerateProtocolBindings function. What this does is queue up a workitem to call into your protocol' ProtocolBindAdapterEx handler for each adapter that is marked as bound in the registry, but isn't currently bound in NDIS. (I.e., for each adapter that INetCfg finds a bindpath to that does not already have an open handle.)
However, due to the large impedance between NPF's API and how NDIS regards binding, you'll need to tackle a few issues:
Multiple simultaneous bindings to the same adapter. (This is a rarely-used feature; NPF is one of two protocols that I know use this, so it's not really discussed much in the MSDN documentation.) The only way to get multiple simultaneous bindings in NDIS 6.x is to call NdisOpenAdapterEx twice within the same ProtocolBindAdapterEx call. That's going to be challenging, because NPF's model is to open a new binding whenever an API call comes in from usermode; it doesn't know in advance how many handles will need to be opened.
If another bind request comes in, you can attempt to close all previous handles to that adapter (transparently to the NPF API[!]), call NdisReEnumerateProtocolBindings, then open N+1 handles in your upcoming ProtocolBindAdpaterEx handler. But this is brittle.
You can also try and merge all API calls to the same adapter. If a second bind request comes in, just route it to the pre-existing binding to that adapter. This might be difficult, depending on how NPF's internals work. (I'm not allowed to read NPF source code; I can't say.)
Finally, the cheesy solution is to just allocate two (or three) binding handles always, and keep the extras cached in case Wireshark needs them. This is cheap to implement, but still a bit fragile, since you can't really know if Wireshark will want more handles than you pre-allocated.
Missing INetCfg bindings. NDIS 5.x protocols are allowed to bind to an adapter even if the protocol isn't actually supposed to be bound (according to INetCfg). Wireshark uses this to get itself bound to all sorts of random adapters, without worrying too much about whether INetCfg agrees that NPF should be bound. Once you convert to NDIS 6.x, the rules are enforced strictly, and you'll need to make sure that your protocol's INF has a LowerRange keyword for each type of adapter you want to bind over. (I.e., the NPF protocol should show up in the Adapter Properties dialog box.)
Asynchronous bindings. The NdisReEnumerateProtocolBindings model is that you call it, and NDIS will make an attempt to bind your protocol to all bindable adapters. If the adapter isn't bindable for some reason (perhaps it's in a low-power state, or it's being surprise-removed), then NDIS will simply not call your protocol back. It's going to be tough to know exactly when to give up and return failure to the usermode NPF API, since you don't get a callback saying "you won't bind to this adapter". You may be able to use NetEventBindsComplete, but frankly that's kind of a dodgy, ill-defined event and I'm not convinced it's bulletproof. I'd put in a timeout, then use the NetEvent as a hint to cut the timeout short.
Finally, I just wanted to note that, although you said that you wanted to minimize the amount of churn in WinPcap, you might want to consider repackaging its driver as an NDIS LWF. LWFs were designed for exactly this purpose, so they tend to fit better with NPF's needs. (In particular, LWFs can see native 802.11 traffic, can get more accurate data without going through the loopback hack, and are quite a bit simpler than protocols.)