65.9K
CodeProject 正在变化。 阅读更多。
Home

基于 DirectX 的强大频谱分析仪

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (19投票s)

2009年11月7日

CPOL

5分钟阅读

viewsIcon

99917

downloadIcon

22900

快速、美观、真实且易于连接到您的应用程序

The Spectrum Analyzer, displaying spectrum of a QPSK stream

引言

本项目模拟了一个真实的频谱分析仪。任何涉及通信信号的应用程序,例如 FM 接收器,都可以使用此应用程序来监视傅里叶域中的信号频谱。事实上,您的应用程序将使用进程间通信 (IPC) 方法与我的项目进行通信。您需要创建一个具有预定义结构的共享缓冲区。然后,您指定采样率 (Fs)、刷新率 (Rf),最后,您将实时信号的时域样本发送给我。频谱分析仪将使用这些信息来计算信号频谱并实时显示它。

我的频谱分析仪的主要特点是速度快且 CPU 负载低。为了实现这一目标,我利用了 Direct3D 的强大功能。因此,大部分渲染工作都由 GPU 完成。您必须拥有一个好的显卡才能受益。此外,我使用了 Intel® 数学核心库 来执行傅里叶变换和其他繁重的数学运算。如果您有 Intel® 处理器,那就没问题了!更多详情请参阅说明。

该应用程序的其他重要功能是

  • 全屏模式 (Alt + Enter)
  • 放大和缩小
  • 原地垂直标尺
  • 视图移动(鼠标左键),缩放后可用
  • 峰值保持

背景

在继续之前,请下载并运行演示应用程序。这样,您将对频谱分析仪的工作原理有一个基本的了解。演示包包含两个项目:《频谱分析仪》和《信号生成》。后者项目生成具有 1 KHz 采样率的 QPSK 频道的时域样本。然后,它将执行前者项目,即频谱分析仪。《信号生成》会不断地将样本数据存储在共享缓冲区中,《频谱分析仪》则读取共享缓冲区来计算和显示信号的频谱。

Zooming in

我使用了一种有趣的算法来处理缩放、移动和其他类似操作。正如您可能从 Direct3D 中所知,您必须指定三个矩阵来描述场景的几何形状

  • 世界变换:定义对象在世界中的位置
  • 视图变换:告诉相机在世界中的位置和方向
  • 投影变换:定义相机的视口(例如,其水平和垂直视角)

为了处理放大和缩小操作,我适当地改变了投影矩阵。例如,当您水平放大时,我减小了相机的水平视口角度。为了处理移动,我适当地改变了视图矩阵。例如,当您将场景向右移动时,我改变了视口矩阵,就像相机向左移动一样。

Using the Code

在接下来的解释中,您会遇到几个带有括号的数字。这些标记指的是源代码中的某个位置。例如,{1} 表示您可以在下载的源代码中搜索 /*{1}*/,并在那里找到相关的代码。

如前所述,您的应用程序将通过共享缓冲区与我通信。要获取此缓冲区的定义并使用以下函数,您必须将《频谱分析仪》项目的两个头文件包含到您的项目中:Shared Buffer.hIPC.h {7}。下面是 Shared Buffer.h 中定义的共享缓冲区的结构

#define BULK_SIZE    1000    // Buffer bulk size
#define FFT_LENGTH    512    // FFT length

struct SHARED_BUFFER
{
    // Time-domain samples (float complex)
    Complex8    Sample[2 * BULK_SIZE];

    float    Fs;    // Sample rate [Hz]
    float    Rf;    // Refresh rate [Hz]
    int    nAverage;    // Number of frames to include in averaging

    HWND    hWnd;    // Oscilloscope Window
};

此结构的一个全局实例在 IPC.h 中创建。

SHARED_BUFFER *pSharedBuffer = NULL;

您的应用程序必须通过调用 CreateMapFile() {1} 来创建共享缓冲区。然后,您初始化采样率 pSharedBuffer->Fs、刷新率 pSharedBuffer->Rf 和用于平均的帧数 pSharedBuffer->nAverage {2}。请注意,pSharedBuffer->nAverage 不应超过 20。

if(!CreateMapFile())
    goto err;    // Fatal error
pSharedBuffer->nAverage = 5;
pSharedBuffer->Fs = BULK_SIZE;
pSharedBuffer->Rf = 25;

然后,您必须执行频谱分析仪。请务必指定可执行文件的正确路径 {6}。

STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
CreateProcess("Spectrum Analyzer.exe", NULL, NULL, NULL, TRUE, 
              NULL, NULL, NULL, &si, &pi);

之后,您需要同步采样率持续将采样数据填充到 pSharedBuffer->Sample 中 {3},{4}。pSharedBuffer->Sample 是一个循环缓冲区;也就是说,当您到达其末尾时,必须从其开头继续写入。在演示应用程序中,TimeProc 负责将样本数据写入共享缓冲区。它由多媒体计时器每秒调用一次,写入 1000 个新数据(等于采样率){3},{4}。

// A global variable filled with QPSK samples
Complex8 Signal[SIGNAL_LENGTH];

void CALLBACK TimeProc(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
    static int j = 0;
    static int k = 0;

    i = min(BULK_SIZE, 2 * BULK_SIZE - k);

    // fill pSharedBuffer->Sample with samples
    CopyMemory(pSharedBuffer->Sample + k, Signal + j, i * sizeof(Complex8));

    // Wrap to the beginning of pSharedBuffer->Sample 
    // and store remaining samples
    CopyMemory(pSharedBuffer->Sample, Signal + j + i, 
              (BULK_SIZE - i) * sizeof(Complex8));

    j = (j + BULK_SIZE) % SIGNAL_LENGTH;
    k = (k + BULK_SIZE) % (2 * BULK_SIZE);
}

当您完成频谱分析仪的工作后,可以通过向其窗口发送 WM_QUIT 消息并随后调用 CloseMapFile() {5} 来销毁共享缓冲区来轻松关闭它。

PostMessage(pSharedBuffer->hWnd, WM_QUIT, 0, 0);
CloseMapFile();

备注

  1. 如果您想自定义或编译代码,您必须安装 Direct3D 9.0 SDK 和 Intel® 数学核心库,并将其与 Visual Studio 集成。否则,拥有可执行的频谱分析仪及其库(包含在我的演示应用程序中)就足够了。
  2. 我在平均方面遇到了问题。它不起作用。当我增加 pSharedBuffer->nAverage 时,频谱曲线并没有变得平滑。我认为平均不仅仅是n个连续帧的总和除以n。您有什么想法吗?
  3. 我无意与 Rohde-Schwarz® 这样的真实频谱分析仪竞争!但是它们有许多有趣的功能也可以添加到此项目中。其中可以列举视频带宽、频率变化和屏幕分辨率。如果您对实现这些功能有任何想法,请告知我。您可以修改代码并通知我,以便我们能够合作开发这个免费项目。我期待您的回复。

历史

  • 2009 年 11 月 8 日:发布第一个版本。等待反馈。
© . All rights reserved.