You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
14 KiB
503 lines
14 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
|
|
#include "audio_pch.h" |
|
#include "voice_mixer_controls.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
// NOTE: Vista deprecated these APIs |
|
// Under vista these settings are per-session (not persistent) |
|
// The correct method is to use the AudioEndpoint COM objects to manage controls like this |
|
// The interface is not 1:1 so for now we'll just back the state with convars and reapply it |
|
// on init of the mixer controls. In the future when XP is no longer the majority of our user base |
|
// we should revisit this and move to the new API. |
|
// http://msdn.microsoft.com/en-us/library/aa964574(VS.85).aspx |
|
|
|
|
|
class CMixerControls : public IMixerControls |
|
{ |
|
public: |
|
CMixerControls(); |
|
virtual ~CMixerControls(); |
|
|
|
virtual bool GetValue_Float(Control iControl, float &value); |
|
virtual bool SetValue_Float(Control iControl, float value); |
|
virtual bool SelectMicrophoneForWaveInput(); |
|
|
|
|
|
private: |
|
bool Init(); |
|
void Term(); |
|
|
|
void Clear(); |
|
|
|
bool GetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, bool &bValue); |
|
bool SetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, const bool bValue); |
|
|
|
bool GetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, DWORD &value); |
|
bool SetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, const DWORD value); |
|
|
|
bool GetLineControls( DWORD dwLineID, MIXERCONTROL *controls, DWORD nControls ); |
|
void FindMicSelectControl( DWORD dwLineID, DWORD nControls ); |
|
|
|
|
|
private: |
|
HMIXER m_hMixer; |
|
|
|
class ControlInfo |
|
{ |
|
public: |
|
DWORD m_dwControlID; |
|
DWORD m_cMultipleItems; |
|
bool m_bFound; |
|
}; |
|
|
|
DWORD m_dwMicSelectControlID; |
|
DWORD m_dwMicSelectMultipleItems; |
|
DWORD m_dwMicSelectControlType; |
|
DWORD m_dwMicSelectIndex; |
|
|
|
// Info about the controls we found. |
|
ControlInfo m_ControlInfos[NumControls]; |
|
}; |
|
|
|
|
|
|
|
CMixerControls::CMixerControls() |
|
{ |
|
m_dwMicSelectControlID = 0xFFFFFFFF; |
|
|
|
Clear(); |
|
Init(); |
|
} |
|
|
|
CMixerControls::~CMixerControls() |
|
{ |
|
Term(); |
|
} |
|
|
|
bool CMixerControls::Init() |
|
{ |
|
Term(); |
|
|
|
|
|
MMRESULT mmr; |
|
|
|
bool bFoundMixer = false; |
|
bool bFoundConnectionWithMicVolume = false; |
|
|
|
CUtlVectorFixedGrowable<MIXERCONTROL, 64> controls; |
|
// Iterate over all the devices |
|
// This is done in reverse so the 0th device is our fallback if none of them had the correct MicVolume control |
|
for ( int iDevice = static_cast<int>( mixerGetNumDevs() ) - 1; iDevice >= 0 && !bFoundConnectionWithMicVolume; --iDevice ) |
|
{ |
|
// Open the mixer. |
|
mmr = mixerOpen(&m_hMixer, (DWORD)iDevice, 0, 0, 0 ); |
|
if(mmr != MMSYSERR_NOERROR) |
|
{ |
|
continue; |
|
} |
|
|
|
// Iterate over each destination line, looking for Play Controls. |
|
MIXERCAPS mxcaps; |
|
mmr = mixerGetDevCaps((UINT)m_hMixer, &mxcaps, sizeof(mxcaps)); |
|
if(mmr != MMSYSERR_NOERROR) |
|
{ |
|
continue; |
|
} |
|
|
|
bFoundMixer = true; |
|
|
|
for(UINT u = 0; u < mxcaps.cDestinations; u++) |
|
{ |
|
MIXERLINE recordLine; |
|
recordLine.cbStruct = sizeof(recordLine); |
|
recordLine.dwDestination = u; |
|
mmr = mixerGetLineInfo((HMIXEROBJ)m_hMixer, &recordLine, MIXER_GETLINEINFOF_DESTINATION); |
|
if(mmr != MMSYSERR_NOERROR) |
|
continue; |
|
|
|
|
|
// Go through the controls that aren't attached to a specific src connection. |
|
// We're looking for the checkbox that enables the user's microphone for waveIn. |
|
if( recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN ) |
|
{ |
|
FindMicSelectControl( recordLine.dwLineID, recordLine.cControls ); |
|
} |
|
|
|
|
|
// Now iterate over each connection (things like wave out, microphone, speaker, CD audio), looking for Microphone. |
|
UINT cConnections = (UINT)recordLine.cConnections; |
|
for (UINT v = 0; v < cConnections; v++) |
|
{ |
|
MIXERLINE micLine; |
|
micLine.cbStruct = sizeof(micLine); |
|
micLine.dwDestination = u; |
|
micLine.dwSource = v; |
|
|
|
mmr = mixerGetLineInfo((HMIXEROBJ)m_hMixer, &micLine, MIXER_GETLINEINFOF_SOURCE); |
|
if(mmr != MMSYSERR_NOERROR) |
|
continue; |
|
|
|
// Now look at all the controls (volume, mute, boost, etc). |
|
controls.RemoveAll(); |
|
controls.SetCount(micLine.cControls); |
|
if( !GetLineControls( micLine.dwLineID, controls.Base(), micLine.cControls ) ) |
|
continue; |
|
|
|
for(UINT i=0; i < micLine.cControls; i++) |
|
{ |
|
MIXERCONTROL *pControl = &controls[i]; |
|
|
|
if(micLine.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE) |
|
{ |
|
if( pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_ONOFF && |
|
( |
|
strstr(pControl->szShortName, "Gain") || |
|
strstr(pControl->szShortName, "Boos") || |
|
strstr(pControl->szShortName, "+20d") |
|
) |
|
) |
|
{ |
|
// This is the (record) boost option. |
|
m_ControlInfos[MicBoost].m_bFound = true; |
|
m_ControlInfos[MicBoost].m_dwControlID = pControl->dwControlID; |
|
m_ControlInfos[MicBoost].m_cMultipleItems = pControl->cMultipleItems; |
|
} |
|
|
|
if(recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_SPEAKERS && |
|
pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE) |
|
{ |
|
// This is the mute button. |
|
m_ControlInfos[MicMute].m_bFound = true; |
|
m_ControlInfos[MicMute].m_dwControlID = pControl->dwControlID; |
|
m_ControlInfos[MicMute].m_cMultipleItems = pControl->cMultipleItems; |
|
} |
|
|
|
if(recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN && |
|
pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME) |
|
{ |
|
// This is the mic input level. |
|
m_ControlInfos[MicVolume].m_bFound = true; |
|
m_ControlInfos[MicVolume].m_dwControlID = pControl->dwControlID; |
|
m_ControlInfos[MicVolume].m_cMultipleItems = pControl->cMultipleItems; |
|
|
|
// We found a good recording device and can stop looking throught the available devices |
|
bFoundConnectionWithMicVolume = true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( !bFoundMixer ) |
|
{ |
|
// Failed to find any mixer (MixVolume or not) |
|
Term(); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CMixerControls::Term() |
|
{ |
|
if(m_hMixer) |
|
{ |
|
mixerClose(m_hMixer); |
|
m_hMixer = 0; |
|
} |
|
|
|
Clear(); |
|
} |
|
|
|
|
|
|
|
bool CMixerControls::GetValue_Float( Control iControl, float &flValue ) |
|
{ |
|
if( iControl < 0 || iControl >= NumControls || !m_ControlInfos[iControl].m_bFound ) |
|
return false; |
|
|
|
if(iControl == MicBoost || iControl == MicMute) |
|
{ |
|
bool bValue = false; |
|
bool ret = GetControlOption_Bool(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, bValue); |
|
flValue = (float)bValue; |
|
return ret; |
|
} |
|
else if(iControl == MicVolume) |
|
{ |
|
DWORD dwValue = (DWORD)0; |
|
if(GetControlOption_Unsigned(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, dwValue)) |
|
{ |
|
flValue = dwValue / 65535.0f; |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
bool CMixerControls::SetValue_Float(Control iControl, float flValue ) |
|
{ |
|
if(iControl < 0 || iControl >= NumControls || !m_ControlInfos[iControl].m_bFound) |
|
return false; |
|
|
|
if(iControl == MicBoost || iControl == MicMute) |
|
{ |
|
bool bValue = !!flValue; |
|
return SetControlOption_Bool(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, bValue); |
|
} |
|
else if(iControl == MicVolume) |
|
{ |
|
DWORD dwValue = (DWORD)(flValue * 65535.0f); |
|
return SetControlOption_Unsigned(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, dwValue); |
|
} |
|
return false; |
|
} |
|
|
|
|
|
bool CMixerControls::SelectMicrophoneForWaveInput() |
|
{ |
|
if( m_dwMicSelectControlID == 0xFFFFFFFF ) |
|
return false; |
|
|
|
MIXERCONTROLDETAILS_BOOLEAN *pmxcdSelectValue = |
|
(MIXERCONTROLDETAILS_BOOLEAN*)_alloca( sizeof(MIXERCONTROLDETAILS_BOOLEAN) * m_dwMicSelectMultipleItems ); |
|
|
|
MIXERCONTROLDETAILS mxcd; |
|
mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); |
|
mxcd.dwControlID = m_dwMicSelectControlID; |
|
mxcd.cChannels = 1; |
|
mxcd.cMultipleItems = m_dwMicSelectMultipleItems; |
|
mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); |
|
mxcd.paDetails = pmxcdSelectValue; |
|
if (mixerGetControlDetails(reinterpret_cast<HMIXEROBJ>(m_hMixer), |
|
&mxcd, |
|
MIXER_OBJECTF_HMIXER | |
|
MIXER_GETCONTROLDETAILSF_VALUE) |
|
== MMSYSERR_NOERROR) |
|
{ |
|
// MUX restricts the line selection to one source line at a time. |
|
if( m_dwMicSelectControlType == MIXERCONTROL_CONTROLTYPE_MUX ) |
|
{ |
|
ZeroMemory(pmxcdSelectValue, |
|
m_dwMicSelectMultipleItems * sizeof(MIXERCONTROLDETAILS_BOOLEAN)); |
|
} |
|
|
|
// set the Microphone value |
|
pmxcdSelectValue[m_dwMicSelectIndex].fValue = 1; |
|
|
|
mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); |
|
mxcd.dwControlID = m_dwMicSelectControlID; |
|
mxcd.cChannels = 1; |
|
mxcd.cMultipleItems = m_dwMicSelectMultipleItems; |
|
mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); |
|
mxcd.paDetails = pmxcdSelectValue; |
|
if (mixerSetControlDetails(reinterpret_cast<HMIXEROBJ>(m_hMixer), |
|
&mxcd, |
|
MIXER_OBJECTF_HMIXER | |
|
MIXER_SETCONTROLDETAILSF_VALUE) == MMSYSERR_NOERROR) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
|
|
return false; |
|
} |
|
|
|
|
|
void CMixerControls::Clear() |
|
{ |
|
m_hMixer = 0; |
|
memset(m_ControlInfos, 0, sizeof(m_ControlInfos)); |
|
} |
|
|
|
bool CMixerControls::GetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, bool &bValue) |
|
{ |
|
MIXERCONTROLDETAILS details; |
|
MIXERCONTROLDETAILS_BOOLEAN controlValue; |
|
|
|
details.cbStruct = sizeof(details); |
|
details.dwControlID = dwControlID; |
|
details.cChannels = 1; // uniform.. |
|
details.cMultipleItems = cMultipleItems; |
|
details.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); |
|
details.paDetails = &controlValue; |
|
|
|
MMRESULT mmr = mixerGetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L); |
|
if(mmr == MMSYSERR_NOERROR) |
|
{ |
|
bValue = !!controlValue.fValue; |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
bool CMixerControls::SetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, const bool bValue) |
|
{ |
|
MIXERCONTROLDETAILS details; |
|
MIXERCONTROLDETAILS_BOOLEAN controlValue; |
|
|
|
details.cbStruct = sizeof(details); |
|
details.dwControlID = dwControlID; |
|
details.cChannels = 1; // uniform.. |
|
details.cMultipleItems = cMultipleItems; |
|
details.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN); |
|
details.paDetails = &controlValue; |
|
|
|
controlValue.fValue = bValue; |
|
|
|
MMRESULT mmr = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L); |
|
return mmr == MMSYSERR_NOERROR; |
|
} |
|
|
|
bool CMixerControls::GetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, DWORD &value) |
|
{ |
|
MIXERCONTROLDETAILS details; |
|
MIXERCONTROLDETAILS_UNSIGNED controlValue; |
|
|
|
details.cbStruct = sizeof(details); |
|
details.dwControlID = dwControlID; |
|
details.cChannels = 1; // uniform.. |
|
details.cMultipleItems = cMultipleItems; |
|
details.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); |
|
details.paDetails = &controlValue; |
|
|
|
MMRESULT mmr = mixerGetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L); |
|
if(mmr == MMSYSERR_NOERROR) |
|
{ |
|
value = controlValue.dwValue; |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
bool CMixerControls::SetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, const DWORD value) |
|
{ |
|
MIXERCONTROLDETAILS details; |
|
MIXERCONTROLDETAILS_UNSIGNED controlValue; |
|
|
|
details.cbStruct = sizeof(details); |
|
details.dwControlID = dwControlID; |
|
details.cChannels = 1; // uniform.. |
|
details.cMultipleItems = cMultipleItems; |
|
details.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED); |
|
details.paDetails = &controlValue; |
|
|
|
controlValue.dwValue = value; |
|
|
|
MMRESULT mmr = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L); |
|
return mmr == MMSYSERR_NOERROR; |
|
} |
|
|
|
|
|
bool CMixerControls::GetLineControls( DWORD dwLineID, MIXERCONTROL *controls, DWORD nControls ) |
|
{ |
|
MIXERLINECONTROLS mxlc; |
|
|
|
mxlc.cbStruct = sizeof(mxlc); |
|
mxlc.dwLineID = dwLineID; |
|
mxlc.cControls = nControls; |
|
mxlc.cbmxctrl = sizeof(MIXERCONTROL); |
|
mxlc.pamxctrl = controls; |
|
|
|
MMRESULT mmr = mixerGetLineControls((HMIXEROBJ)m_hMixer, &mxlc, MIXER_GETLINECONTROLSF_ALL); |
|
return mmr == MMSYSERR_NOERROR; |
|
} |
|
|
|
|
|
void CMixerControls::FindMicSelectControl( DWORD dwLineID, DWORD nControls ) |
|
{ |
|
m_dwMicSelectControlID = 0xFFFFFFFF; |
|
|
|
MIXERCONTROL *recControls = (MIXERCONTROL*)_alloca( sizeof(MIXERCONTROL) * nControls ); |
|
if( !GetLineControls( dwLineID, recControls, nControls ) ) |
|
return; |
|
|
|
for( UINT iRecControl=0; iRecControl < nControls; iRecControl++ ) |
|
{ |
|
if( recControls[iRecControl].dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER || |
|
recControls[iRecControl].dwControlType == MIXERCONTROL_CONTROLTYPE_MUX ) |
|
{ |
|
m_dwMicSelectControlID = recControls[iRecControl].dwControlID; |
|
m_dwMicSelectControlType = recControls[iRecControl].dwControlType; |
|
m_dwMicSelectMultipleItems = recControls[iRecControl].cMultipleItems; |
|
m_dwMicSelectIndex = iRecControl; |
|
|
|
// Get the index of the one that selects the mic. |
|
MIXERCONTROLDETAILS_LISTTEXT *pmxcdSelectText = |
|
(MIXERCONTROLDETAILS_LISTTEXT*)_alloca( sizeof(MIXERCONTROLDETAILS_LISTTEXT) * m_dwMicSelectMultipleItems ); |
|
|
|
MIXERCONTROLDETAILS mxcd; |
|
mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS); |
|
mxcd.dwControlID = m_dwMicSelectControlID; |
|
mxcd.cChannels = 1; |
|
mxcd.cMultipleItems = m_dwMicSelectMultipleItems; |
|
mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT); |
|
mxcd.paDetails = pmxcdSelectText; |
|
|
|
if (mixerGetControlDetails((HMIXEROBJ)m_hMixer, |
|
&mxcd, |
|
MIXER_OBJECTF_HMIXER | |
|
MIXER_GETCONTROLDETAILSF_LISTTEXT) == MMSYSERR_NOERROR) |
|
{ |
|
// determine which controls the Microphone source line |
|
for (DWORD dwi = 0; dwi < m_dwMicSelectMultipleItems; dwi++) |
|
{ |
|
// get the line information |
|
MIXERLINE mxl; |
|
mxl.cbStruct = sizeof(MIXERLINE); |
|
mxl.dwLineID = pmxcdSelectText[dwi].dwParam1; |
|
|
|
if (mixerGetLineInfo((HMIXEROBJ)m_hMixer, |
|
&mxl, |
|
MIXER_OBJECTF_HMIXER | |
|
MIXER_GETLINEINFOF_LINEID) == MMSYSERR_NOERROR && |
|
mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE) |
|
{ |
|
// found, dwi is the index. |
|
m_dwMicSelectIndex = dwi; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
IMixerControls* g_pMixerControls = NULL; |
|
void InitMixerControls() |
|
{ |
|
if ( !g_pMixerControls ) |
|
{ |
|
g_pMixerControls = new CMixerControls; |
|
} |
|
} |
|
|
|
void ShutdownMixerControls() |
|
{ |
|
delete g_pMixerControls; |
|
g_pMixerControls = NULL; |
|
} |
|
|
|
|