How to get audio session GUID with Windows API - c++

I'm trying to make a simple mixer app using Windows audio API. I've already figured out how to get IAudioSessionEnumerator and IAudioSessionControl which allows me to retrieve the DisplayName of each session. And now I want to get/set volumes of these sessions. From winApi docs I think I first need to use GetSimpleAudioVolume method but it requires session GUID as a parameter. So how do I get a GUID of an existing session? I couldn't find any answers for that in docs or google. Or maybe I missunderstood something?
//code prints DisplayNames of all sessions
void getSessions() {
CoInitialize(NULL);
IMMDeviceEnumerator *pDEnumerator = NULL;
CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pDEnumerator);
IMMDevice *pDevice = NULL;
pDEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
IAudioSessionManager2 *pSManager2 = NULL;
pDevice->Activate(IID_IAudioSessionManager2, CLSCTX_ALL, NULL, (void**)&pSManager2);
IAudioSessionEnumerator *pSEnumerator = NULL;
pSManager2->GetSessionEnumerator(&pSEnumerator);
int audioSessionCount;
pSEnumerator->GetCount(&audioSessionCount);
std::cout << audioSessionCount << '\n';
for (int i = 0; i < audioSessionCount; ++i) {
IAudioSessionControl *controls;
pSEnumerator->GetSession(i, &controls);
LPWSTR name;
controls->GetDisplayName(&name);
while (*name != 0) {
std::wcout << *name;
++name;
}
std::cout << '\n';
}
}

Each audio session is uniquely identified with a GUID—session instance
identifier.
You can IAudioSessionControl2::GetSessionInstanceIdentifier and retrieve a string that contains the session identifier.
Or you can query ISimpleAudioVolume interface on IAudioSessionControl interface like this:
ISimpleAudioVolume *simpleAudioVol = NULL;
controls->QueryInterface(IID_PPV_ARGS(&simpleAudioVol));

Related

get application name from IAudioSessionControl2

How can I get the simple application name from following code of IAudioSessionControl2 ? Should I substring or use other method such as using GUID, although I dont know how to get app name from GUID. My target is to know the app name that is using a audio session.
Code:
LPWSTR instId = NULL;
hr = pSessionControl2->GetSessionInstanceIdentifier(&instId);
if (S_OK == hr)
{
wprintf_s(L"SessionInstanceIdentifier: %s\n", instId);
}
GUID guid;
HRESULT hr1 = CLSIDFromString(instId, (LPCLSID)&guid);
if (hr1 != S_OK) {
// bad GUID string...
//std::cout << "\nHi___\n" << guid.Data1 << "\n";
}
// out:
// SessionInstanceIdentifier: {0.0.0.00000000}.{db6e6565-391a-45a1-970d-41d8389b71ae}|\Device\HarddiskVolume4\Program Files\WindowsApps\App_15.83.3408.0_x86__kzf8qxf38zg5a\App\App.exe%b{00000000-...}|1%b9532

Retrieving info from multiple sensors using Windows Sensor API

I am using the Windows Sensor API to get info from various sensors including accelerometer and gyroscope. (https://learn.microsoft.com/en-us/windows/desktop/sensorsapi/sensor-api-programming-guide)
My initial implementation of sensor driver for accelerometer worked - I asynchronously can get the values for that sensor.
The working initialization code for a single sensor (accelerometer) looks like the following:
void initialize1(AccelerometerCallBack callBack) {
HRESULT hr = S_OK;
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
ISensorManager* pSensorManager = NULL;
ISensorCollection* pSensorColl = NULL;
ISensor* accelerometer = NULL;
hr = CoCreateInstance(CLSID_SensorManager,
NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pSensorManager));
if (SUCCEEDED(hr))
{
printf("Succeeded getting Sensor...\n");
ULONG ulCount = 0;
// Verify that the collection contains
// at least one sensor.
hr = pSensorColl->GetCount(&ulCount);
if (SUCCEEDED(hr))
{
if (ulCount < 1)
{
wprintf_s(L"\nNo sensors of the requested category.\n");
hr = E_UNEXPECTED;
}
}
}
else {
printf("Failed to get Sensor...\n");
}
if (SUCCEEDED(hr))
{
// Get the first available sensor.
hr = pSensorColl->GetAt(0, &accelerometer);
BSTR name = 0;
hr = accelerometer->GetFriendlyName(&name);
wprintf(L"%s\n", name);
}
AccelerometerEvent* pEventClass = NULL;
ISensorEvents* pMyEvents = NULL;
if (SUCCEEDED(hr))
{
// Create an instance of the event class.
pEventClass = new(std::nothrow) AccelerometerEvent();
pEventClass->globalCallBack = callBack;
}
if (SUCCEEDED(hr))
{
// Retrieve the pointer to the callback interface.
hr = pEventClass->QueryInterface(IID_PPV_ARGS(&pMyEvents));
}
if (SUCCEEDED(hr))
{
// Start receiving events.
hr = accelerometer->SetEventSink(pMyEvents);
}
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
Now I wanted to simultaneously get the gyroscope, so I add a similar initialization code:
void initialize2(GyroscopeCallBack callBack);, which does similar thing as above.
Now my C# layer triggers those code like this:
internal void Start()
{
AccelerometerCallBack myCallBack1 = new AccelerometerCallBack(onAccelerometerDataChanged);
initialize1(myCallBack1);
GyroscopeCallBack myCallBack2 = new GyroscopeCallBack(onGyroscopeDataChanged);
initialize2(myCallBack2);
}
However, only the accelerometer info is received through the callback, not the gyroscope's.
I have confirmed that my device has both sensors of the correct type
I have confirmed that my callback functions and C# to C++ interface all works properly
What is the correct way to "fetch" multiple sensors (instead of one) using the Windows Sensor API?
There are no examples on the official website about doing this, and the only related SO post (Exposing multiple sensors on a single device to Windows Sensor API) does not help. Thanks!
It worked! Solution was to remove the message pump and use COINIT_MULTITHREADED option for Coinitializine(). Thanks for all the help.

Set WMP volume level

What i want to do is set the Windows Media Player's volume level. By default the volume is in-/decreased by 10% when e.g. clicking the down or up menu item (Play -> Volume -> Up), but this is, in my opinion, not fine enough (especially when e.g. skypeing with someone while listening to music).
The media player should stay an independent application.
Currently i'm using a little tool that sends app commands via SendMessage to the player with parameters as seen in spy++.
I thought of three ways to achieve my goal:
using WASAPI to get the media player's audio session and dynamically set the volume level
sending mouse down/up events to the volume slider of the media player host control by point
getting an instance of the media player control via IWMPPlayer4
including a media player control in my WPF application within a windows forms host (not preferred due to loss of independence)
Point 2 seems rather ugly due to the fact that the media player control is a COM element and has as far spy++ displays only one handle, meaning i would have to determine the exact position of the volume slider and send very precise mouse events. Additional i don't know whether this would work at all.
Point 3 has the presupposition that one can get an instance of a COM element by a handle. Since i have yet not worked with COM elements i don't know if this is possible.
Update: One can get an instance of a remote mediay player using the IWMPPlayer4 interface. Though i have to see whether one can change settings.
Point 1 has the impression on me that it would be possible without much effort.
Though i'd be facing the next problem: identifying the media players audio session. Enumerating them using IAudioSessionManager2 and displaying the name using
IAudioSessionControl2 ctrl2 = NULL;
// ...
hr = ctrl2->GetDisplayName(&name);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
String ^sessionName = gcnew String(name);
Console::WriteLine("Session name: '" + sessionName + "'");
prints most the times an emtpy string except for Mozilla Firefox and System Sounds (the other processes might not have set a session name themselfes => a default name is chosen and GetDisplayName returns an empty string).
Update 2:
As Simon Mourier pointed out one can compare the process ids to get the right ISimpleAudioVolume instance and it works as far as it comes to WMP to adopt the changes. The said instance is aquired the following way:
IMMDeviceEnumerator *pEnumerator = NULL;
ISimpleAudioVolume *pVolume = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager2 *pManager = NULL;
IAudioSessionEnumerator *pSessionEnumerator = NULL;
int sessionCount = 0;
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eMultimedia, &pDevice);
pDevice->GetState(&deviceState);
pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&pManager);
pManager->GetSessionEnumerator(&pSessionEnumerator);
pSessionEnumerator->GetCount(&sessionCount);
for (int i = 0; i < sessionCount; i++)
{
IAudioSessionControl *ctrl = NULL;
IAudioSessionControl2 *ctrl2 = NULL;
DWORD processId = 0;
hr = pSessionEnumerator->GetSession(i, &ctrl);
if (FAILED(hr))
{
continue;
}
hr = ctrl->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&ctrl2);
if (FAILED(hr))
{
SafeRelease(ctrl);
continue;
}
hr = ctrl2->GetProcessId(&processId);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
if (processId == wmpProcessId)
{
hr = ctrl2->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&pVolume);
SafeRelease(ctrl);
SafeRelease(ctrl2);
break;
}
SafeRelease(ctrl);
SafeRelease(ctrl2);
}
When aquiering an ISimpleAudioVolume instance via a IAudioClient one has to provide a session id to have volume changes reported to event subscribers. Is this possible using this approach?
Though i know that adding a media player control to my application would be the easiest way, i'd like to not use this option if possible.
I don't know what may happened during my initial try to set the media player's volume level, but the following code works (most exception handling excluded):
HRESULT hr;
IMMDeviceEnumerator *pEnumerator = NULL;
ISimpleAudioVolume *pVolume = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager2 *pManager = NULL;
IAudioSessionEnumerator *pSessionEnumerator = NULL;
int sessionCount = 0;
int wmpProcess = GetWmpProcessId(); // Aquire WMPs process id
// Get the device enumerator and initialize the application for COM
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
// Get the default device
hr = pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender,
ERole::eMultimedia, &pDevice);
// Get the session 2 manager
hr = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL,
NULL, (void**)&pManager);
// Get the session enumerator
hr = pManager->GetSessionEnumerator(&pSessionEnumerator);
// Get the session count
hr = pSessionEnumerator->GetCount(&sessionCount);
// Loop through all sessions
for (int i = 0; i < sessionCount; i++)
{
IAudioSessionControl *ctrl = NULL;
IAudioSessionControl2 *ctrl2 = NULL;
DWORD processId = 0;
hr = pSessionEnumerator->GetSession(i, &ctrl);
if (FAILED(hr))
{
continue;
}
hr = ctrl->QueryInterface(__uuidof(IAudioSessionControl2), (void**)&ctrl2);
if (FAILED(hr))
{
SafeRelease(ctrl);
continue;
}
//Identify WMP process
hr = ctrl2->GetProcessId(&processId);
if (FAILED(hr))
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
if (processId != wmpProcess)
{
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
hr = ctrl2->QueryInterface(__uuidof(ISimpleAudioVolume), (void**)&pVolume);
if (FAILED(hr))
{
Error(hr, "Failed to get ISimpleAudioVolume.");
SafeRelease(ctrl);
SafeRelease(ctrl2);
continue;
}
// Set the master volume
hr = pVolume->SetMasterVolume(1.0, NULL);
if (FAILED(hr))
{
Error(hr, "Failed to set the master volume.");
SafeRelease(ctrl);
SafeRelease(ctrl2);
SafeRelease(pVolume);
continue;
}
SafeRelease(ctrl);
SafeRelease(ctrl2);
SafeRelease(pVolume);
}

Problems accessing a COM interface in C++

What I want to do is access a COM interface and then call the "Open" method of that interface.
I have a sample code in Visual Basic which works fine, but I need to write it in C++ and I can't seem to get it to work.
First of all, this is the working VB code:
Dim CANapeApplication As CANAPELib.Application
CANapeApplication = CreateObject("CANape.Application")
Call CANapeApplication.Open("C:\Users\Public\Documents\Vector\CANape\12\Project", 0)
CANape.Application is the ProgID which selects the interface I need.
After reading some docs at msdn.microsoft.com and this question, I wrote this code:
void ErrorDescription(HRESULT hr); //Function to output a readable hr error
int InitCOM();
int OpenCANape();
// Declarations of variables used.
HRESULT hresult;
void **canApeAppPtr;
IDispatch *pdisp;
CLSID ClassID;
DISPID FAR dispid;
UINT nArgErr;
OLECHAR FAR* canApeWorkingDirectory = L"C:\\Users\\Public\\Documents\\Vector\\CANape\\12\\Project";
int main(){
// Instantiate CANape COM interface
if (InitCOM() != 0) {
std::cout << "init error";
return 1;
}
// Open CANape
if (OpenCANape() != 0) {
std::cout << "Failed to open CANape Project" << std::endl;
return 1;
}
CoUninitialize();
return 0;
}
void ErrorDescription(HRESULT hr) {
if(FACILITY_WINDOWS == HRESULT_FACILITY(hr))
hr = HRESULT_CODE(hr);
TCHAR* szErrMsg;
if(FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&szErrMsg, 0, NULL) != 0)
{
_tprintf(TEXT("%s"), szErrMsg);
LocalFree(szErrMsg);
} else
_tprintf( TEXT("[Could not find a description for error # %#x.]\n"), hr);
}
int InitCOM() {
// Initialize OLE DLLs.
hresult = OleInitialize(NULL);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// Get CLSID from ProgID
//hresult = CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);
hresult = CLSIDFromProgID(OLESTR("CanapeCom.CanapeCom"), &ClassID);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// OLE function CoCreateInstance starts application using GUID/CLSID
hresult = CoCreateInstance(ClassID, NULL, CLSCTX_LOCAL_SERVER,
IID_IDispatch, (void **)&pdisp);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// Call QueryInterface to see if object supports IDispatch
hresult = pdisp->QueryInterface(IID_IDispatch, (void **)&pdisp);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
std::cout << "success" << std::endl;
return 0;
}
int OpenCANape() {
//Method name
OLECHAR *szMember = L"Open";
// Retrieve the dispatch identifier for the Open method
// Use defaults where possible
DISPID idFileExists;
hresult = pdisp->GetIDsOfNames(
IID_NULL,
&szMember,
1,
LOCALE_SYSTEM_DEFAULT,
&idFileExists);
if (!SUCCEEDED(hresult)) {
std::cout << "GetIDsOfNames: ";
ErrorDescription(hresult);
return 1;
}
unsigned int puArgErr = 0;
VARIANT VarResult;
VariantInit(&VarResult);
DISPPARAMS pParams;
memset(&pParams, 0, sizeof(DISPPARAMS));
pParams.cArgs = 2;
VARIANT Arguments[2];
VariantInit(&Arguments[0]);
pParams.rgvarg = Arguments;
pParams.cNamedArgs = 0;
pParams.rgvarg[0].vt = VT_BSTR;
pParams.rgvarg[0].bstrVal = SysAllocString(canApeWorkingDirectory);
pParams.rgvarg[1].vt = VT_INT;
pParams.rgvarg[1].intVal = 0; // debug mode
// Invoke the method. Use defaults where possible.
hresult = pdisp->Invoke(
dispid,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD,
&pParams,
&VarResult,
NULL,
&puArgErr
);
SysFreeString(pParams.rgvarg[0].bstrVal);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
return 0;
}
There are several problems with this.
Using the ClassID received from CLSIDFromProgID as the first parameter of CoCreateInstance does not work, it returns the error: class not registered
If i use the ProgID CanapeCom.CanapeCom (I found it by looking in the Registry), CoCreateInstance works. However, when I use pdisp->GetIDsOfNames I get the error message: Unkown name. Which I think means that the method was not found. That seems logical because I've used a different ProgID, but I just can't figure out how to get to the interface I'm looking for.
I have also tried to use the resulting CLSID from CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID); as the 4th argument of CoCreateInstance but that resulted in a "No such interface supported" error.
Do I need the dll file of the software? In the VB example the dll file is used to get the interface and then create a new object using the ProgID. I'm not sure if I need to do the same in C++ or how this should work.
I'm really stuck here and hope that someone can help me.
Thanks for your comments.
I've fixed the problem, although the solution is kind of embarrassing...
In my defense, I'm still a student and new to this kind of stuff.
I've used the Process Monitor to check what happens when I execute the VB script.
I saw that the CLSID used there is the ID returned by CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);, which meant that this had to be the right one and the problem had to be somewhere else. I've looked again at the CoCreateInstance and then took a look at the other parameters. Turns out that the context CLSCTX_LOCAL_SERVER was wrong, it has to be CLSCTX_INPROC_SERVER. I don't know why I've set it to local_server in the first place or why I've never questioned it. I wrote that part of the code a few days ago and then focused too much on the CLSID and IID rather than on the other parameters.
I've also taken the first comment from Alex into account and created a tlb file.
This is a simplified version of the code that works:
#import "CANape.tlb"
int _tmain(int argc, _TCHAR* argv[])
{
_bstr_t path = "C:\\Users\\Public\\Documents\\Vector\\CANape\\12\\Project";
CLSID idbpnt;
CoInitialize(NULL);
HRESULT hr = CLSIDFromProgID (L"CANape.Application", &idbpnt);
CANAPELib::IApplication *app;
hr = CoCreateInstance(idbpnt,NULL,CLSCTX_INPROC_SERVER,__uuidof(CANAPELib::IApplication),(LPVOID*)&app );
app->Open(path,0);
CoUninitialize();
return 0;
}

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.