DirectSound如何实现C语言音频播放控制?

99ANYc3cd6
预计阅读时长 35 分钟
位置: 首页 C语言 正文

DirectSound 是微软 DirectX API 的一部分,主要用于在 Windows 平台上进行低级别的音频播放、混音和捕获,它提供了比标准 Windows API(如 PlaySoundwaveOut* 函数)更强大的功能,

  • 硬件加速:利用声卡的硬件混音器,可以同时播放多个声音而不会大量消耗 CPU。
  • 精确控制:可以精确控制音量、声相(左右声道平衡)、频率等。
  • 3D 音效:支持 3D 空间音效,让声音听起来有位置和距离感。
  • 音频流:可以播放从文件或网络中实时读取的音频流。

核心概念

在开始编码之前,需要理解几个 DirectSound 的核心对象和概念:

  1. IDirectSound:这是 DirectSound 的主接口对象,在初始化时创建,代表与音频设备的连接,所有其他操作都通过这个对象或其派生对象进行。
  2. IDirectSoundBuffer:这是主缓冲区,它代表声卡上的最终混音输出,你通常不需要直接操作它,但需要设置它的格式(如采样率、位深度、声道数),以确保所有次级缓冲区的声音都能被正确混音。
  3. IDirectSoundBuffer8:这是 IDirectSoundBuffer 的扩展接口,增加了更多功能(如设置音量、频率的 SetVolumeSetFrequency 方法),现代编程中,我们总是直接请求这个接口。
  4. IDirectSoundSecondaryBuffer / IDirectSoundSecondaryBuffer8:这是次级缓冲区,这是你实际用来加载和播放单个声音文件(如 WAV)的对象,你可以创建多个次级缓冲区,DirectSound 会在后台将它们混合到主缓冲区中,然后输出到扬声器,这是最常用的缓冲区类型。

编程步骤

使用 DirectSound 的典型流程如下:

  1. 初始化 COM 库:DirectSound 基于 COM (Component Object Model),所以第一步是调用 CoInitialize
  2. 创建 IDirectSound 对象:通过 CoCreateInstance 创建 DirectSound 对象。
  3. 设置协作级别:告诉 DirectSound 你的应用程序如何与其他应用程序共享音频设备。DSSCL_PRIORITY 是一个常用的级别,它允许你创建主缓冲区并设置其格式。
  4. 创建并设置主缓冲区:创建一个主缓冲区,并设置其音频格式(通常是 44.1kHz, 16-bit, Stereo)。
  5. 加载音频数据:从文件(通常是 WAV)中读取音频数据到内存。
  6. 创建次级缓冲区:使用加载的音频数据创建一个次级缓冲区。
  7. 播放声音:在次级缓冲区上调用 Play 方法。
  8. 清理资源:使用完毕后,释放所有 COM 对件,并调用 CoUninitialize

详细代码示例

下面是一个完整的 C 语言示例,演示如何播放一个简单的 WAV 文件,这个例子会创建一个控制台应用程序。

环境准备

你需要安装 Windows SDK,它包含了 DirectSound 的头文件 (dsound.h) 和库文件 (dsound.lib, dxguid.lib)。

在 Visual Studio 中,创建一个新的 "Windows Desktop Application" 项目会自动配置好这些链接,如果你使用 MinGW 或其他编译器,需要手动链接这些库。

代码

#define COBJMACROS // 允许直接使用 COM 对象的指针调用方法,如 pDS->Release()
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>   // 用于 waveOut 函数和 WAVEFORMATEX
#include <dsound.h>     // DirectSound 头文件
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "winmm.lib")
// 全局变量
LPDIRECTSOUND8       g_pDS = NULL;         // DirectSound 对象
LPDIRECTSOUNDBUFFER8 g_pDSBPrimary = NULL; // 主缓冲区
LPDIRECTSOUNDBUFFER8 g_pDSBSecondary = NULL; // 次级缓冲区(用于播放声音)
// 函数声明
HRESULT InitDirectSound(HWND hWnd);
HRESULT LoadAndPlayWAV(LPCSTR szFileName);
void    Cleanup();
// 简单的 WAV 文件头结构 (仅用于演示,不完整)
typedef struct {
    char           RIFF[4];
    DWORD          dwSize;
    char           WAVE[4];
    char           fmt[4];
    DWORD          dwFmtSize;
    WAVEFORMATEX   wfex;
    char           data[4];
    DWORD          dwDataSize;
} WAVEHDR_EX;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // 初始化 COM
    if (FAILED(CoInitialize(NULL))) {
        printf("CoInitialize failed!\n");
        return 1;
    }
    // 获取窗口句柄 (控制台窗口)
    HWND hWnd = GetConsoleWindow();
    if (!hWnd) {
        printf("GetConsoleWindow failed!\n");
        CoUninitialize();
        return 1;
    }
    // 初始化 DirectSound
    if (FAILED(InitDirectSound(hWnd))) {
        printf("InitDirectSound failed!\n");
        CoUninitialize();
        return 1;
    }
    // 加载并播放 WAV 文件
    // 请确保你的项目目录下有一个名为 "test.wav" 的文件
    // 或者修改 szFileName 为你的文件路径
    if (FAILED(LoadAndPlayWAV("test.wav"))) {
        printf("LoadAndPlayWAV failed!\n");
        Cleanup();
        CoUninitialize();
        return 1;
    }
    printf("Sound playback started. Press any key to exit...\n");
    getchar(); // 等待用户按键
    Cleanup();
    CoUninitialize();
    return 0;
}
// 初始化 DirectSound
HRESULT InitDirectSound(HWND hWnd) {
    HRESULT hr;
    // 1. 创建 DirectSound 对象
    hr = CoCreateInstance(&CLSID_DirectSound8, NULL, CLSCTX_ALL, &IID_IDirectSound8, (LPVOID*)&g_pDS);
    if (FAILED(hr)) {
        printf("CoCreateInstance for DirectSound8 failed (0x%x)\n", hr);
        return hr;
    }
    // 2. 设置协作级别
    hr = IDirectSound8_SetCooperativeLevel(g_pDS, hWnd, DSSCL_PRIORITY);
    if (FAILED(hr)) {
        printf("SetCooperativeLevel failed (0x%x)\n", hr);
        return hr;
    }
    // 3. 创建主缓冲区
    DSBUFFERDESC dsbd;
    ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; // 标记为主缓冲区
    dsbd.dwBufferBytes = 0; // 主缓冲区大小由驱动决定
    dsbd.lpwfxFormat = NULL; // 主缓冲区格式单独设置
    hr = IDirectSound8_CreateSoundBuffer(g_pDS, &dsbd, &g_pDSBPrimary, NULL);
    if (FAILED(hr)) {
        printf("CreateSoundBuffer for primary buffer failed (0x%x)\n", hr);
        return hr;
    }
    // 4. 设置主缓冲区的格式
    WAVEFORMATEX wfex;
    wfex.wFormatTag = WAVE_FORMAT_PCM;
    wfex.nChannels = 2; // 立体声
    wfex.nSamplesPerSec = 44100; // 44.1 kHz
    wfex.wBitsPerSample = 16; // 16-bit
    wfex.nBlockAlign = (wfex.nChannels * wfex.wBitsPerSample) / 8;
    wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;
    wfex.cbSize = 0;
    hr = IDirectSoundBuffer8_SetFormat(g_pDSBPrimary, &wfex);
    if (FAILED(hr)) {
        printf("SetFormat for primary buffer failed (0x%x)\n", hr);
        // 注意:即使失败,程序也可能继续运行,使用系统默认格式
    }
    printf("DirectSound initialized successfully.\n");
    return S_OK;
}
// 加载 WAV 文件并创建次级缓冲区进行播放
HRESULT LoadAndPlayWAV(LPCSTR szFileName) {
    HRESULT hr;
    HANDLE hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("Failed to open WAV file: %s\n", szFileName);
        return E_FAIL;
    }
    DWORD dwFileSize = GetFileSize(hFile, NULL);
    if (dwFileSize == 0) {
        CloseHandle(hFile);
        printf("WAV file is empty.\n");
        return E_FAIL;
    }
    // 读取整个文件到内存
    LPVOID pvFileData = LocalAlloc(LMEM_FIXED, dwFileSize);
    if (!pvFileData) {
        CloseHandle(hFile);
        printf("Failed to allocate memory for WAV file.\n");
        return E_FAIL;
    }
    DWORD dwRead;
    if (!ReadFile(hFile, pvFileData, dwFileSize, &dwRead, NULL) || dwRead != dwFileSize) {
        LocalFree(pvFileData);
        CloseHandle(hFile);
        printf("Failed to read WAV file.\n");
        return E_FAIL;
    }
    CloseHandle(hFile);
    WAVEHDR_EX* pWaveHdr = (WAVEHDR_EX*)pvFileData;
    // 验证 RIFF 和 WAVE 标记
    if (memcmp(pWaveHdr->RIFF, "RIFF", 4) != 0 || memcmp(pWaveHdr->WAVE, "WAVE", 4) != 0) {
        LocalFree(pvFileData);
        printf("Invalid WAV file format.\n");
        return E_FAIL;
    }
    // 5. 创建次级缓冲区
    DSBUFFERDESC dsbd;
    ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; // 常用标志
    dsbd.dwBufferBytes = pWaveHdr->dwDataSize;
    dsbd.lpwfxFormat = &pWaveHdr->wfex; // 使用 WAV 文件中的格式
    hr = IDirectSound8_CreateSoundBuffer(g_pDS, &dsbd, &g_pDSBSecondary, NULL);
    LocalFree(pvFileData); // 创建缓冲区后,可以立即释放文件数据
    if (FAILED(hr)) {
        printf("CreateSoundBuffer for secondary buffer failed (0x%x)\n", hr);
        return hr;
    }
    // 6. 将音频数据写入次级缓冲区
    LPVOID pLock1, pLock2;
    DWORD dwLockSize1, dwLockSize2;
    hr = IDirectSoundBuffer8_Lock(g_pDSBSecondary, 0, pWaveHdr->dwDataSize, &pLock1, &dwLockSize1, &pLock2, &dwLockSize2, 0);
    if (SUCCEEDED(hr)) {
        // 复制数据
        memcpy(pLock1, (char*)pWaveHdr + sizeof(WAVEHDR_EX), dwLockSize1);
        if (pLock2) {
            memcpy(pLock2, (char*)pWaveHdr + sizeof(WAVEHDR_EX) + dwLockSize1, dwLockSize2);
        }
        IDirectSoundBuffer8_Unlock(g_pDSBSecondary, pLock1, dwLockSize1, pLock2, dwLockSize2);
    }
    // 7. 播放声音
    hr = IDirectSoundBuffer8_Play(g_pDSBSecondary, 0, 0, 0); // 循环播放
    if (FAILED(hr)) {
        printf("Play failed (0x%x)\n", hr);
        return hr;
    }
    printf("WAV file '%s' loaded and playing.\n", szFileName);
    return S_OK;
}
// 释放所有资源
void Cleanup() {
    if (g_pDSBSecondary) {
        IDirectSoundBuffer8_Stop(g_pDSBSecondary);
        IDirectSoundBuffer8_Release(g_pDSBSecondary);
        g_pDSBSecondary = NULL;
    }
    if (g_pDSBPrimary) {
        IDirectSoundBuffer8_Release(g_pDSBPrimary);
        g_pDSBPrimary = NULL;
    }
    if (g_pDS) {
        IDirectSound8_Release(g_pDS);
        g_pDS = NULL;
    }
    printf("DirectSound resources cleaned up.\n");
}

如何编译和运行

  1. 准备一个 WAV 文件:将一个名为 test.wav 的文件(使用 Windows 录音机创建的简单声音)放在你的项目 .exe 文件所在的目录下。

  2. 编译

    • Visual Studio: 创建项目,将上述代码粘贴到主 .c 文件中,然后直接编译运行。
    • 命令行 (使用 cl.exe):
      cl /EHsc dsound_example.c /link dsound.lib dxguid.lib winmm.lib
    • MinGW (gcc):
      gcc -o dsound_example.exe dsound_example.c -ldsound -ldxguid -lwinmm
  3. 运行:编译成功后,运行生成的可执行文件,你应该会看到控制台输出,并且声音会开始播放,按任意键退出程序。


现代替代方案

虽然 DirectSound 非常经典,但对于新项目,微软推荐使用 XAudio2,XAudio2 是 DirectSound 的继任者,功能更强大、性能更高,并且设计更现代化。

XAudio2 的优势:

  • 更清晰的架构:使用“源声部”(Source Voice)和“子声部”(Submix Voice)的概念,比 DirectSound 的缓冲区模型更灵活。
  • 更好的性能:针对现代 CPU 和 GPU 架构进行了优化。
  • 更强大的效果链:可以轻松地构建音频效果处理链(如 EQ、混响等)。
  • 支持 XAPO:一个插件系统,可以自定义音频处理效果。

对于初学者来说,DirectSound 的概念相对简单,是学习 Windows 音频编程的一个很好的起点,但如果你打算开发新的商业游戏或应用程序,强烈建议直接学习 XAudio2。

-- 展开阅读全文 --
头像
DedeCMS文档复制发布如何操作?
« 上一篇 01-03
织梦dedecms 5.7如何升级或优化?
下一篇 » 01-03

相关文章

取消
微信二维码
支付宝二维码

目录[+]