I'm writing an application that reading audio endpoint on windows, via WASAPI. For now I have similar code for capture thread (treat this code as minimal example):
DWORD CWASAPICapture::DoCaptureThread()
{
BYTE *pData;
UINT32 framesAvailable = 1;
DWORD flags, state;
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr))
{
printf("Unable to initialize COM in render thread: %x\n", hr);
return hr;
}
while (framesAvailable != 0) {
// get the available data in the shared buffer.
state = WaitForSingleObject(_AudioSamplesReadyEvent, INFINITE);
hr = _CaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, NULL);
if ((state != 0) || (hr != S_OK)) {
// my breakpoint
assert(false);
}
UINT32 framesToCopy = min(framesAvailable, static_cast<UINT32>((_CaptureBufferSize - _CurrentCaptureIndex) / _FrameSize));
if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
ZeroMemory(&_CaptureBuffer[_CurrentCaptureIndex], framesToCopy*_FrameSize);
} else {
CopyMemory(&_CaptureBuffer[_CurrentCaptureIndex], pData, framesToCopy*_FrameSize);
}
_CurrentCaptureIndex += framesToCopy*_FrameSize;
// release data
hr = _CaptureClient->ReleaseBuffer(framesAvailable);
assert(hr == S_OK);
}
CoUninitialize();
return 0;
}
In Init section I requested buffer for 1s duration, blockAlign equal to 8, so my captureBufferSize = 3528000 bytes. _AudioSamplesReadyEvent set as _AudioClient->SetEventHandle(_AudioSamplesReadyEvent). Packet size = 441. Other code based on official windows SDK CaptureSharedEventDriven example, this method I changed.
Problem:
Program always fails on my breakpoint (by design) and i'm watching into variables, expected values are hr = AUDCLNT_S_BUFFER_EMPTY (0x08890001) and _CurrentCaptureIndex = 3528000, and I even got this values like in 1 from 10 cases... Usually hr = AUDCLNT_S_BUFFER_EMPTY (0x08890001) and _CurrentCaptureIndex equals to random values, most often is 0, 3528 (8 packets) and 10584 (24 packets).
First of all, when I just started to use WaitForSingleObject with _AudioClient->SetEventHandle, I supposed it signals when buffer is filled for requested time in _AudioClient->Initialize() (MSDN description pretty fluent), but that wrong, next my suggestion was that it waits until a single packet with data got filled, but as my example shows, I don't have available frames in buffer. I tried ti use GetNextPacketSize nex to WaitSingleObject, but still the same result.
My questions is:
Why my example works that way?
What exactly IAudioClient->SetEventHandle() signaling about to WaitForSingleObject()?
How to read buffer of size I requested properly?
UPD1: Sleep() placed before while, instead of WaitForSingleObject() works fine, I get all frames for time my thread slept.
UPD2: minimal example
Related
As a college project we have to develop a Server-Client music streaming application using the DirectSound API. However, due to lack of information, guides or tutorials online, the only source I can gather info about it is the piece of code provided below (which was the only thing provided by the lecturer). Can anyone help me understand the general purpose of these functions and the order they should be implemented in?
Thanks in advance.
IDirectSound8 * directSound = nullptr;
IDirectSoundBuffer * primaryBuffer = nullptr;
IDirectSoundBuffer8 * secondaryBuffer = nullptr;
BYTE * dataBuffer = nullptr;
DWORD dataBufferSize;
DWORD averageBytesPerSecond;
// Search the file for the chunk we want
// Returns the size of the chunk and its location in the file
HRESULT FindChunk(HANDLE fileHandle, FOURCC fourcc, DWORD & chunkSize, DWORD & chunkDataPosition)
{
HRESULT hr = S_OK;
DWORD chunkType;
DWORD chunkDataSize;
DWORD riffDataSize = 0;
DWORD fileType;
DWORD bytesRead = 0;
DWORD offset = 0;
if (SetFilePointer(fileHandle, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
{
return HRESULT_FROM_WIN32(GetLastError());
}
while (hr == S_OK)
{
if (ReadFile(fileHandle, &chunkType, sizeof(DWORD), &bytesRead, NULL) == 0)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (ReadFile(fileHandle, &chunkDataSize, sizeof(DWORD), &bytesRead, NULL) == 0)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
switch (chunkType)
{
case fourccRIFF:
riffDataSize = chunkDataSize;
chunkDataSize = 4;
if (ReadFile(fileHandle, &fileType, sizeof(DWORD), &bytesRead, NULL) == 0)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
break;
default:
if (SetFilePointer(fileHandle, chunkDataSize, NULL, FILE_CURRENT) == INVALID_SET_FILE_POINTER)
{
return HRESULT_FROM_WIN32(GetLastError());
}
}
offset += sizeof(DWORD) * 2;
if (chunkType == fourcc)
{
chunkSize = chunkDataSize;
chunkDataPosition = offset;
return S_OK;
}
offset += chunkDataSize;
if (bytesRead >= riffDataSize)
{
return S_FALSE;
}
}
return S_OK;
}
// Read a chunk of data of the specified size from the file at the specifed location into the
supplied buffer
HRESULT ReadChunkData(HANDLE fileHandle, void * buffer, DWORD buffersize, DWORD bufferoffset)
{
HRESULT hr = S_OK;
DWORD bytesRead;
if (SetFilePointer(fileHandle, bufferoffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (ReadFile(fileHandle, buffer, buffersize, &bytesRead, NULL) == 0)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
bool Initialise()
{
HRESULT result;
DSBUFFERDESC bufferDesc;
WAVEFORMATEX waveFormat;
// Initialize the direct sound interface pointer for the default sound device.
result = DirectSoundCreate8(NULL, &directSound, NULL);
if (FAILED(result))
{
return false;
}
// Set the cooperative level to priority so the format of the primary sound buffer can be modified.
// We use the handle of the desktop window since we are a console application. If you do write a
// graphical application, you should use the HWnd of the graphical application.
result = directSound->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY);
if (FAILED(result))
{
return false;
}
// Setup the primary buffer description.
bufferDesc.dwSize = sizeof(DSBUFFERDESC);
bufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
bufferDesc.dwBufferBytes = 0;
bufferDesc.dwReserved = 0;
bufferDesc.lpwfxFormat = NULL;
bufferDesc.guid3DAlgorithm = GUID_NULL;
// Get control of the primary sound buffer on the default sound device.
result = directSound->CreateSoundBuffer(&bufferDesc, &primaryBuffer, NULL);
if (FAILED(result))
{
return false;
}
// Setup the format of the primary sound bufffer.
// In this case it is a .WAV file recorded at 44,100 samples per second in 16-bit stereo (cd audio
format).
// Really, we should set this up from the wave file format loaded from the file.
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nSamplesPerSec = 44100;
waveFormat.wBitsPerSample = 16;
waveFormat.nChannels = 2;
waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
waveFormat.cbSize = 0;
// Set the primary buffer to be the wave format specified.
result = primaryBuffer->SetFormat(&waveFormat);
if (FAILED(result))
{
return false;
}
return true;
}
void Shutdown()
{
// Destroy the data buffer
if (dataBuffer != nullptr)
{
delete[] dataBuffer;
dataBuffer = nullptr;
}
// Release the primary sound buffer pointer.
if (primaryBuffer != nullptr)
{
primaryBuffer->Release();
primaryBuffer = nullptr;
}
// Release the direct sound interface pointer.
if (directSound != nullptr)
{
directSound->Release();
directSound = nullptr;
}
}
// Load the wave file into memory and setup the secondary buffer.
bool LoadWaveFile(TCHAR * filename)
{
WAVEFORMATEXTENSIBLE wfx = { 0 };
WAVEFORMATEX waveFormat;
DSBUFFERDESC bufferDesc;
HRESULT result;
IDirectSoundBuffer * tempBuffer;
DWORD chunkSize;
DWORD chunkPosition;
DWORD filetype;
HRESULT hr = S_OK;
// Open the wave file
HANDLE fileHandle = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0,
NULL);
if (fileHandle == INVALID_HANDLE_VALUE)
{
return false;
}
if (SetFilePointer(fileHandle, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
{
return false;
}
// Make sure we have a RIFF wave file
FindChunk(fileHandle, fourccRIFF, chunkSize, chunkPosition);
ReadChunkData(fileHandle, &filetype, sizeof(DWORD), chunkPosition);
if (filetype != fourccWAVE)
{
return false;
}
// Locate the 'fmt ' chunk, and copy its contents into a WAVEFORMATEXTENSIBLE structure.
FindChunk(fileHandle, fourccFMT, chunkSize, chunkPosition);
ReadChunkData(fileHandle, &wfx, chunkSize, chunkPosition);
// Find the audio data chunk
FindChunk(fileHandle, fourccDATA, chunkSize, chunkPosition);
dataBufferSize = chunkSize;
// Read the audio data from the 'data' chunk. This is the data that needs to be copied into
// the secondary buffer for playing
dataBuffer = new BYTE[dataBufferSize];
ReadChunkData(fileHandle, dataBuffer, dataBufferSize, chunkPosition);
CloseHandle(fileHandle);
// Set the wave format of the secondary buffer that this wave file will be loaded onto.
// The value of wfx.Format.nAvgBytesPerSec will be very useful to you since it gives you
// an approximate value for how many bytes it takes to hold one second of audio data.
waveFormat.wFormatTag = wfx.Format.wFormatTag;
waveFormat.nSamplesPerSec = wfx.Format.nSamplesPerSec;
waveFormat.wBitsPerSample = wfx.Format.wBitsPerSample;
waveFormat.nChannels = wfx.Format.nChannels;
waveFormat.nBlockAlign = wfx.Format.nBlockAlign;
waveFormat.nAvgBytesPerSec = wfx.Format.nAvgBytesPerSec;
waveFormat.cbSize = 0;
// Set the buffer description of the secondary sound buffer that the wave file will be loaded onto.
// In this example, we setup a buffer the same size as that of the audio data. For the assignment,
// your secondary buffer should only be large enough to hold approximately four seconds of data.
bufferDesc.dwSize = sizeof(DSBUFFERDESC);
bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY;
bufferDesc.dwBufferBytes = dataBufferSize;
bufferDesc.dwReserved = 0;
bufferDesc.lpwfxFormat = &waveFormat;
bufferDesc.guid3DAlgorithm = GUID_NULL;
// Create a temporary sound buffer with the specific buffer settings.
result = directSound->CreateSoundBuffer(&bufferDesc, &tempBuffer, NULL);
if (FAILED(result))
{
return false;
}
// Test the buffer format against the direct sound 8 interface and create the secondary buffer.
result = tempBuffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&secondaryBuffer);
if (FAILED(result))
{
return false;
}
// Release the temporary buffer.
tempBuffer->Release();
tempBuffer = nullptr;
return true;
}
void ReleaseSecondaryBuffer()
{
// Release the secondary sound buffer.
if (secondaryBuffer != nullptr)
{
(secondaryBuffer)->Release();
secondaryBuffer = nullptr;
}
}
bool PlayWaveFile()
{
HRESULT result;
unsigned char * bufferPtr1;
unsigned long bufferSize1;
unsigned char * bufferPtr2;
unsigned long bufferSize2;
BYTE * dataBufferPtr = dataBuffer;
DWORD soundBytesOutput = 0;
bool fillFirstHalf = true;
LPDIRECTSOUNDNOTIFY8 directSoundNotify;
DSBPOSITIONNOTIFY positionNotify[2];
// Set position of playback at the beginning of the sound buffer.
result = secondaryBuffer->SetCurrentPosition(0);
if (FAILED(result))
{
return false;
}
// Set volume of the buffer to 100%.
result = secondaryBuffer->SetVolume(DSBVOLUME_MAX);
if (FAILED(result))
{
return false;
}
// Create an event for notification that playing has stopped. This is only useful
// when your audio file fits in the entire secondary buffer (as in this example).
// For the assignment, you are going to need notifications when the playback has reached the
// first quarter of the buffer or the third quarter of the buffer so that you know when
// you should copy more data into the secondary buffer.
HANDLE playEventHandles[1];
playEventHandles[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
result = secondaryBuffer->QueryInterface(IID_IDirectSoundNotify8, (LPVOID*)&directSoundNotify);
if (FAILED(result))
{
return false;
}
// This notification is used to indicate that we have finished playing the buffer of audio. In
// the assignment, you will need two different notifications as mentioned above.
positionNotify[0].dwOffset = DSBPN_OFFSETSTOP;
positionNotify[0].hEventNotify = playEventHandles[0];
directSoundNotify->SetNotificationPositions(1, positionNotify);
directSoundNotify->Release();
// Now we can fill our secondary buffer and play it. In the assignment, you will not be able to fill
// the buffer all at once since the secondary buffer will not be large enough. Instead, you will need to
// loop through the data that you have retrieved from the server, filling different sections of the
// secondary buffer as you receive notifications.
// Lock the first part of the secondary buffer to write wave data into it. In this case, we lock the entire
// buffer, but for the assignment, you will only want to lock the half of the buffer that is not being played.
// You will definately want to look up the methods for the IDIRECTSOUNDBUFFER8 interface to see what these
// methods do and what the parameters are used for.
result = secondaryBuffer->Lock(0, dataBufferSize, (void**)&bufferPtr1, (DWORD*)&bufferSize1, (void**)&bufferPtr2, (DWORD*)&bufferSize2, 0);
if (FAILED(result))
{
return false;
}
// Copy the wave data into the buffer. If you need to insert some silence into the buffer, insert values of 0.
memcpy(bufferPtr1, dataBuffer, bufferSize1);
if (bufferPtr2 != NULL)
{
memcpy(bufferPtr2, dataBuffer, bufferSize2);
}
// Unlock the secondary buffer after the data has been written to it.
result = secondaryBuffer->Unlock((void*)bufferPtr1, bufferSize1, (void*)bufferPtr2, bufferSize2);
if (FAILED(result))
{
return false;
}
// Play the contents of the secondary sound buffer. If you want play to go back to the start of the buffer
// again, set the last parameter to DSBPLAY_LOOPING instead of 0. If play is already in progress, then
// play will just continue.
result = secondaryBuffer->Play(0, 0, 0);
if (FAILED(result))
{
return false;
}
// Wait for notifications. In this case, we only have one notification so we could use WaitForSingleObject,
// but for the assignment you will need more than one notification, so you will need WaitForMultipleObjects
result = WaitForMultipleObjects(1, playEventHandles, FALSE, INFINITE);
// In this case, we have been notified that playback has finished so we can just finish. In the assignment,
// you should use the appropriate notification to determine which part of the secondary buffer needs to be
// filled and handle it accordingly.
CloseHandle(playEventHandles[0]);
return true;
}
DirectSound is deprecated. See below for recommended replacements.
Documentation can be found on Microsoft Docs. The last time samples for DirectSound were shipped was in the legacy DirectX SDK (November 2007) release which is why you are having a hard time finding them. You can find them on GitHub. The headers and link libraries for DirectSound are in the Windows SDK.
Recommendations
For 'real-time mixing and effects' often used in games, the modern replacement is XAudio2. XAudio 2.9 is included in Windows 10, and is available through a simple side-by-side redistribution model for Windows 7, Windows 8.0, and Windows 8.1. Documentation can be found here, samples can be found here, and the
redist can be found here. You may also want to take a look at DirectX Tool Kit for Audio.
For other audio output and input, see Windows Core Audio APIs (WASAPI) which is supported on Windows Vista, Windows 7, Windows 8.0, Windows 8.1, and Windows 10. Documentation can be found here. Some samples can be found on GitHub in Xbox-ATG-Samples and Windows-universal-samples--while these are all UWP samples, the API also supports Win32 desktop.
There's also a new Microsoft Spatial Sounds API on Windows 10 (a.k.a. Windows Sonic). Documentation can be found here. Samples can be found on GitHub in Xbox-ATG-Samples.
I stumbled across a Windows 10 crash that's probably new since the Windows 10 update to 2004.
The problem is a crash in ntdll when COM is starting. COM is needed to use Core Audio.
Code which triggers it:
IMMDeviceEnumerator* enumerator = nullptr;
CoInitializeEx(nullptr, COINIT_MULTITHREADED); // Also with STA
HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, 0, CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&enumerator);
Popup:
Unhandled exception at 0x00007FFE7846D3D1 (ntdll.dll) in MicTest.exe:
A LIST_ENTRY has been corrupted (i.e. double remove).
Callstack:
ntdll.dll!LdrpInsertDataTableEntry()
ntdll.dll!LdrpMapDllWithSectionHandle()
ntdll.dll!LdrpMapDllNtFileName()
ntdll.dll!LdrpMapDllFullPath()
ntdll.dll!LdrpProcessWork()
ntdll.dll!LdrpLoadDllInternal()
ntdll.dll!LdrpLoadForwardedDll()
ntdll.dll!LdrpGetDelayloadExportDll()
ntdll.dll!LdrpHandleProtectedDelayload()
ntdll.dll!LdrResolveDelayLoadedAPI()
combase.dll!00007ffe769ab1c2()
combase.dll!00007ffe769c56fd()
combase.dll!00007ffe769b3b27()
ntdll.dll!RtlRunOnceExecuteOnce()
KernelBase.dll!InitOnceExecuteOnce()
combase.dll!00007ffe7696c11f()
combase.dll!00007ffe7696baf8()
combase.dll!00007ffe7696b9e6()
combase.dll!00007ffe76907df2()
combase.dll!00007ffe76906fce()
combase.dll!00007ffe76907928()
combase.dll!00007ffe76907718()
MicTest.exe!Microphone::Microphone() Line 23 C++
[External Code]
MicTest.exe!main(int argc, char * * argv) Line 67 C++
It's not 100% reproducible there, sometimes the crash is a few lines later in IAudioClient::Initialize when ntdll crashes under loader lock in
> ntdll.dll!LdrpInitializeThread()
ntdll.dll!_LdrpInitialize()
ntdll.dll!LdrpInitialize()
ntdll.dll!LdrInitializeThunk()
This happens in one of the new Windows 10 threadpool threads that Windows itself uses to load a DLL. The offending code shows up as AudioSes.dll!CAudioClient::CreateRemoteStream (Note: AudioSes.dll is Core Audio, symbols per the Microsoft Symbol Server.)
Is this a known problem? Is there a workaround?
For completeness, complete code:
Microphone::Microphone()
{
IMMDeviceEnumerator* enumerator = nullptr;
CoInitializeEx(nullptr, COINIT_MULTITHREADED); // crash near here
HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, 0, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&enumerator);
if (!enumerator) throw std::system_error(hr, std::system_category());
IMMDevice* device = nullptr;
hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &device);
enumerator->Release();
if (!device) throw std::system_error(hr, std::system_category());
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&client);
device->Release();
if (!client) throw std::system_error(hr, std::system_category());
WAVEFORMATEXTENSIBLE *pwfx = nullptr;
hr = client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&pwfx));
if (SUCCEEDED(hr) && pwfx)
{
this->span.sampleRate = pwfx->Format.nSamplesPerSec;
this->blockAlign = pwfx->Format.nBlockAlign;
// In general, this is NOT pwfx->wBitsPerSample;
if (pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
{
this->format = fmt_FP32;
}
else if (pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)
{
this->format = fmt_PCM;
}
else
{
// Can't deal with that format, and Core Audio ought to support FP32.
pwfx->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
this->format = fmt_FP32;
}
}
// or crash here
hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 1000000, 0, &pwfx->Format, NULL);
if (!SUCCEEDED(hr))
{
throw std::system_error(hr, std::system_category());
}
CoTaskMemFree(pwfx);
// Start capture
hr = client->GetService( IID_IAudioCaptureClient, (void**)&capture);
client->Start();
As RbMm pointed out, this requires looking at the assembly. It turns out that Windows 10 2004 corrupts its PEB_LDR_DATA data structure. This is a per-process structure which contains important data. In particular, LdrpInitializeThread uses PEB_LDR_DATA::InLoadOrderModuleList directly after acquiring the Loader Lock. This can be a null pointer for unclear reasons.
Since this happens in a Windows-created thread in the default threadpool, it's unrelated to and unsynchronized with user code. The exact point of crash can vary - the damage to the PEB_LDR_DATA structure appears to be more extensive.
Workaround
While this is not a solution for the CoInitializeEx crash, it appears that the second crash due to PEB_LDR_DATA::InLoadOrderModuleList corruption can be avoided with minor changes:
Don't set a buffer size, but let Windows determine one and roll with that:
hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 0, 0, &pwfx->Format, NULL);
UINT32 bufSize = 0;
hr = client->GetBufferSize(&bufSize);
Determine when to call GetBuffer
auto samplePollRatio = pwfx->Format.nSamplesPerSec / 2666;
auto nextPoll = std::chrono::system_clock::now() +
std::chrono::milliseconds(bufSize / samplePollRatio);
And wait for it
std::this_thread::sleep_until(nextPoll);
auto hr = capture->GetBuffer(&buf, &samples, &flags, nullptr, nullptr);
This is a reasonable pattern anyway. The MSDN example also has a Sleep call, but sleep_until is more robust against variable delays in processing. (E.g. on the first call you may have some more delay when opening a WAV file to save the results etc). But by delaying the first sample acquisition you bypass an apparent race condition in Windows 10.
I'm trying to write a program that transfers images and videos from a camera (for my personal use, on Win 8.1). I'm using Microsoft's example code as a starting point (WIA Tutorial), and I've hit a wall trying to detect connected camera devices. The problem is that there are no errors and the code seems to work, but it just doesn't detect any connected camera (I've tried with two different cameras), while the camera is clearly detected by the OS (shows up in Windows Explorer).
Am I missing something? Is IWiaDevMgr2::EnumDeviceInfo not the way to detect connected devices? Here's the code I'm using:
HRESULT WiaCreateDeviceManager(IWiaDevMgr2 **ppWiaDevMgr)
{
if(NULL == ppWiaDevMgr) return E_INVALIDARG;
*ppWiaDevMgr = NULL;
// Create an instance of the device manager
HRESULT hr = CoCreateInstance(CLSID_WiaDevMgr2, NULL, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr2, (void**)ppWiaDevMgr);
return hr;
}
HRESULT WiaEnumerateDevices(IWiaDevMgr2 *pWiaDevMgr)
{
if(NULL == pWiaDevMgr)
{
return E_INVALIDARG;
}
// Get a device enumerator interface
IEnumWIA_DEV_INFO *pWiaEnumDevInfo = NULL;
HRESULT hr = pWiaDevMgr->EnumDeviceInfo(WIA_DEVINFO_ENUM_LOCAL, &pWiaEnumDevInfo);
assert(hr == S_OK);
if(SUCCEEDED(hr))
{
ULONG count(911);
HRESULT res = pWiaEnumDevInfo->GetCount(&count);
if(res == S_OK) printf("EnumDeviceInfo: count = %lu\n", count); // count is always zero
else printf("IEnumWIA_DEV_INFO::GetCount() failed!\n");
// Loop until you get an error or pWiaEnumDevInfo->Next returns
// S_FALSE to signal the end of the list.
while(S_OK == hr)
{
// Get the next device's property storage interface pointer
IWiaPropertyStorage *pWiaPropertyStorage = NULL;
hr = pWiaEnumDevInfo->Next(1, &pWiaPropertyStorage, NULL);
// pWiaEnumDevInfo->Next will return S_FALSE when the list is
// exhausted, so check for S_OK before using the returned
// value.
if(hr == S_OK)
{
// Do something with the device's IWiaPropertyStorage*
WiaReadProperties(pWiaPropertyStorage); // this line is never reached
// Release the device's IWiaPropertyStorage*
pWiaPropertyStorage->Release();
pWiaPropertyStorage = NULL;
}
}
// If the result of the enumeration is S_FALSE (which
// is normal), change it to S_OK.
if(S_FALSE == hr) hr = S_OK;
// Release the enumerator
pWiaEnumDevInfo->Release();
pWiaEnumDevInfo = NULL;
}
return hr;
}
int main()
{
...
IWiaDevMgr2 *wiamgr;
WiaCreateDeviceManager(&wiamgr);
HRESULT res = WiaEnumerateDevices(wiamgr); // res is always S_OK, but no device is detected
...
}
Apparently, WIA does not support camera devices on Windows Vista and later. I've only seen this implied or mentioned in passing twice in the WIA documentation, the last time being on this page. I can't believe this is happening, after I've spent so much time researching WIA. Apparently, I'm supposed to be using WPD for cameras, not WIA.
Edit: That being said, I'm still not sure what's going on. If I can't use WIA programmatically on Win 8.1, then why do these PowerShell commands work?
$WIAdialog = New-Object -ComObject "WIA.CommonDialog"
$Device = $WIAdialog.ShowSelectDevice()
$i=$WIAdialog.ShowAcquireImage()
$i.SaveFile("$pwd\test.$($i.fileExtension)")
Is it that only the API doesn't work for cameras, while the Scripting Model does?
In my custom output pin I call IMemAllocator->GetBuffer to obtain a new sample and copy the data into it:
HRESULT MCMyOutputPin::Deliver(IMediaSample* sample)
{
HRESULT hr = NO_ERROR;
myLogger->LogDebug("In Outputpin Deliver", L"D:\\TEMP\\yc.log");
if (sample->GetActualDataLength() > 0)
{
IMediaSample *outsample;
hr = m_pAllocator->GetBuffer(&outsample, NULL, NULL, NULL);
BYTE* sampleBuffer = NULL;
BYTE* newBuffer = NULL;
sample->GetPointer(&sampleBuffer);
UINT32 ulDataLen = sample->GetSize();
outsample->GetPointer(&newBuffer);
ZeroMemory(newBuffer, ulDataLen);
CopyMemory(newBuffer, sampleBuffer, ulDataLen);
outsample->SetActualDataLength(ulDataLen);
m_pInputPin->Receive(outsample);
}
return hr;
}
The problem is that the call to GetBuffer blocks at the second call.
According to some research i have done this cann happen if the buffer size is to small. So i tried doubling it.
HRESULT MCMyOutputPin::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProps)
{
myLogger->LogDebug("On DecideBufferSIze", L"D:\\TEMP\\yc.log");
ALLOCATOR_PROPERTIES act;
HRESULT hr;
// by default we do something like this...
pProps->cbAlign = 1;
pProps->cBuffers = 1;
long buffersize = this->CurrentMediaType().lSampleSize;
pProps->cbBuffer = buffersize * 2;
pProps->cbPrefix = 0;
hr = pAlloc->SetProperties(pProps, &act);
if (FAILED(hr)) return hr;
// make sure the allocator is OK with it.
if ((pProps->cBuffers > act.cBuffers) ||
(pProps->cbBuffer > act.cbBuffer) ||
(pProps->cbAlign > act.cbAlign))
return E_FAIL;
return NOERROR;
}
That didn't help. I probably just have forgotten something. As it is the second call i probably should clean up something after a call to GetBuffer but i don't know what.
Memory allocators manage fixed number of media samples. When all media samples are given away, GetBuffer blocks until some media sample gets back availalble.
In you particular case, you have ONE media sample in the allocator and you do not do properly release the COM interface, hence it never gets back availalble, and you get infinite lock.
m_pInputPin->Receive(outsample);
outsample->Release(); // <<--- Here is the missing thing
I am using Qt on windows platform.
i want to get and display vendor id and product id of a plugged usb device from my local system.
Below is my full source code to get the vendor id and product id from the usb device.
when i run the my qt application it does not throw me any errors .
so i plug the usb device into the system.
but my print statement displays the result as below
qDebug ()<<pDetData->DevicePath;
i get the result as 0x4
Whether i have any implementation mistakes in my source code ?
if so please guide me what i am doing wrong..
Have i missed out any other functions ?
Is it possible to get the vendor id and product id from the usb device based on my source code .( my implementation of the code ) ?
kindly find my source code below
static GUID GUID_DEVINTERFACE_USB_DEVICE = { 0xA5DCBF10L, 0x6530, 0x11D2,
{ 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } };
HANDLE hInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE,NULL,NULL,
DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if ( hInfo == INVALID_HANDLE_VALUE )
{
qDebug ()<<"invalid";
}
else
{
qDebug ()<<"valid handle";
SP_DEVINFO_DATA DeviceInfoData;
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
SP_INTERFACE_DEVICE_DATA Interface_Info;
Interface_Info.cbSize = sizeof(Interface_Info);
BYTE Buf[1024];
DWORD i;
DWORD InterfaceNumber= 0;
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd =
(PSP_DEVICE_INTERFACE_DETAIL_DATA) Buf;
for (i=0;SetupDiEnumDeviceInfo(hInfo,i,&DeviceInfoData);i++)
{
DWORD DataT;
LPTSTR buffer = NULL;
DWORD buffersize = 0;
while (!SetupDiGetDeviceRegistryProperty( hInfo,
&DeviceInfoData,
SPDRP_DEVICEDESC,
&DataT,
(PBYTE)buffer,
buffersize,
&buffersize))
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// Change the buffer size.
if (buffer) LocalFree(buffer);
buffer = (LPTSTR)LocalAlloc(LPTR,buffersize);
}
else
{
// Insert error handling here.
break;
}
qDebug ()<<(TEXT("Device Number %i is: %s\n"),i, buffer);
if (buffer) LocalFree(buffer);
if ( GetLastError() != NO_ERROR
&& GetLastError() != ERROR_NO_MORE_ITEMS )
{
// Insert error handling here.
qDebug ()<<"return false";
}
InterfaceNumber = 0; // this just returns the first one, you can iterate on this
if (SetupDiEnumDeviceInterfaces(hInfo,
NULL,
&GUID_DEVINTERFACE_USB_DEVICE,
InterfaceNumber,
&Interface_Info))
{
printf("Got interface");
DWORD needed;
pspdidd->cbSize = sizeof(*pspdidd);
SP_DEVICE_INTERFACE_DETAIL_DATA *pDetData = NULL;
DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA)
+ 256;
SetupDiGetDeviceInterfaceDetail(hInfo,
&Interface_Info, pDetData,dwDetDataSize, NULL,
&DeviceInfoData);
qDebug ()<<pDetData->DevicePath;
//qDebug ()<<QString::fromWCharArray(pDetData->DevicePath);
}
else
{
printf("\nNo interface");
//ErrorExit((LPTSTR) "SetupDiEnumDeviceInterfaces");
if ( GetLastError() == ERROR_NO_MORE_ITEMS)
printf(", since there are no more items found.");
else
printf(", unknown reason.");
}
// Cleanup
SetupDiDestroyDeviceInfoList(hInfo);
qDebug ()<<"return true";
}
}
}
--------------- Edited to add: -----------------
Hi... the application comes and prints this
\?\usb#vid_04f2&pid_0111#5&1ba5a77f&0&2#{a5dcbf1 0-6530-11d2-901f-00c04fb951ed}
again it goes to while loop .... here it gets breaked in the else statement...
Qt Code:
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
// Change the buffer size.
if (buffer) LocalFree(buffer);
buffer = (LPTSTR)LocalAlloc(LPTR,buffersize);
} else {
qDebug ()<<"Here it quits the application";
// Insert error handling here. break;
}
Any ideas in this....
After this line:
SP_DEVICE_INTERFACE_DETAIL_DATA *pDetData = NULL;
Add this:
DWORD dwDetDataSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA) + 256;
pDetData = (_SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc (dwDetDataSize);
pDetData->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
After this line:
qDebug ()<<pDetData->DevicePath;
Add this:
free(pDetData);
But eventually you're going to have to read the docs for SetupDiGetDeviceInterfaceDetail(). Do it, there are lots of functions that work like this, with pointers to variable-size structs.
-------- Edited to add: --------
You're really going about this the wrong way. I see you're following the advice you got here, and it's taken you down the wrong path. idVendor and idProduct can only be found in the USB_DEVICE_DESCRIPTOR (MSDN).
It looks like you already know how to get the device handle (using CreateFile()). After that, you call WinUsb_Initialize() (MSDN). That gets you a WINUSB_INTERFACE_HANDLE.
Once you have that handle, you want to call WinUsb_GetDescriptor() (MSDN), with the DescriptorType set to URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE. I can't test code now, but it will look something like this:
USB_DEVICE_DESCRIPTOR udd;
memset(&udd, 0, sizeof(udd));
ULONG LengthTransferred = 0;
WinUsb_GetDescriptor(
winusb_interface_handle, // returned by WinUsbInitialize
URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE,
0, // not sure if we need this
0x409, // not sure if we need this
&udd,
sizeof(udd),
&LengthTransferred);
After that, udd->idVendor and udd->idProduct should have what you want.
Microsoft used to supply sample code for all this in the DDK, and probably still does, but I don't have access to one.
---------- Edited to add: ----------
Daniel K writes that the code should really be:
USB_DEVICE_DESCRIPTOR udd;
memset(&udd, 0, sizeof(udd));
ULONG LengthTransferred = 0;
WinUsb_GetDescriptor(
winusb_interface_handle, // returned by WinUsbInitialize
USB_DEVICE_DESCRIPTOR_TYPE, // Daniel K's suggestion
0,
0x409, // asks for English
&udd,
sizeof(udd),
&LengthTransferred);
See the comments for further details.
An alternative is to obtain the hardwareID which includes the VID and PID.
Call SetupDiGetDeviceRegistryProperty with SPDRP_HARDWAREID like so:
wchar_t *hardwareID;
// First get requiredLength
SetupDiGetDeviceRegistryProperty(deviceInfoList, &deviceInfoData, SPDRP_HARDWAREID, NULL, NULL, 0, &requiredLength);
hardwareID = (wchar_t*)(new char[requiredLength]());
// Second call to populate hardwareID
SetupDiGetDeviceRegistryProperty(deviceInfoList, &deviceInfoData, SPDRP_HARDWAREID, NULL, (PBYTE)hardwareID, requiredLength, NULL);
// Display the string
qDebug() << "hardwareID =" << QString::fromWCharArray(hardwareID);
This will give you a string like USB\ROOT_HUB20&VID1002&PID4396&REV0000 which you can parse.
*Note: not all devices will have a VID and PID, such as non-USB devices.
You are enumerating the device "interface". Interfaces do not have a VID or PID - device instances do. I am not sure whether you are enumerating the interfaces to narrow down the devices you are interested in, for because it's an error.
If you just enumerate the device instances, then you can call SetupDiGetDeviceProperty with DEVPKEY_Device_HardwareIds and then grep the resulting hardware id for the VID and PID.
If you are using the device interfaces on purpose, then you need to call SetupDiGetDeviceInterfaceDetail once with a NULL PSP_DEVICE_INTERFACE_DETAIL parameter and a valid requiredSize pointer to get the required size of memory to allocate, allocate that memory and then call the function again. In that call, the last parameter is a SP_DEVINFO_DATA structure, which once retrieved, you can use in the call to SetupDiGetDeviceProperty as I mentioned above.