峰值计量

为了支持显示峰值计量的 Windows 应用程序,EndpointVolume API 包含 IAudioMeterInformation 接口。 此接口表示音频终结点设备上的峰值计量。 对于呈现设备,从峰值计量中检索到的值表示在上一个计量期间在设备输出流中出现的最大样本值。 对于捕获设备,从峰值计量中检索到的值表示设备输入流中出现的最大样本值。

IAudioMeterInformation 接口中的方法获取的峰值计值是归一化范围从 0.0 到 1.0 的浮点数。 例如,如果 PCM 流包含 16 位样本,并且特定计量周期内的峰值样本值为 -8914,则峰值计量记录的绝对值为 8914,IAudioMeterInformation 接口报告的归一化峰值为 8914/32768 = 0.272。

如果音频终结点设备在硬件中实现峰值计量,则 IAudioMeterInformation 接口使用硬件峰值计量。 否则,该接口在软件中实现峰值计量。

如果设备具有硬件峰值计量,则峰值计量在共享模式和独占模式下均处于活动状态。 如果设备缺少硬件峰值计量,则峰值计量在共享模式下处于活动状态,但在独占模式下不处于活动状态。 在独占模式下,应用程序和音频硬件直接交换音频数据,绕过软件峰值计量(始终报告峰值为 0.0)。

以下 C++ 代码示例是显示默认呈现设备的峰值计量的 Windows 应用程序:

// Peakmeter.cpp -- WinMain and dialog box functions

#include <windows.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include "resource.h"

static BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
static void DrawPeakMeter(HWND, float);

// Timer ID and period (in milliseconds)
#define ID_TIMER  1
#define TIMER_PERIOD  125

#define EXIT_ON_ERROR(hr)  \
              if (FAILED(hr)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

//-----------------------------------------------------------
// WinMain -- Opens a dialog box that contains a peak meter.
//   The peak meter displays the peak sample value that plays
//   through the default rendering device.
//-----------------------------------------------------------
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
    HRESULT hr;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioMeterInformation *pMeterInfo = NULL;

    if (hPrevInstance)
    {
        return 0;
    }

    CoInitialize(NULL);

    // Get enumerator for audio endpoint devices.
    hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                          NULL, CLSCTX_INPROC_SERVER,
                          __uuidof(IMMDeviceEnumerator),
                          (void**)&pEnumerator);
    EXIT_ON_ERROR(hr)

    // Get peak meter for default audio-rendering device.
    hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
    EXIT_ON_ERROR(hr)

    hr = pDevice->Activate(__uuidof(IAudioMeterInformation),
                           CLSCTX_ALL, NULL, (void**)&pMeterInfo);
    EXIT_ON_ERROR(hr)

    DialogBoxParam(hInstance, L"PEAKMETER", NULL, (DLGPROC)DlgProc, (LPARAM)pMeterInfo);

Exit:
    if (FAILED(hr))
    {
        MessageBox(NULL, TEXT("This program requires Windows Vista."),
                   TEXT("Error termination"), MB_OK);
    }
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pMeterInfo)
    CoUninitialize();
    return 0;
}

//-----------------------------------------------------------
// DlgProc -- Dialog box procedure
//-----------------------------------------------------------

BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    static IAudioMeterInformation *pMeterInfo = NULL;
    static HWND hPeakMeter = NULL;
    static float peak = 0;
    HRESULT hr;

    switch (message)
    {
    case WM_INITDIALOG:
        pMeterInfo = (IAudioMeterInformation*)lParam;
        SetTimer(hDlg, ID_TIMER, TIMER_PERIOD, NULL);
        hPeakMeter = GetDlgItem(hDlg, IDC_PEAK_METER);
        return TRUE;

    case WM_COMMAND:
        switch ((int)LOWORD(wParam))
        {
        case IDCANCEL:
            KillTimer(hDlg, ID_TIMER);
            EndDialog(hDlg, TRUE);
            return TRUE;
        }
        break;

    case WM_TIMER:
        switch ((int)wParam)
        {
        case ID_TIMER:
            // Update the peak meter in the dialog box.
            hr = pMeterInfo->GetPeakValue(&peak);
            if (FAILED(hr))
            {
                MessageBox(hDlg, TEXT("The program will exit."),
                           TEXT("Fatal error"), MB_OK);
                KillTimer(hDlg, ID_TIMER);
                EndDialog(hDlg, TRUE);
                return TRUE;
            }
            DrawPeakMeter(hPeakMeter, peak);
            return TRUE;
        }
        break;

    case WM_PAINT:
        // Redraw the peak meter in the dialog box.
        ValidateRect(hPeakMeter, NULL);
        DrawPeakMeter(hPeakMeter, peak);
        break;
    }
    return FALSE;
}

//-----------------------------------------------------------
// DrawPeakMeter -- Draws the peak meter in the dialog box.
//-----------------------------------------------------------

void DrawPeakMeter(HWND hPeakMeter, float peak)
{
    HDC hdc;
    RECT rect;

    GetClientRect(hPeakMeter, &rect);
    hdc = GetDC(hPeakMeter);
    FillRect(hdc, &rect, (HBRUSH)(COLOR_3DSHADOW+1));
    rect.left++;
    rect.top++;
    rect.right = rect.left +
                 max(0, (LONG)(peak*(rect.right-rect.left)-1.5));
    rect.bottom--;
    FillRect(hdc, &rect, (HBRUSH)(COLOR_3DHIGHLIGHT+1));
    ReleaseDC(hPeakMeter, hdc);
}

在上述代码示例中,WinMain 函数会调用 CoCreateInstance 函数来创建 IMMDeviceEnumerator 接口的实例,并会调用 IMMDeviceEnumerator::GetDefaultAudioEndpoint 方法来获取默认呈现设备的 IMMDevice 接口。 WinMain 调用 IMMDevice::Activate 方法以获取设备的 IAudioMeterInformation 接口,并打开一个对话框以显示设备的峰值计量。 有关 WinMainCoCreateInstance 的详细信息,请参阅 Windows SDK 文档。 有关 IMMDeviceEnumeratorIMMDevice 的详细信息,请参阅枚举音频设备

在前面的代码示例中,DlgProc 函数在对话框中显示峰值计量。 在处理 WM_INITDIALOG 消息期间,DlgProc 调用 SetTimer 函数以设置计时器,该计时器将定期生成 WM_TIMER 消息。 DlgProc 收到 WM_TIMER 消息时会调用 IAudioMeterInformation::GetPeakValue 以获取流的最新峰值计量读数。 然后,DlgProc 调用 DrawPeakMeter 函数在对话框中绘制更新的峰值计量。 有关 SetTimer 以及 WM_INITDIALOG 和 WM_TIMER 消息的详细信息,请参阅 Windows SDK 文档。

可以轻松修改前面的代码示例,以显示默认捕获设备的峰值计量。 在 WinMain 函数中,会将 IMMDeviceEnumerator::GetDefaultAudioEndpoint 调用中的第一个参数值从 eRender 更改为 eCapture。

下面的代码示例是一个资源脚本,用于定义前面的代码示例中出现的控件:

// Peakmeter.rc -- Resource script

#include "resource.h"
#include "windows.h"

//
// Dialog
//
PEAKMETER DIALOGEX 0, 0, 150, 34
STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT
CAPTION "Peak Meter"
FONT 8, "Arial Rounded MT Bold", 400, 0, 0x0
BEGIN
    CTEXT      "",IDC_PEAK_METER,34,14,82,5
    LTEXT      "Min",IDC_STATIC_MINVOL,10,12,20,12
    RTEXT      "Max",IDC_STATIC_MAXVOL,120,12,20,12
END

下面的代码示例是资源头文件,它定义了前面代码示例中出现的控件标识符:

// Resource.h -- Control identifiers

#define IDC_STATIC_MINVOL      1001
#define IDC_STATIC_MAXVOL      1002
#define IDC_PEAK_METER         1003

音量控件