管理音频会话
[与本页关联的功能 MFPlay 是一项旧版功能。 它已被 MediaPlayer 和 IMFMediaEngine 取代。 这些功能已针对 Windows 10 和 Windows 11 进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayer 和 IMFMediaEngine,而不是 DirectShow。 如果可能,Microsoft 建议应重写使用旧 API 的现有代码,以尽可能地使用新的 API。]
本主题介绍如何在使用 MFPlay 进行音频/视频播放时控制音频音量。
MFPlay 提供以下方法来控制播放期间的音频音量。
方法 | 说明 |
---|---|
IMFPMediaPlayer::SetBalance | 设置左右声道之间的平衡。 |
IMFPMediaPlayer::SetMute | 将音频静音或取消静音。 |
IMFPMediaPlayer::SetVolume | 设置音量。 |
若要了解这些方法的表现,必须了解 Windows 音频会话 API (WASAPI) 中的一些术语,该 API 实现了 MFPlay 使用的低级别音频功能。
在 WASAPI 中,每个音频流都完全属于一个 音频会话,即一组相关的音频流。 通常,应用程序维护单个音频会话,尽管应用程序可以创建多个会话。 系统音量控制程序 (Sndvol) 为每个音频会话显示音量控制。 通过 Sndvol,用户可以从应用程序外部调整音频会话的音量。 下图说明了此过程。
在 MFPlay 中,媒体项可以有一个或多个活动音频流(通常只有一个)。 在内部,MFPlay 使用流式处理音频呈现器 (SAR) 来呈现音频流。 除非另外配置,否则 SAR 会加入应用程序的默认音频会话。
MFPlay 音频方法仅控制属于当前媒体项的流。 它们不会影响属于同一音频会话的任何其他流的音量。 就 WASAPI 而言,MFPlay 方法控制每个声道的音量,而不是主音量。 下图说明了此过程。
了解 MFPlay 的此功能的一些影响非常重要。 首先,应用程序可以调整播放音量,而不会影响其他音频流。 如果 MFPlay 实现音频交叉淡化,则可以使用此功能,方法是创建 MFPlay 对象的两个实例并单独调整音量。
如果使用 MFPlay 方法更改音量或静音状态,则更改不会显示在 Sndvol 中。 例如,可以调用 SetMute 将音频静音,但 Sndvol 不会将会话显示为静音。 相反,如果使用 SndVol 调整会话音量,则更改不会反映在 IMFPMediaPlayer::GetVolume 或 IMFPMediaPlayer::GetMute 返回的值中。
对于 MFPlay 播放器对象的每个实例,有效音量等于 fPlayerVolume × fSessionVolume,其中 fPlayerVolume 是 GetVolume 返回的值,fSessionVolume 是会话的主音量。
对于简单的播放方案,最好使用 WASAPI 控制整个会话的音频音量,而不是使用 MFPlay 方法。
示例代码
下面是处理 WASAPI 中基本任务的 C++ 类:
- 控制会话的音量和静音状态。
- 每当音量或静音状态更改时收到通知。
类声明
CAudioSessionVolume
类声明实现 IAudioSessionEvents 接口,该接口是音频会话事件的回调接口。
class CAudioSessionVolume : public IAudioSessionEvents
{
public:
// Static method to create an instance of the object.
static HRESULT CreateInstance(
UINT uNotificationMessage,
HWND hwndNotification,
CAudioSessionVolume **ppAudioSessionVolume
);
// IUnknown methods.
STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IAudioSessionEvents methods.
STDMETHODIMP OnSimpleVolumeChanged(
float NewVolume,
BOOL NewMute,
LPCGUID EventContext
);
// The remaining audio session events do not require any action.
STDMETHODIMP OnDisplayNameChanged(LPCWSTR,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnIconPathChanged(LPCWSTR,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnChannelVolumeChanged(DWORD,float[],DWORD,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnGroupingParamChanged(LPCGUID,LPCGUID)
{
return S_OK;
}
STDMETHODIMP OnStateChanged(AudioSessionState)
{
return S_OK;
}
STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason)
{
return S_OK;
}
// Other methods
HRESULT EnableNotifications(BOOL bEnable);
HRESULT GetVolume(float *pflVolume);
HRESULT SetVolume(float flVolume);
HRESULT GetMute(BOOL *pbMute);
HRESULT SetMute(BOOL bMute);
HRESULT SetDisplayName(const WCHAR *wszName);
protected:
CAudioSessionVolume(UINT uNotificationMessage, HWND hwndNotification);
~CAudioSessionVolume();
HRESULT Initialize();
protected:
LONG m_cRef; // Reference count.
UINT m_uNotificationMessage; // Window message to send when an audio event occurs.
HWND m_hwndNotification; // Window to receives messages.
BOOL m_bNotificationsEnabled; // Are audio notifications enabled?
IAudioSessionControl *m_pAudioSession;
ISimpleAudioVolume *m_pSimpleAudioVolume;
};
当 CAudioSessionVolume
对象收到音频会话事件时,它会向应用程序发布专用窗口消息。 窗口句柄和窗口消息作为参数提供给静态 CAudioSessionVolume::CreateInstance
方法。
获取 WASAPI 接口指针
CAudioSessionVolume
使用两个主要 WASAPI 接口:
- IAudioSessionControl 管理音频会话。
- ISimpleAudioVolume 控制会话的音量和静音状态。
若要获取这些接口,必须枚举 SAR 使用的音频终结点。 音频终结点是捕获或使用音频数据的硬件设备。 对于音频播放,终结点只是扬声器或其他音频输出端。 默认情况下,SAR 使用 eConsole 设备角色的默认终结点。 设备角色是为终结点分配的角色。 设备角色由 ERole 枚举指定,该枚举记录在核心音频 API 中。
以下代码演示如何枚举终结点并获取 WASAPI 接口。
HRESULT CAudioSessionVolume::Initialize()
{
HRESULT hr = S_OK;
IMMDeviceEnumerator *pDeviceEnumerator = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager *pAudioSessionManager = NULL;
// Get the enumerator for the audio endpoint devices.
hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator),
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pDeviceEnumerator)
);
if (FAILED(hr))
{
goto done;
}
// Get the default audio endpoint that the SAR will use.
hr = pDeviceEnumerator->GetDefaultAudioEndpoint(
eRender,
eConsole, // The SAR uses 'eConsole' by default.
&pDevice
);
if (FAILED(hr))
{
goto done;
}
// Get the session manager for this device.
hr = pDevice->Activate(
__uuidof(IAudioSessionManager),
CLSCTX_INPROC_SERVER,
NULL,
(void**) &pAudioSessionManager
);
if (FAILED(hr))
{
goto done;
}
// Get the audio session.
hr = pAudioSessionManager->GetAudioSessionControl(
&GUID_NULL, // Get the default audio session.
FALSE, // The session is not cross-process.
&m_pAudioSession
);
if (FAILED(hr))
{
goto done;
}
hr = pAudioSessionManager->GetSimpleAudioVolume(
&GUID_NULL, 0, &m_pSimpleAudioVolume
);
done:
SafeRelease(&pDeviceEnumerator);
SafeRelease(&pDevice);
SafeRelease(&pAudioSessionManager);
return hr;
}
控制音量
控制音频音量的 CAudioSessionVolume
方法调用等效 ISimpleAudioVolume 方法。 例如,CAudioSessionVolume::SetVolume
调用 ISimpleAudioVolume::SetMasterVolume,如以下代码所示。
HRESULT CAudioSessionVolume::SetVolume(float flVolume)
{
if (m_pSimpleAudioVolume == NULL)
{
return E_FAIL;
}
else
{
return m_pSimpleAudioVolume->SetMasterVolume(
flVolume,
&AudioSessionVolumeCtx // Event context.
);
}
}
完成 CAudioSessionVolume 代码
下面是 CAudioSessionVolume
类采用的方法的完整列表。
static const GUID AudioSessionVolumeCtx =
{ 0x2715279f, 0x4139, 0x4ba0, { 0x9c, 0xb1, 0xb3, 0x51, 0xf1, 0xb5, 0x8a, 0x4a } };
CAudioSessionVolume::CAudioSessionVolume(
UINT uNotificationMessage,
HWND hwndNotification
)
: m_cRef(1),
m_uNotificationMessage(uNotificationMessage),
m_hwndNotification(hwndNotification),
m_bNotificationsEnabled(FALSE),
m_pAudioSession(NULL),
m_pSimpleAudioVolume(NULL)
{
}
CAudioSessionVolume::~CAudioSessionVolume()
{
EnableNotifications(FALSE);
SafeRelease(&m_pAudioSession);
SafeRelease(&m_pSimpleAudioVolume);
};
// Creates an instance of the CAudioSessionVolume object.
/* static */
HRESULT CAudioSessionVolume::CreateInstance(
UINT uNotificationMessage,
HWND hwndNotification,
CAudioSessionVolume **ppAudioSessionVolume
)
{
CAudioSessionVolume *pAudioSessionVolume = new (std::nothrow)
CAudioSessionVolume(uNotificationMessage, hwndNotification);
if (pAudioSessionVolume == NULL)
{
return E_OUTOFMEMORY;
}
HRESULT hr = pAudioSessionVolume->Initialize();
if (SUCCEEDED(hr))
{
*ppAudioSessionVolume = pAudioSessionVolume;
}
else
{
pAudioSessionVolume->Release();
}
return hr;
}
// Initializes the CAudioSessionVolume object.
HRESULT CAudioSessionVolume::Initialize()
{
HRESULT hr = S_OK;
IMMDeviceEnumerator *pDeviceEnumerator = NULL;
IMMDevice *pDevice = NULL;
IAudioSessionManager *pAudioSessionManager = NULL;
// Get the enumerator for the audio endpoint devices.
hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator),
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pDeviceEnumerator)
);
if (FAILED(hr))
{
goto done;
}
// Get the default audio endpoint that the SAR will use.
hr = pDeviceEnumerator->GetDefaultAudioEndpoint(
eRender,
eConsole, // The SAR uses 'eConsole' by default.
&pDevice
);
if (FAILED(hr))
{
goto done;
}
// Get the session manager for this device.
hr = pDevice->Activate(
__uuidof(IAudioSessionManager),
CLSCTX_INPROC_SERVER,
NULL,
(void**) &pAudioSessionManager
);
if (FAILED(hr))
{
goto done;
}
// Get the audio session.
hr = pAudioSessionManager->GetAudioSessionControl(
&GUID_NULL, // Get the default audio session.
FALSE, // The session is not cross-process.
&m_pAudioSession
);
if (FAILED(hr))
{
goto done;
}
hr = pAudioSessionManager->GetSimpleAudioVolume(
&GUID_NULL, 0, &m_pSimpleAudioVolume
);
done:
SafeRelease(&pDeviceEnumerator);
SafeRelease(&pDevice);
SafeRelease(&pAudioSessionManager);
return hr;
}
STDMETHODIMP CAudioSessionVolume::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CAudioSessionVolume, IAudioSessionEvents),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_(ULONG) CAudioSessionVolume::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) CAudioSessionVolume::Release()
{
LONG cRef = InterlockedDecrement( &m_cRef );
if (cRef == 0)
{
delete this;
}
return cRef;
}
// Enables or disables notifications from the audio session. For example, the
// application is notified if the user mutes the audio through the system
// volume-control program (Sndvol).
HRESULT CAudioSessionVolume::EnableNotifications(BOOL bEnable)
{
HRESULT hr = S_OK;
if (m_hwndNotification == NULL || m_pAudioSession == NULL)
{
return E_FAIL;
}
if (m_bNotificationsEnabled == bEnable)
{
// No change.
return S_OK;
}
if (bEnable)
{
hr = m_pAudioSession->RegisterAudioSessionNotification(this);
}
else
{
hr = m_pAudioSession->UnregisterAudioSessionNotification(this);
}
if (SUCCEEDED(hr))
{
m_bNotificationsEnabled = bEnable;
}
return hr;
}
// Gets the session volume level.
HRESULT CAudioSessionVolume::GetVolume(float *pflVolume)
{
if ( m_pSimpleAudioVolume == NULL)
{
return E_FAIL;
}
else
{
return m_pSimpleAudioVolume->GetMasterVolume(pflVolume);
}
}
// Sets the session volume level.
//
// flVolume: Ranges from 0 (silent) to 1 (full volume)
HRESULT CAudioSessionVolume::SetVolume(float flVolume)
{
if (m_pSimpleAudioVolume == NULL)
{
return E_FAIL;
}
else
{
return m_pSimpleAudioVolume->SetMasterVolume(
flVolume,
&AudioSessionVolumeCtx // Event context.
);
}
}
// Gets the muting state of the session.
HRESULT CAudioSessionVolume::GetMute(BOOL *pbMute)
{
if (m_pSimpleAudioVolume == NULL)
{
return E_FAIL;
}
else
{
return m_pSimpleAudioVolume->GetMute(pbMute);
}
}
// Mutes or unmutes the session audio.
HRESULT CAudioSessionVolume::SetMute(BOOL bMute)
{
if (m_pSimpleAudioVolume == NULL)
{
return E_FAIL;
}
else
{
return m_pSimpleAudioVolume->SetMute(
bMute,
&AudioSessionVolumeCtx // Event context.
);
}
}
// Sets the display name for the session audio.
HRESULT CAudioSessionVolume::SetDisplayName(const WCHAR *wszName)
{
if (m_pAudioSession == NULL)
{
return E_FAIL;
}
else
{
return m_pAudioSession->SetDisplayName(wszName, NULL);
}
}
// Called when the session volume level or muting state changes.
// (Implements IAudioSessionEvents::OnSimpleVolumeChanged.)
HRESULT CAudioSessionVolume::OnSimpleVolumeChanged(
float NewVolume,
BOOL NewMute,
LPCGUID EventContext
)
{
// Check if we should post a message to the application.
if ( m_bNotificationsEnabled &&
(*EventContext != AudioSessionVolumeCtx) &&
(m_hwndNotification != NULL)
)
{
// Notifications are enabled, AND
// We did not trigger the event ourselves, AND
// We have a valid window handle.
// Post the message.
::PostMessage(
m_hwndNotification,
m_uNotificationMessage,
*((WPARAM*)(&NewVolume)), // Coerce the float.
(LPARAM)NewMute
);
}
return S_OK;
}
要求
MFPlay 需要 Windows 7。
相关主题