In the Sound Control Panel in Windows, and in the Volume Mixer, all audio devices have a specific icon set.
Is it possible to get which icon is set for a device through the Windows API?
Sound Control Panel
Volume Mixer
As suggested by Simon Mourier in the comments above, there is a PKEY that can access that data.
You can follow along with most of this sample and just replace PKEY_Device_FriendlyNamewith PKEY_DeviceClass_IconPath.
In short, it's something like this:
// [...]
IMMDevice *pDevice; // the device you want to look up
HRESULT hr = S_OK;
IPropertyStore *pProps = NULL;
PROPVARIANT varName;
hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
EXIT_ON_ERROR(hr);
PropVariantInit(&varName);
hr = pProps->GetValue(PKEY_DeviceClass_IconPath, &varName);
EXIT_ON_ERROR(hr);
// [...]
Related
I have the following, very simple code (delphi/Object Pascal) which uses Microsoft SAPI 5.4 to do some Text To Speech
// init
var engine : tspvoice;
var voice : ispeechobjecttoken;
engine:=tspvoice.Create(nil);
// pick the first voice and assign it to the engine
voice := ISpeechObjectToken(0);
engine.Voice := Voice;
// speak
engine.Speak(text, SVSFlagsAsync);
However I don't see any control on the output format (Bits per sample / Frequency). I have a sample app (TTSapp) coming from an old SAPI SDKdownload, which allows to control such parameters. Unfortunately I can't locate its source code anywhere, and I can't find any download to an up-to-date sapi SDK too.
Any hints?
I typically don't use delphi, but I do have an example in C++ if it helps. In this case you can see I'm messing with the CSpStreamFormat and then calling SPBindToFile to set it. sapi.h has all the SPSTREAMFORMAT types you can use.
HRESULT hr = S_OK;
CComPtr<ISpObjectToken> cpVoiceToken;
CComPtr<ISpVoice> cpVoice;
CSpStreamFormat cAudioFmt;
CComPtr<ISpStream> cpStream;
::CoInitialize(NULL);
hr = cAudioFmt.AssignFormat(SPSF_22kHz16BitMono);
hr = SPBindToFile(L"E:\\fileName.wav", SPFM_CREATE_ALWAYS, &cpStream, &cAudioFmt.FormatId(), cAudioFmt.WaveFormatExPtr());
hr = cpVoice.CoCreateInstance(CLSID_SpVoice);
cpVoice->SetOutput(cpStream, TRUE);
hr = cpVoice->Speak(L"My spoken text goes here", SPF_DEFAULT, NULL);
cpStream->Close();
cpStream.Release();
cpVoice.Release();
::CoUninitialize();
When I am using a shared recognizer to a previously registered medical dictation topic , dictation grammar is loaded without error but when I change my recognizer to Inproc recognizer it is not able to load a dictation topic. Is there a way to load dictation topic while still using in proc recognizer?
My code for Loading grammar looks like
CComPtr<ISpObjectToken> cpObjectToken;
CComPtr<ISpAudio> cpAudio;
CComPtr<ISpRecognizer> cpEngine;
CComPtr<ISpRecoContext> cpRecoCtx;
CComPtr<ISpRecoGrammar> cpGram;
hr = cpEngine.CoCreateInstance(CLSID_SpInprocRecognizer);
hr = SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOIN, &cpObjectToken);
hr = cpEngine->SetInput(cpObjectToken, TRUE);
hr = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &cpAudio);
hr = cpEngine->SetInput(cpAudio, TRUE);
hr = cpEngine->CreateRecoContext(&cpRecoCtx);
hr = cpEngine->SetRecognizer(NULL);
hr = cpRecoCtx->CreateGrammar(1, &cpGram);
hr = cpGram->LoadDictation(L"Medical", SPLO_STATIC);
Inproc recognizers don't have a default SR engine, so calling
hr = cpEngine->SetRecognizer(NULL);
won't actually load an engine. I wrote a simple function to load the default recognizer; the code's longish, so I won't put it inline, but you can find it here.
Also, in your other question, you had the topic named "Medical", and here you have it named "Medicine"; they do need to be the same.
The vendor Id and product Id is being passed to FindAllAsync and there is no device returned from FindAllAsync. We've verified that these are the right device IDs and works on other platforms. It's not a plug and play device.
Here is the code below:
UInt32 VendorId = 0x1D1B;
UInt32 ProductId = 0x1202;
string aqs = UsbDevice.GetDeviceSelector(VendorId, ProductId);
var myDevices = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(aqs);
if (myDevices.Count == 0)
{
return;
}
There are no devices found. Any ideas?
Clarification
I should clarify. It's not behaving like it's PNP and not appearing in the device manager in win8 and win7. The device works with a native application for driving the device even though it does not appear in the device manager.
if (myDevices.Count == 0) then you AQS filter doesn't match any device interface in the system.
This means that there are not USB interfaces with VendorId = 0x1D1B & ProductId = 0x1202.
You should probably walk thorough the system device interfaces, and see what they actually are. I can show you how to do that if you need.
I have a 7.1 channel audio output device and a custom kext to drive that. My custom application needs to send 7.1 rear channel audio data to the device but the device receives only 2 channel audio data. I checked "Configure Speaker" option in "Audio MIDI setup" application and it's set to stereo. When I set it to "7.1 Rear Surround" everything works fine. In my final product, I don't want the user to have to do all of this manually. So, the question is - Is there any Core Audio API or any other means of doing this programatically?
Ok, after playing around with some Core Audio APIs, finally I could get this done.
Get the AudioDeviceID:
AudioDeviceID audioDevice = getMyAwesomeDeviceID();
Create an AudioObjectPropertyAddress:
AudioObjectPropertyAddress propertyAddress;
propertyAddress.mSelector = kAudioDevicePropertyPreferredChannelLayout;
propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
Query if the audio object has this property:
AudioObjectHasProperty(audioDevice, &propertyAddress)
Get the data size of this property and create AudioChannelLayout:
UInt32 propSize(0);
AudioObjectGetPropertyDataSize(audioDevice, &propertyAddress, 0, NULL, &propSize);
AudioChannelLayout* layout = (AudioChannelLayout*)malloc(propSize);
Configure your AudioChannelLayout structure (eg: stereo layout):
AudioChannelLabel labels[2] = {kAudioChannelLabel_Right, kAudioChannelLabel_Left};
layout->mNumberChannelDescriptions = 2;
for (UInt32 i = 2; i < layout->mNumberChannelDescriptions; i++) {
layout->mChannelDescriptions[i].mChannelLabel = labels[i];
layout->mChannelDescriptions[i].mChannelFlags = kAudioChannelFlags_AllOff;
}
Set the AudioObject property data:
AudioObjectSetPropertyData(audioDevice, &propertyAddress, 0, NULL, propSize, layout);
I'm displaying a CPrintDialogEx dialog to choose a printer and modify the settings. I set the hDevNames member so that a default printer will be selected, but I leave hDevMode set to NULL. On successful return I pull some values such as paper size out of the returned DEVMODE structure from hDevMode.
I'm having a problem because hDevMode appears to be initialized with the values from the default printer that I passed in, not the printer that was finally selected. How do I get the parameters from the actual selected printer?
As requested here's the relevant part of the code. I've deleted some of it in the interest of space. TOwnedHandle is a smart pointer I wrote for holding a memory handle and locking it automatically.
CPrintDialogEx dlg(PD_ALLPAGES | PD_NOCURRENTPAGE | PD_NOPAGENUMS | PD_NOSELECTION, this);
ASSERT(dlg.m_pdex.hDevMode == NULL);
ASSERT(dlg.m_pdex.hDevNames == NULL);
dlg.m_pdex.hDevNames = GlobalAlloc(GHND, sizeof(DEVNAMES) + iSizeName);
DEVNAMES * pDevNames = (DEVNAMES *) GlobalLock(dlg.m_pdex.hDevNames);
// ...
GlobalUnlock(dlg.m_pdex.hDevNames);
if ((dlg.DoModal() == S_OK) && (dlg.m_pdex.dwResultAction == PD_RESULT_PRINT))
{
TOwnedHandle<DEVMODE> pDevMode = dlg.m_pdex.hDevMode;
TRACE("Printer config = %dx%d %d\n", (int)pDevMode->dmPaperWidth, (int)pDevMode->dmPaperLength, (int)pDevMode->dmOrientation);
// ...
}
Edit: I've determined that I don't get the problem if I don't set the hDevNames parameter. I wonder if I've discovered a Windows bug? This is in XP, I don't have a more recent version of Windows handy to test with.
I've distilled the code into a test that doesn't use MFC, this is strictly a Windows API problem. This is the whole thing, nothing left out except the definition of pDefaultPrinter - but of course it doesn't do anything useful anymore.
PRINTDLGEX ex = {sizeof(PRINTDLGEX)};
ex.hwndOwner = m_hWnd;
ex.Flags = PD_ALLPAGES | PD_NOCURRENTPAGE | PD_NOPAGENUMS | PD_NOSELECTION;
ex.nStartPage = START_PAGE_GENERAL;
#if 1
int iSizeName = (strlen(pDefaultPrinter) + 1) * sizeof(char);
ex.hDevNames = GlobalAlloc(GHND, sizeof(DEVNAMES) + iSizeName);
DEVNAMES * pDevNames = (DEVNAMES *) GlobalLock(ex.hDevNames);
ASSERT(pDevNames != NULL);
pDevNames->wDeviceOffset = sizeof(DEVNAMES);
strcpy((char *)pDevNames + pDevNames->wDeviceOffset, pDefaultPrinter);
GlobalUnlock(ex.hDevNames);
#endif
HRESULT hr = PrintDlgEx(&ex);
if ((hr == S_OK) && (ex.dwResultAction == PD_RESULT_PRINT))
{
DEVMODE * pdm = (DEVMODE *) GlobalLock(ex.hDevMode);
ASSERT(pdm != NULL);
TRACE("Printer config = %dx%d %d\n", (int)pdm->dmPaperWidth, (int)pdm->dmPaperLength, (int)pdm->dmOrientation);
GlobalUnlock(ex.hDevMode);
DEVNAMES * pdn = (DEVNAMES *) GlobalLock(ex.hDevNames);
ASSERT(pdn != NULL);
TRACE(_T("Printer device = %s\n"), (char *)pdn + pdn->wDeviceOffset);
GlobalUnlock(ex.hDevNames);
}
If I can't get a fix, I'd love to hear of a work-around.
After much head scratching I think I've figured it out.
When the dialog comes up initially, the hDevMode member gets filled with the defaults for the printer that is initially selected. If you select a different printer before closing the dialog, that DEVMODE structure is presented to the new printer driver; if the paper size doesn't make sense to the driver it may change it, and the drivers are not consistent.
The reason this tripped me up is that I was switching between three printers: two label
printers with very different characteristics, and a laser printer with US Letter paper.
The laser printer always responds with the proper dimensions but may indicate a wrong paper size code.
The first label printer will override the size provided by the laser printer but not the other label printer.
The second label printer will accept the size provided by the first label printer, because it's capable of using that size even though it's not loaded and not configured. It modifies the size provided by the laser printer by returning the maximum width and the Letter size length of 11 inches.
I determined two ways to work around the problem. The first is to implement IPrintDialogCallback and respond to SelectionChange calls by reloading the default DEVMODE for the newly selected printer. EDIT: I tried this and it does not work. CPrintDialogEx already implements an IPrintDialogCallback interface, making this easy. It appears that PrintDlgEx has its own internal handle that it uses to track the current DEVMODE structure and only uses the one in the PRINTDLGEX structure for input/output. There's no way to affect the DEVMODE while the dialog is up, and by the time it returns it's too late.
The second solution is to ignore the returned results entirely and work from the default paper configuration for the printer. Any changes made from the printer defaults within the dialog are lost completely, but for my application this is acceptable.
bool MyDialog::GetPaperSize(const TCHAR * pPrinterName, double & dPaperWidth, double & dPaperLength)
{
// you need to open the printer before you can get its properties
HANDLE hPrinter;
if (OpenPrinter((TCHAR *)pPrinterName, &hPrinter, NULL))
{
// determine how much space is needed for the DEVMODE structure by the printer driver
int iDevModeSize = DocumentProperties(m_hWnd, hPrinter, (TCHAR *)pPrinterName, NULL, NULL, 0);
ASSERT(iDevModeSize >= sizeof(DEVMODE);
// allocate a DEVMODE structure and initialize it to a clean state
std::vector<char> buffer(iDevModeSize, 0);
DEVMODE * pdm = (DEVMODE *) &buffer[0];
pdm->dmSpecVersion = DM_SPECVERSION;
DocumentProperties(m_hWnd, hPrinter, (TCHAR *)pPrinterName, pdm, NULL, DM_OUT_BUFFER);
ClosePrinter(hPrinter);
// convert paper size from tenths of a mm to inches
dPaperWidth = pdm->dmPaperWidth / 254.;
dPaperLength = pdm->dmPaperLength / 254.;
return true;
}
return false;
}