设置 WMP 音量级别
Set WMP volume level
我想做的是设置 Windows 媒体播放器的音量级别。默认情况下,音量是 in-/decreased 10%。单击向下或向上菜单项(播放 -> 音量 -> 向上),但在我看来,这还不够好(尤其是在例如边听音乐边与某人进行 Skype 通话时)。
媒体播放器应该保持独立的应用程序。
目前我正在使用一个小工具,通过 SendMessage 向播放器发送应用程序命令,参数如 spy++ 所示。
我想到了三种方法来实现我的目标:
- 使用 WASAPI 获取媒体播放器的音频会话并动态设置音量级别
- 发送鼠标down/up事件到媒体播放器宿主控件的音量滑块
- 通过 IWMPPlayer4 获取媒体播放器控件的实例
- 在 windows 表单主机内的我的 WPF 应用程序中包含一个媒体播放器控件(由于失去独立性而不是首选)
第 2 点看起来相当难看,因为媒体播放器控件是一个 COM 元素并且到目前为止 spy++ 只显示一个句柄,这意味着我必须确定音量滑块的确切位置并非常精确地发送鼠标事件。另外我不知道这是否有效。
第 3 点的前提是可以通过句柄获取 COM 元素的实例。由于我还没有使用过 COM 元素,所以我不知道这是否可行。
更新: 可以使用 IWMPPlayer4
接口获取远程媒体播放器的实例。虽然我必须看看是否可以更改设置。
第 1 点给我的印象是不费吹灰之力就可以实现。
虽然我会面临下一个问题:识别媒体播放器音频会话。使用 IAudioSessionManager2
枚举它们并使用
显示名称
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 + "'");
除了 Mozilla Firefox 和系统声音之外,大多数时候打印一个空字符串(其他进程可能没有自己设置会话名称 => 选择了默认名称并且 GetDisplayName
returns空字符串)。
更新二:
正如 Simon Mourier 指出的那样,可以比较进程 ID 以获得正确的 ISimpleAudioVolume
实例,并且它在 WMP 采纳更改时起作用。上述实例是通过以下方式获取的:
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);
}
当通过 IAudioClient
获取 ISimpleAudioVolume
实例时,必须提供会话 ID 才能将音量变化报告给事件订阅者。使用这种方法是否可行?
虽然我知道向我的应用程序添加媒体播放器控件是最简单的方法,但我希望尽可能不使用此选项。
我不知道在我最初尝试设置媒体播放器的音量级别时会发生什么,但以下代码有效(排除了大多数异常处理):
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);
}
我想做的是设置 Windows 媒体播放器的音量级别。默认情况下,音量是 in-/decreased 10%。单击向下或向上菜单项(播放 -> 音量 -> 向上),但在我看来,这还不够好(尤其是在例如边听音乐边与某人进行 Skype 通话时)。
媒体播放器应该保持独立的应用程序。
目前我正在使用一个小工具,通过 SendMessage 向播放器发送应用程序命令,参数如 spy++ 所示。
我想到了三种方法来实现我的目标:
- 使用 WASAPI 获取媒体播放器的音频会话并动态设置音量级别
- 发送鼠标down/up事件到媒体播放器宿主控件的音量滑块
- 通过 IWMPPlayer4 获取媒体播放器控件的实例
- 在 windows 表单主机内的我的 WPF 应用程序中包含一个媒体播放器控件(由于失去独立性而不是首选)
第 2 点看起来相当难看,因为媒体播放器控件是一个 COM 元素并且到目前为止 spy++ 只显示一个句柄,这意味着我必须确定音量滑块的确切位置并非常精确地发送鼠标事件。另外我不知道这是否有效。
第 3 点的前提是可以通过句柄获取 COM 元素的实例。由于我还没有使用过 COM 元素,所以我不知道这是否可行。
更新: 可以使用 IWMPPlayer4
接口获取远程媒体播放器的实例。虽然我必须看看是否可以更改设置。
第 1 点给我的印象是不费吹灰之力就可以实现。
虽然我会面临下一个问题:识别媒体播放器音频会话。使用 IAudioSessionManager2
枚举它们并使用
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 + "'");
除了 Mozilla Firefox 和系统声音之外,大多数时候打印一个空字符串(其他进程可能没有自己设置会话名称 => 选择了默认名称并且 GetDisplayName
returns空字符串)。
更新二:
正如 Simon Mourier 指出的那样,可以比较进程 ID 以获得正确的 ISimpleAudioVolume
实例,并且它在 WMP 采纳更改时起作用。上述实例是通过以下方式获取的:
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);
}
当通过 IAudioClient
获取 ISimpleAudioVolume
实例时,必须提供会话 ID 才能将音量变化报告给事件订阅者。使用这种方法是否可行?
虽然我知道向我的应用程序添加媒体播放器控件是最简单的方法,但我希望尽可能不使用此选项。
我不知道在我最初尝试设置媒体播放器的音量级别时会发生什么,但以下代码有效(排除了大多数异常处理):
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);
}