Does GDI+ have standard image encoder CLSIDs? - c++

The GDI+ Image::Save method requires a CLSID parameter to specify the encoder to use. The documentation points to some sample code for getting the encoder associated with a particular MIME type, such as image/jpeg or image/png. However I'm balking at the thought of copying a half-page function just to support a 1-line debugging aid where I save an intermediate result out to disk.
Shouldn't there be a list of standard CLSIDs for the standard encoders? Where would I find such a list? I haven't been able to find one by searching Microsoft's include files.

There isn't one. I think they intended the codec list to be extensible and support plugins, but never got around to it. Given that they haven't made any changes to GDI+ in quite some time, they likely won't anytime soon. You could probably get away with generating your own hard coded list based on an enumeration of Gdiplus::GetImageEncoders.
That is:
image/bmp : {557cf400-1a04-11d3-9a73-0000f81ef32e}
image/jpeg : {557cf401-1a04-11d3-9a73-0000f81ef32e}
image/gif : {557cf402-1a04-11d3-9a73-0000f81ef32e}
image/tiff : {557cf405-1a04-11d3-9a73-0000f81ef32e}
image/png : {557cf406-1a04-11d3-9a73-0000f81ef32e}
Here's the function I routinely cut&paste between projects for getting at the CLSID of the encoder. You could modify it to be a table lookup.
#include <windows.h>
#include <gdiplus.h>
#include <string>
#include <vector>
HRESULT GetGdiplusEncoderClsid(const std::wstring& format, GUID* pGuid)
{
HRESULT hr = S_OK;
UINT nEncoders = 0; // number of image encoders
UINT nSize = 0; // size of the image encoder array in bytes
std::vector<BYTE> spData;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::Status status;
bool found = false;
if (format.empty() || !pGuid)
{
hr = E_INVALIDARG;
}
if (SUCCEEDED(hr))
{
*pGuid = GUID_NULL;
status = Gdiplus::GetImageEncodersSize(&nEncoders, &nSize);
if ((status != Gdiplus::Ok) || (nSize == 0))
{
hr = E_FAIL;
}
}
if (SUCCEEDED(hr))
{
spData.resize(nSize);
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)&spData.front();
status = Gdiplus::GetImageEncoders(nEncoders, nSize, pImageCodecInfo);
if (status != Gdiplus::Ok)
{
hr = E_FAIL;
}
}
if (SUCCEEDED(hr))
{
for (UINT j = 0; j < nEncoders && !found; j++)
{
if (pImageCodecInfo[j].MimeType == format)
{
*pGuid = pImageCodecInfo[j].Clsid;
found = true;
}
}
hr = found ? S_OK : E_FAIL;
}
return hr;
}
int main()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
GUID guidBmp = {};
GUID guidJpeg = {};
GUID guidGif = {};
GUID guidTiff = {};
GUID guidPng = {};
GetGdiplusEncoderClsid(L"image/bmp", &guidBmp);
GetGdiplusEncoderClsid(L"image/jpeg", &guidJpeg);
GetGdiplusEncoderClsid(L"image/gif", &guidGif);
GetGdiplusEncoderClsid(L"image/tiff", &guidTiff);
GetGdiplusEncoderClsid(L"image/png", &guidPng);
return 0;
}

If you just want to write a PNG, this appears to work:
// image/png : {557cf406-1a04-11d3-9a73-0000f81ef32e}
const CLSID pngEncoderClsId = { 0x557cf406, 0x1a04, 0x11d3,{ 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } };
stat = image->Save(L"test.png", &pngEncoderClsId, NULL);
Note the reformatting of the hex values.
From:
How to initialize a constant CLSID

You will probably want to use ImageCodecInfo with GetImageEncodersSize() and GetImageEncoders() I'm not aware of any easier way.
EDIT: If you know specifically what you want and damn all the rest you can get away with doing something like this ...
CLSID pngClsid;
GetEncoderClsid("image/png", &pngClsid);
image.Save("imagename.png", &pngClsid, NULL);

Related

GDI+ Image::SetPropertyItem not working as expected

I am trying to use SetPropertyItem to set a Date Taken property to a file (click here for MSDN docs description).
I have tried assigning a newly initialized FILETIME to an input image with no success (or error messages). To ensure that it was not an issue with Date Taken, I also tried following this MSDN example to no avail.
Currently, I am attempting to extract a Date Taken property item from one input file (works fine) and attempting to set it to a different file. This approach does not work either, and the Status code returned is always 0 (Ok).
The code I am using is below. I can only assume I am making a simple mistake or perhaps misunderstanding what SetPropertyItem is supposed to do. I thought that SetPropertyItem changed the metadata value such that it can be viewed through the Windows properties menu, like in this screenshot.
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
#include <iostream>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Image* image = new Image(L"FakePhoto.jpg"); // input image
UINT totalBufferSize;
UINT numProperties; // setup the buffer
image->GetPropertySize(&totalBufferSize, &numProperties);
// extract all metadata property items
PropertyItem* pAllItems = (PropertyItem*)malloc(totalBufferSize);
image->GetAllPropertyItems(totalBufferSize, numProperties, pAllItems);
for (UINT j = 0; j < numProperties; ++j)
{ // loop through each property
if (pAllItems[j].id == PropertyTagExifDTOrig)
{ // if it's the Date Taken property
PropertyItem* propItem = new PropertyItem;
Image* newImage = new Image(L"Test2.jpg");
Status status; // second image
propItem->id = PropertyTagExifDTOrig;
propItem->length = pAllItems[j].length;
propItem->type = PropertyTagTypeASCII;
propItem->value = pAllItems[j].value;
// create a new property item with the input photo Date Taken metadata
status = newImage->SetPropertyItem(propItem);
if (status == Ok)
std::cout << "No errors.";
}
}
free(pAllItems);
delete image;
GdiplusShutdown(gdiplusToken);
}
Any help is greatly appreciated. Also, I apologise about any obvious/potential errors. I am still learning the ropes as this is my first time using C++.
You code works fine, but you must save the image back, for example like this
...
newImage->SetPropertyItem(propItem);
CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
newImage->Save(L"Test2.jpg", &clsid);
...
BOOL GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
ImageCodecInfo* info = NULL;
ZeroMemory(pClsid, sizeof(CLSID));
GetImageEncodersSize(&num, &size);
if (size == 0)
return FALSE;
info = (ImageCodecInfo*)(malloc(size));
if (info == NULL)
return FALSE;
GetImageEncoders(num, size, info);
for (UINT j = 0; j < num; ++j)
{
if (!wcscmp(info[j].MimeType, format))
{
*pClsid = info[j].Clsid;
free(info);
return TRUE;
}
}
free(info);
return FALSE;
}

How to use IAudioClient3 (WASAPI) with Real-Time Work Queue API

I'm working on a lowest-possible latency MIDI synthetizer software. I'm aware of ASIO and other alternatives, but as they have apparently made significant improvements to the WASAPI stack (in shared mode, at least), I'm curious to try it out. I first wrote a simple event-driven version of program, but as that's not the recommended way to do low-latency audio on Windows 10 (according to the docs), I'm trying to migrate to the Real-Time Work Queue API.
The documentation on Low Latency Audio states that it is recommended to use the Real-Time Work Queue API or MFCreateMFByteStreamOnStreamEx with WASAPI in order for the OS to manage work items in a way that will avoid interference from non-audio subsystems. This seems like a good idea, but the latter option seems to require some managed code (demonstrated in this WindowsAudioSession example), which I know nothing about and would preferably avoid (also the header Robytestream.h which has defs for the IRandomAccessStream isn't found on my system either).
The RTWQ example included in the docs is incomplete (doesn't compile as such), and I have made the necessary additions to make it compilable:
class my_rtqueue : IRtwqAsyncCallback {
public:
IRtwqAsyncResult* pAsyncResult;
RTWQWORKITEM_KEY workItemKey;
DWORD WorkQueueId;
STDMETHODIMP GetParameters(DWORD* pdwFlags, DWORD* pdwQueue)
{
HRESULT hr = S_OK;
*pdwFlags = 0;
*pdwQueue = WorkQueueId;
return hr;
}
//-------------------------------------------------------
STDMETHODIMP Invoke(IRtwqAsyncResult* pAsyncResult)
{
HRESULT hr = S_OK;
IUnknown* pState = NULL;
WCHAR className[20];
DWORD bufferLength = 20;
DWORD taskID = 0;
LONG priority = 0;
BYTE* pData;
hr = render_info.renderclient->GetBuffer(render_info.buffer_framecount, &pData);
ERROR_EXIT(hr);
update_buffer((unsigned short*)pData, render_info.framesize_bytes / (2*sizeof(unsigned short))); // 2 channels, sizeof(unsigned short) == 2
hr = render_info.renderclient->ReleaseBuffer(render_info.buffer_framecount, 0);
ERROR_EXIT(hr);
return S_OK;
}
STDMETHODIMP QueryInterface(const IID &riid, void **ppvObject) {
return 0;
}
ULONG AddRef() {
return 0;
}
ULONG Release() {
return 0;
}
HRESULT queue(HANDLE event) {
HRESULT hr;
hr = RtwqPutWaitingWorkItem(event, 1, this->pAsyncResult, &this->workItemKey);
return hr;
}
my_rtqueue() : workItemKey(0) {
HRESULT hr = S_OK;
IRtwqAsyncCallback* callback = NULL;
DWORD taskId = 0;
WorkQueueId = RTWQ_MULTITHREADED_WORKQUEUE;
//WorkQueueId = RTWQ_STANDARD_WORKQUEUE;
hr = RtwqLockSharedWorkQueue(L"Pro Audio", 0, &taskId, &WorkQueueId);
ERROR_THROW(hr);
hr = RtwqCreateAsyncResult(NULL, reinterpret_cast<IRtwqAsyncCallback*>(this), NULL, &pAsyncResult);
ERROR_THROW(hr);
}
int stop() {
HRESULT hr;
if (pAsyncResult)
pAsyncResult->Release();
if (0xFFFFFFFF != this->WorkQueueId) {
hr = RtwqUnlockWorkQueue(this->WorkQueueId);
if (FAILED(hr)) {
printf("Failed with RtwqUnlockWorkQueue 0x%x\n", hr);
return 0;
}
}
return 1;
}
};
And so, the actual WASAPI code (HRESULT error checking is omitted for clarity):
void thread_main(LPVOID param) {
HRESULT hr;
REFERENCE_TIME hnsRequestedDuration = 0;
IMMDeviceEnumerator* pEnumerator = NULL;
IMMDevice* pDevice = NULL;
IAudioClient3* pAudioClient = NULL;
IAudioRenderClient* pRenderClient = NULL;
WAVEFORMATEX* pwfx = NULL;
HANDLE hEvent = NULL;
HANDLE hTask = NULL;
UINT32 bufferFrameCount;
BYTE* pData;
DWORD flags = 0;
hr = RtwqStartup();
// also, hr is checked for errors every step of the way
hr = CoInitialize(NULL);
hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&pEnumerator);
hr = pEnumerator->GetDefaultAudioEndpoint(
eRender, eConsole, &pDevice);
hr = pDevice->Activate(
IID_IAudioClient, CLSCTX_ALL,
NULL, (void**)&pAudioClient);
WAVEFORMATEX wave_format = {};
wave_format.wFormatTag = WAVE_FORMAT_PCM;
wave_format.nChannels = 2;
wave_format.nSamplesPerSec = 48000;
wave_format.nAvgBytesPerSec = 48000 * 2 * 16 / 8;
wave_format.nBlockAlign = 2 * 16 / 8;
wave_format.wBitsPerSample = 16;
UINT32 DP, FP, MINP, MAXP;
hr = pAudioClient->GetSharedModeEnginePeriod(&wave_format, &DP, &FP, &MINP, &MAXP);
printf("DefaultPeriod: %u, Fundamental period: %u, min_period: %u, max_period: %u\n", DP, FP, MINP, MAXP);
hr = pAudioClient->InitializeSharedAudioStream(AUDCLNT_STREAMFLAGS_EVENTCALLBACK, MINP, &wave_format, 0);
my_rtqueue* workqueue = NULL;
try {
workqueue = new my_rtqueue();
}
catch (...) {
hr = E_ABORT;
ERROR_EXIT(hr);
}
hr = pAudioClient->GetBufferSize(&bufferFrameCount);
PWAVEFORMATEX wf = &wave_format;
UINT32 current_period;
pAudioClient->GetCurrentSharedModeEnginePeriod(&wf, &current_period);
INT32 FrameSize_bytes = bufferFrameCount * wave_format.nChannels * wave_format.wBitsPerSample / 8;
printf("bufferFrameCount: %u, FrameSize_bytes: %d, current_period: %u\n", bufferFrameCount, FrameSize_bytes, current_period);
hr = pAudioClient->GetService(
IID_IAudioRenderClient,
(void**)&pRenderClient);
render_info.framesize_bytes = FrameSize_bytes;
render_info.buffer_framecount = bufferFrameCount;
render_info.renderclient = pRenderClient;
hEvent = CreateEvent(nullptr, false, false, nullptr);
if (hEvent == INVALID_HANDLE_VALUE) { ERROR_EXIT(0); }
hr = pAudioClient->SetEventHandle(hEvent);
const size_t num_samples = FrameSize_bytes / sizeof(unsigned short);
DWORD taskIndex = 0;
hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
if (hTask == NULL) {
hr = E_FAIL;
}
hr = pAudioClient->Start(); // Start playing.
running = 1;
while (running) {
workqueue->queue(hEvent);
}
workqueue->stop();
hr = RtwqShutdown();
delete workqueue;
running = 0;
return 1;
}
This seems to kind of work (ie. audio is being output), but on every other invocation of my_rtqueue::Invoke(), IAudioRenderClient::GetBuffer() returns a HRESULT of 0x88890006 (-> AUDCLNT_E_BUFFER_TOO_LARGE), and the actual audio output is certainly not what I intend it to be.
What issues are there with my code? Is this the right way to use RTWQ with WASAPI?
Turns out there were a number of issues with my code, none of which had really anything to do with Rtwq. The biggest issue was me assuming that the shared mode audio stream was using 16-bit integer samples, when in reality my audio was setup for 32-bit float format (WAVE_FORMAT_IEEE_FLOAT). The currently active shared mode format, period etc. should be fetched like this:
WAVEFORMATEX *wavefmt = NULL;
UINT32 current_period = 0;
hr = pAudioClient->GetCurrentSharedModeEnginePeriod((WAVEFORMATEX**)&wavefmt, &current_period);
wavefmt now contains the output format info of the current shared mode. If the wFormatTag field is equal to WAVE_FORMAT_EXTENSIBLE, one needs to cast WAVEFORMATEX to WAVEFORMATEXTENSIBLE to see what the actual format is. After this, one needs to fetch the minimum period supported by the current setup, like so:
UINT32 DP, FP, MINP, MAXP;
hr = pAudioClient->GetSharedModeEnginePeriod(wavefmt, &DP, &FP, &MINP, &MAXP);
and then initialize the audio stream with the new InitializeSharedAudioStream function:
hr = pAudioClient->InitializeSharedAudioStream(AUDCLNT_STREAMFLAGS_EVENTCALLBACK, MINP, wavefmt, NULL);
... get the buffer's actual size:
hr = pAudioClient->GetBufferSize(&render_info.buffer_framecount);
and use GetCurrentPadding in the Get/ReleaseBuffer logic:
UINT32 pad = 0;
hr = render_info.audioclient->GetCurrentPadding(&pad);
int actual_size = (render_info.buffer_framecount - pad);
hr = render_info.renderclient->GetBuffer(actual_size, &pData);
if (SUCCEEDED(hr)) {
update_buffer((float*)pData, actual_size);
hr = render_info.renderclient->ReleaseBuffer(actual_size, 0);
ERROR_EXIT(hr);
}
The documentation for IAudioClient::Initialize states the following about shared mode streams (I assume it also applies to the new IAudioClient3):
Each time the thread awakens, it should call IAudioClient::GetCurrentPadding to determine how much data to write to a rendering buffer or read from a capture buffer. In contrast to the two buffers that the Initialize method allocates for an exclusive-mode stream that uses event-driven buffering, a shared-mode stream requires a single buffer.
Using GetCurrentPadding solves the problem with AUDCLNT_E_BUFFER_TOO_LARGE, and feeding the buffer with 32-bit float samples instead of 16-bit integers makes the output sound fine on my system (although the effect was quite funky!).
If someone comes up with better/more correct ways to use the Rtwq API, I would love to hear them.

Render an IDirect3DSurface9 from DXVA2?

I got a IDirect3DSurface9 from DXVA2 video decoder using hardware acceleration.
I'm try to Render this hardware IDirect3DSurface9 on My Window via its handle. The following is my summary code.
The first, I call dxva2_init(AVCodecContext *s, HWND hwnd); with hwnd is window's handle
int dxva2_init(AVCodecContext *s, HWND hwnd)
{
InputStream *ist = (InputStream *)s->opaque;
int loglevel = (ist->hwaccel_id == HWACCEL_AUTO) ? AV_LOG_VERBOSE : AV_LOG_ERROR;
DXVA2Context *ctx;
int ret;
if (!ist->hwaccel_ctx) {
ret = dxva2_alloc(s);
if (ret < 0)
return ret;
}
ctx = (DXVA2Context *)ist->hwaccel_ctx;
ctx->deviceHandle = hwnd;
if (s->codec_id == AV_CODEC_ID_H264 &&
(s->profile & ~FF_PROFILE_H264_CONSTRAINED) > FF_PROFILE_H264_HIGH) {
av_log(NULL, loglevel, "Unsupported H.264 profile for DXVA2 HWAccel: %d\n", s->profile);
return AVERROR(EINVAL);
}
if (ctx->decoder)
dxva2_destroy_decoder(s);
ret = dxva2_create_decoder(s);
if (ret < 0) {
av_log(NULL, loglevel, "Error creating the DXVA2 decoder\n");
return ret;
}
return 0;
}
After Decoding successful, I got got a IDirect3DSurface9, and I Render it by following function.
int dxva2_render(AVCodecContext *s, AVFrame *frame)
{
LPDIRECT3DSURFACE9 surface = (LPDIRECT3DSURFACE9)frame->data[3];
InputStream *ist = (InputStream *)s->opaque;
DXVA2Context *ctx = (DXVA2Context *)ist->hwaccel_ctx;
try
{
lockRenderCS.Enter();
HRESULT hr = ctx->d3d9device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);
if (hr != D3D_OK)
return 0;
hr = ctx->d3d9device->BeginScene();
if (hr != D3D_OK)
return 0;
hr = ctx->d3d9device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);
if (hr != D3D_OK)
return 0;
hr = ctx->d3d9device->StretchRect(surface, NULL, pBackBuffer, NULL, D3DTEXF_LINEAR);
if (hr != D3D_OK)
return 0;
hr = ctx->d3d9device->EndScene();
if (hr != D3D_OK)
return 0;
hr = ctx->d3d9device->Present(NULL, NULL, NULL, NULL);
if (hr != D3D_OK)
return 0;
}
finally
{
lockRenderCS.Leave();
}
return 0;
}
Note: All D3D function above: Clear(), BeginScene(), GetBackBuffer(), StretchRect(), EndScene(), Present() were return Successful. But the frame was not display on My Window.
I Guess that, I miss some code for integrated My Window Handle with DXVA2Context. In this code I only assign: ctx->deviceHandle = hwnd; in function dxva2_init().
I search many times, but so far I still cannot find the solution, Anyone can help me?
Many Thanks!
I could help with full code.
Without full code, i suggest you to check your use of StretchRect, just like that :
IDirect3DDevice9::StretchRect
StretchRect Restrictions
Driver support varies. See the section on driver support (below) to see which drivers support which source and destination formats.
The source and destination surfaces must be created in the default memory pool.
If filtering is specified, you must set the appropriate filter caps (see StretchRectFilterCaps in D3DCAPS9).
etc...
The idea behind this, we don't know nothing about your d3d9device/surface9 (creation/initialization/parameter/etc...), for example.
Also, you can study this code from my project to do dxva2 decoding :
H264Dxva2Decoder

Console app vs Win32 app - DirectSound capture-device enumeration gives different results

I am looking for a reliable method to map a DirectShow capture device GUID to its corresponding waveID value.
I found the following project by Chris_P:
The solution works great, and it relies on an a rather obscure IKsPropertySet interface to retrieve the mapping.
Unfortunately, if I attempt the same technique from a C++/CLI library, the code fails with E_NOTIMPL (this behavior has been described on this question, - see the answer by Vladimir Hmelyoff)
So, I figured that I could write a simple console-based auxiliary app to retrieve the mappings and print them. My library could then launch this auxiliary app and parse the redirected output to obtain the mappings.
The console program runs fine, however, the GUIDs that are being passed to the enumeration callback are completely different to the ones passed by Chris_P's solution.
In fact they all share the same basic structure:
lpGuid = 0x02ad0808 {BDF35A00-B9AC-11D0-A619-00AA00A7C000}
The only variation occurs in the last digits of the GUID, where coincidentally, they match the mapped waveId value.
Another weird thing is that the capture device description is truncated to 31 characters, as if the enumeration was being performed using WaveIn APIs!
It would almost seem that some DirectSound facade is now wrapping the WaveIn API.
Any pointers on what could be happening?, Can I disable this behavior and enumerate the same GUIDS that the WIN32 app is enumerating?
Here is the code for the console application:
#include "stdafx.h"
#include <mmreg.h>
#include <initguid.h>
#include <Dsound.h>
#include <dsconf.h>
static BOOL CALLBACK DSEnumCallback(
LPGUID lpGuid,
LPCTSTR lpcstrDescription,
LPCTSTR lpcstrModule,
LPVOID lpContext
);
static BOOL GetInfoFromDSoundGUID(GUID i_sGUID, DWORD &dwWaveID);
static HRESULT DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * ppKsPropertySet);
typedef WINUSERAPI HRESULT(WINAPI *LPFNDLLGETCLASSOBJECT) (const CLSID &, const IID &, void **);
BOOL GetInfoFromDSoundGUID(GUID i_sGUID, DWORD &dwWaveID) {
LPKSPROPERTYSET pKsPropertySet = NULL;
HRESULT hr;
BOOL retval = FALSE;
PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA psDirectSoundDeviceDescription = NULL;
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA sDirectSoundDeviceDescription;
memset(&sDirectSoundDeviceDescription, 0, sizeof(sDirectSoundDeviceDescription));
hr = DirectSoundPrivateCreate(&pKsPropertySet);
if( SUCCEEDED(hr) ) {
ULONG ulBytesReturned = 0;
sDirectSoundDeviceDescription.DeviceId = i_sGUID;
// On the first call the final size is unknown so pass the size of the struct in order to receive
// "Type" and "DataFlow" values, ulBytesReturned will be populated with bytes required for struct+strings.
hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice,
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION,
NULL,
0,
&sDirectSoundDeviceDescription,
sizeof(sDirectSoundDeviceDescription),
&ulBytesReturned
);
if( ulBytesReturned ) {
// On the first call it notifies us of the required amount of memory in order to receive the strings.
// Allocate the required memory, the strings will be pointed to the memory space directly after the struct.
psDirectSoundDeviceDescription = (PDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA)new BYTE[ulBytesReturned];
*psDirectSoundDeviceDescription = sDirectSoundDeviceDescription;
hr = pKsPropertySet->Get(DSPROPSETID_DirectSoundDevice,
DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION,
NULL,
0,
psDirectSoundDeviceDescription,
ulBytesReturned,
&ulBytesReturned
);
dwWaveID = psDirectSoundDeviceDescription->WaveDeviceId;
delete[] psDirectSoundDeviceDescription;
retval = TRUE;
}
pKsPropertySet->Release();
}
return retval;
}
HRESULT DirectSoundPrivateCreate(OUT LPKSPROPERTYSET * ppKsPropertySet) {
HMODULE hLibDsound = NULL;
LPFNDLLGETCLASSOBJECT pfnDllGetClassObject = NULL;
LPCLASSFACTORY pClassFactory = NULL;
LPKSPROPERTYSET pKsPropertySet = NULL;
HRESULT hr = DS_OK;
// Load dsound.dll
hLibDsound = LoadLibrary(TEXT("dsound.dll"));
if( !hLibDsound ) {
hr = DSERR_GENERIC;
}
// Find DllGetClassObject
if( SUCCEEDED(hr) ) {
pfnDllGetClassObject =
(LPFNDLLGETCLASSOBJECT)GetProcAddress(hLibDsound, "DllGetClassObject");
if( !pfnDllGetClassObject ) {
hr = DSERR_GENERIC;
}
}
// Create a class factory object
if( SUCCEEDED(hr) ) {
hr = pfnDllGetClassObject(CLSID_DirectSoundPrivate, IID_IClassFactory, (LPVOID *)&pClassFactory);
}
// Create the DirectSoundPrivate object and query for an IKsPropertySet
// interface
if( SUCCEEDED(hr) ) {
hr = pClassFactory->CreateInstance(NULL, IID_IKsPropertySet, (LPVOID *)&pKsPropertySet);
}
// Release the class factory
if( pClassFactory ) {
pClassFactory->Release();
}
// Handle final success or failure
if( SUCCEEDED(hr) ) {
*ppKsPropertySet = pKsPropertySet;
} else if( pKsPropertySet ) {
pKsPropertySet->Release();
}
FreeLibrary(hLibDsound);
return hr;
}
BOOL CALLBACK DSEnumCallback(
LPGUID lpGuid,
LPCTSTR lpcstrDescription,
LPCTSTR lpcstrModule,
LPVOID lpContext
) {
LPWSTR psz = NULL;
StringFromCLSID(*lpGuid, &psz);
DWORD WaveID = 0xFFFFFFFF;
if( lpGuid ) {
GUID i_guid = *lpGuid;
GetInfoFromDSoundGUID(i_guid, WaveID);
}
if( WaveID != 0xFFFFFFFF )
wprintf(_T("%d %s\r\n"), WaveID, psz);
if( psz ) {
CoTaskMemFree(psz);
}
return TRUE;
}
int main()
{
DirectSoundCaptureEnumerate(DSEnumCallback, NULL);
Sleep(10000);
return 0;
}
It turns out I was not initializing COM.
I added the following snippet at the beginning of my main() procedure and the program retrieved the expected GUIDs:
HRESULT hr = NULL;
hr = CoInitialize(NULL);
if( FAILED(hr) ) {
printf("Failed to initialize COM");
return -1;
}
So I guess that if COM is not initialized, the DirectSound engine falls back to the WaveIn API (creating a DirectShow facade around it).

scanning pages using WIA or TWAIN

Edit: Are there any tutorials on how to use WIA or TWAIN in c++, that explain how to scan pages, adjust settings (DPI, using automatic feeder etc.) and save them as PNG files?
I'd like to use WIA to scan pages and store them as png files. If the scanner supports automatic feeding I'd also like to use that feature. Currently I am following the steps of this tutorial and am stuck at the section Transferring Image Data in WIA 2.0.
So far my scanner has been found and I am able to create the device, and an IWiaItem2* has been created. How can use it to scan at 300dpi and store the result as png file?
The tutorial is not clear about how to start the scan process or how to set dpi for scanning, so I hope someone can help me with the code.
This is essentially the code for getting all local devices:
bool init(IWiaDevMgr2* devMgr)
{
//creating the device manager
*devMgr = 0;
CoCreateInstance( CLSID_WiaDevMgr2, 0, CLSCTX_LOCAL_SERVER, IID_IWiaDevMgr2, (void**)&devMgr);
//enumerating wia devices
IEnumWIA_DEV_INFO* enumDevInfo = 0;
HRESULT hr = devMgr->EnumDeviceInfo( WIA_DEVINFO_ENUM_LOCAL, &enumDevInfo);
if(SUCCEEDED(hr))
{
//loop until an error occurs or end of list
while(hr == S_OK)
{
IWiaPropertyStorage* storage = 0;
hr = enumDevInfo->Next( 1, &storage, 0);
if(hr == S_OK)
{
readProperties(storage);
storage->Release();
storage = 0;
}
}
//set hr to ok, so no error code is returned
if(hr == S_FALSE) hr = S_OK;
enumDevInfo->Release();
enumDevInfo = 0;
}
return SUCCEEDED(hr);
}
void readProperties(IWiaPropertyStorage* storage)
{
PROPSPEC propSpec[2] = {0};
PROPVARIANT propVar[2] = {0};
const ULONG propCount = sizeof(propSpec) / sizeof(propSpec[0]);
propSpec[0].ulKind = PRSPEC_PROPID;
propSpec[0].propid = WIA_DIP_DEV_ID;
propSpec[1].ulKind = PRSPEC_PROPID;
propSpec[1].propid = WIA_DIP_DEV_NAME;
HRESULT hr = storage->ReadMultiple(propCount, propSpec, propVar);
if(SUCCEEDED(hr))
{
Device* dev = new Device(propVar[0].bstrVal, propVar[1].bstrVal);
devices.push_back( dev );
FreePropVariantArray( propCount, propVar );
}
}
Afterwards a device is initialized like this:
bool createDevice(BSTR id, IWiaItem2** item)
{
*item = 0;
HRESULT hr = devMgr->CreateDevice( 0, deviceId, item);
return SUCCEEDED(hr);
}
Then the items are enumerated:
bool enumerateItems(IWiaItem2* item)
{
LONG itemType = 0;
HRESULT hr = item->GetItemType(&itemType);
if(SUCCEEDED(hr))
{
if(itemType & WiaItemTypeFolder || itemType & WiaItemTypeHasAttachments)
{
IEnumWiaItem2* enumItem = 0;
hr = item->EnumChildItems(0, &enumItem );
while(hr == S_OK)
{
IWiaItem2* child = 0;
hr = enumItem->Next( 1, &child, 0 );
if(hr == S_OK)
{
hr = enumerateItems( child );
child->Release();
child = 0;
}
}
if(hr == S_FALSE) hr = S_OK;
enumItem->Release();
enumItem = 0;
}
}
return SUCCEEDED(hr);
}
Now that everything has been initialized I'd like to implement a scan function. However, the code provided at the tutorial is for transferring files and folders and not for scanning images.
void scanAndSaveAsPNG(IWiaItem2* item, unsigned int dpi, std::string targetPath)
{
}
EDIT:
I installed the latest version available of the scanner driver (WIA and TWAIN) and after checking the supported commands using this code
void printCommands(IWiaItem2* i)
{
IEnumWIA_DEV_CAPS* caps = 0;
HRESULT h = item->EnumDeviceCapabilities(WIA_DEVICE_COMMANDS, &caps);
if(SUCCEEDED(h))
{
ULONG count = 0;
caps->GetCount(&count);
if(count > 0)
{
WIA_DEV_CAP* cap = new WIA_DEV_CAP[ count ];
ULONG fetched;
caps->Next(count, cap, &fetched);
for(int i = 0; i < fetched; i++)
{
std::cout << bstr_t( cap[i].bstrName ) << "\n";
}
}
caps->Release();
}
}
I noticed it only lists WIA Synchronize command. I am not sure if I didn't initialize the device correctly, or if the device doesn't support all WIA commands although the driver is installed.
So unless this problem is solved I am alternatively also looking for the same code based on TWAIN.
You want to use IWiaItem2::DeviceCommand which sends a command to the image capture device. The list of commands you can send are listed here.