$theTitle=wp_title(" - ", false); if($theTitle != "") { ?>
About Virtualization, VDI, SBC, Application Compatibility and anything else I feel like
Yesterday I was asked to investigate a problem with a presentation pc. Even though the volume was set maximal there was not audio output.
The machine was used to connect to a Citrix XenApp desktop and RES Workspace Extender was used to integrate local applications in the XenApp desktop.
The local sound volume control was published as a subscribed application so I launched that and verified that the volume was set to Maximum:
I decided to launch the local explorer shell and noticed that there were two volume control icons in the Traybar:
I clicked the second icon and a control named “Philips Audio Control” popped up:
I raised the volume in the Philips Audio Control and immediately the audio worked as expected.
I was curious where this “Philips Audio Control” came from and found out that it belongs to the Philips G2 Speech software.
Apparently the G2 Speech driver was put in the pc image to accommodate medical staff that use dictation and speech recognition software (so they can use it on any pc).
When the G2 Speech hardware is plugged in the volume controls are synched so if you adjust one of the two volumes the other follows.
However when the G2 Speech hardware is not present, the Philips Audio Control volume is set to mute by default and setting the master volume to maximum still gives no audio.
This was a potential issue on all pc’s so I needed to find a way to set the volume programmatically.
I investigated the Philips Audio Control binary (PspContr.exe) with my favourite tool, Ida Pro. PspContr.exe loads an audio driver:
1 | OpenDriver("pspaudrv.dll", 0, 0) |
I loaded pspaudrv.dll in Ida Pro calls and it calls into a function named PSPSBEXTwodMessage from pspsbext.dll with different integers (messages?). I thought that this might be of interest because wodMessage could mean Waveform Output Driver Message.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | signed int __stdcall PSPSBEXTwodMessage(int a1, DWORD lpMem, DWORD Data, int cbwh) { signed int result; // eax@3 int v5; // ebx@5 HWAVEOUT v6; // ecx@21 if ( byte_20598544 ) { if ( sub_205030E0(a1, lpMem, Data, cbwh, 2, (int)&cbwh) ) { result = cbwh; } else { fnLOGNumErr("PSPSBEXT.DLL", ".\\Waveproc.c", 363, 0, "Error in redirecting command for citrix"); result = 8; } } else { v5 = cbwh; if ( byte_20598545 && sub_20503260(a1, lpMem, Data, cbwh, 2, (int)&cbwh) ) { result = cbwh; } else if ( (unsigned int)a1 > 0x4000 ) { switch ( a1 ) { case 16385: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 436, "wodMessage() - WODM_SETSPEED"); result = sub_205066D0(Data); break; case 16386: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 440, "wodMessage() - WODM_GETSPEED"); result = sub_20506850(Data); break; case 16387: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 444, "wodMessage() - WODM_SETTONE"); result = sub_20506880(Data); break; case 16388: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 448, "wodMessage() - WODM_GETTONE"); result = sub_20506A00(Data); break; case 16389: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 452, "wodMessage() - WODM_GETSIGNAL"); if ( !*(_DWORD *)lpMem ) goto LABEL_33; result = sub_20506F30(2, lpMem, Data); break; case 16392: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 464, "wodMessage() - WODM_GETPEAK"); if ( !*(_DWORD *)lpMem ) goto LABEL_33; result = sub_20506F90(2, lpMem, Data); break; case 16393: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 476, "wodMessage() - WODM_SETCHANNELSELECTION"); result = sub_205070D0(Data); break; default: LABEL_33: result = 8; break; } } else if ( a1 == 16384 ) { fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 432, "wodMessage() - WODM_GETPSPCAPS"); result = sub_20506670(2, (void *)Data, v5); } else { switch ( a1 ) { case 3: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 380, "wodMessage() - WODM_GETNUMDEVS"); result = waveOutGetNumDevs(); break; case 4: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 384, "wodMessage() - WODM_GETDEVCAPS"); result = sub_20501F00((void *)Data, v5) != 0 ? 0 : 7; break; case 5: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 395, "wodMessage() - WODM_OPEN"); result = sub_20507710((HKEY)2, lpMem, Data, v5); break; case 6: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 399, "wodMessage() - WODM_CLOSE"); result = sub_20509550(2, lpMem); break; case 9: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 403, "wodMessage() - WODM_WRITE"); result = sub_20509190(2, lpMem, (LPWAVEHDR)Data, v5); break; case 10: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 407, "wodMessage() - WODM_PAUSE"); result = sub_20509B80(2, lpMem); break; case 11: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 411, "wodMessage() - WODM_RESTART"); result = sub_20509D00(2, lpMem); break; case 12: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 415, "wodMessage() - WODM_RESET"); result = sub_20509DE0(2, lpMem); break; case 13: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 419, "wodMessage() - WODM_GETPOS"); result = sub_20509FE0(2, lpMem, (LPMMTIME)Data, v5); break; case 17: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 423, "wodMessage() - WODM_SETVOLUME"); result = sub_20508FE0(Data); break; case 16: fnLOGFunction("PSPSBEXT.DLL", ".\\Waveproc.c", 427, "wodMessage() - WODM_GETVOLUME"); result = sub_20509100(v6, (LPDWORD)Data); break; default: goto LABEL_33; } } } return result; } |
If you look at the marked lines you can see that message 17 is setvolume and message 16 is getvolume!
From the code in PspContr.exe we can see a sample for calling getvolume:
1 | v4 = PSPSBEXTwodMessage(16, 0, &a1, 0); |
That made it quite easy to guess the function parameters:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | {$WARN SYMBOL_PLATFORM OFF} function PSPSBEXTwodMessage(Msg: Integer; lpMem: DWORD; Data: PDWORD; cbwh: Integer): DWORD; stdcall external 'pspsbext.dll' delayed; {$WARN SYMBOL_PLATFORM ON} function GetVolume(var Vol: TPspVolume): Boolean; begin Result := PSPSBEXTwodMessage(WODM_GETVOLUME, 0, @Vol.Volume, 0) = 0; end; function SetVolume(const Vol: TPspVolume): Boolean; begin Result := PSPSBEXTwodMessage(WODM_SETVOLUME, 0, Pointer(Vol.Volume), 0) = 0; end; |
I made a commandline tool to get and set the volume so it could be deployed with the customer’s deployment tool (RES Automation Manager).
Philips G2 Speech Volume Commandline Tool (1712 downloads)
Leave a reply