以下是一个简单的示例代码,演示了如何使用MFC框架、WASAPI接口和多线程来实现点击按钮时精确同时播放10个PCM音频数据。
首先,在你的 MFC 应用程序中创建一个对话框,并添加一个按钮控件(ID 为 IDC_PLAY_BTN)。
然后,打开对话框类的头文件(例如 MyDialog.h),添加以下代码:
#include <vector>
#include <thread>
#include <audioclient.h>
// 定义常量
constexpr int NUM_AUDIO_STREAMS = 10; // 音频流数量
constexpr int BUFFER_SIZE = 44100; // 缓冲区大小
class MyDialog : public CDialogEx {
public:
MyDialog(CWnd* pParent = nullptr);
protected:
HICON m_hIcon;
bool m_isPlaying;
std::vector<std::thread> m_playThreads;
// 控件相关
CButton m_playBtn;
// WASAPI 相关
IMMDevice* m_audioDevice;
IAudioClient* m_audioClient;
WAVEFORMATEX* m_waveFormat;
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
private:
static DWORD WINAPI PlayAudioThread(LPVOID lpParam);
};
在源文件(例如 MyDialog.cpp)中,添加以下代码:
#include "MyDialog.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <mmsystem.h>
#pragma comment(lib, "Winmm.lib")
// 全局变量
extern UINT const MM_WOM_DONE;
MyDialog::MyDialog(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MYDIALOG, pParent), m_hIcon(nullptr),
m_isPlaying(false), m_audioDevice(nullptr),
m_audioClient(nullptr), m_waveFormat(nullptr) {
}
BOOL MyDialog::OnInitDialog() {
// 设置图标
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
// 初始化 WASAPI 接口
CoInitialize(NULL);
IMMDeviceEnumerator* deviceEnumerator;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, IID_PPV_ARGS(&deviceEnumerator));
if (FAILED(hr)) {
MessageBox(_T("Failed to create instance of MMDeviceEnumerator!"),
_T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
// 获取默认音频设备
hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole,
&m_audioDevice);
if (FAILED(hr)) {
MessageBox(_T("Failed to get default audio endpoint!"),
_T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
// 初始化音频客户端
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; // 缓冲区时间长度
hr = m_audioDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
nullptr, reinterpret_cast<void**>(&m_audioClient));
if (FAILED(hr)) {
MessageBox(_T("Failed to activate audio client!"),
_T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
hr = m_audioClient->GetMixFormat(&m_waveFormat);
if (FAILED(hr)) {
MessageBox(_T("Failed to get mix format!"),
_T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
hr = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration, 0, m_waveFormat,
nullptr);
if (FAILED(hr)) {
MessageBox(_T("Failed to initialize audio client!"),
_T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
// 创建播放线程
for (int i = 0; i < NUM_AUDIO_STREAMS; ++i) {
m_playThreads.emplace_back(PlayAudioThread, this);
}
// 关闭设备枚举器
deviceEnumerator->Release();
CDialogEx::OnInitDialog();
return TRUE;
}
void MyDialog::OnPaint() {
if (IsIconic()) {
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
dc.DrawIcon(x, y, m_hIcon);
} else {
CDialogEx::OnPaint();
}
}
HCURSOR MyDialog::OnQueryDragIcon() {
return static_cast<HCURSOR>(m_hIcon);
}
BEGIN_MESSAGE_MAP(MyDialog, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
END_MESSAGE_MAP()
DWORD WINAPI MyDialog::PlayAudioThread(LPVOID lpParam) {
MyDialog* pThis = static_cast<MyDialog*>(lpParam);
if (!pThis->m_audioClient) {
return 0;
}
// 创建音频渲染器
IAudioRenderClient* renderClient;
HRESULT hr = pThis->m_audioClient->GetService(IID_PPV_ARGS(&renderClient));
if (FAILED(hr)) {
MessageBox(_T("Failed to get audio render client!"),
_T("Error"), MB_OK | MB_ICONERROR);
return 0;
}
// 随机生成音频数据(PCM格式,16位采样)
std::srand(static_cast<unsigned int>(std::time(nullptr)));
short buffer[BUFFER_SIZE];
for (int i = 0; i < BUFFER_SIZE; ++i) {
buffer[i] = std::rand() % 65536 - 32768;
}
UINT32 bufferSize = pThis->m_waveFormat->nSamplesPerSec / 10;
BYTE* pData;
renderClient->GetBuffer(bufferSize, &pData);
memcpy(pData, buffer, sizeof(buffer));
hr = renderClient->ReleaseBuffer(bufferSize, AUDCLNT_BUFFERFLAGS_SILENT);
while (pThis->m_isPlaying) {
// 填充音频数据
pData = nullptr;
renderClient->GetBuffer(bufferSize, &pData);
memcpy(pData, buffer, sizeof(buffer));
hr = renderClient->ReleaseBuffer(bufferSize, 0);
if (FAILED(hr)) {
MessageBox(_T("Failed to release audio buffer!"),
_T("Error"), MB_OK | MB_ICONERROR);
break;
}
}
renderClient->Release();
return 0;
}
MyDialog* pDlg;
void CALLBACK PlayThreadCallback(HWAVEOUT hwo, UINT uMsg,
DWORD_PTR dwInstance, DWORD_PTR dwParam1,
DWORD_PTR dwParam2) {
if (uMsg == MM_WOM_DONE) {
for (auto& thread : pDlg->m_playThreads) {
thread.join();
}
pDlg->m_isPlaying = false;
}
}
afx_msg void MyDialog::OnBnClickedPlayBtn() {
if (!m_isPlaying) {
m_isPlaying = true;
for (auto& thread : m_playThreads) {
thread.detach();
}
// 使用默认音频设备播放
HWAVEOUT hWaveOut;
WAVEFORMATEX waveFormat;
memcpy(&waveFormat, m_waveFormat, sizeof(WAVEFORMATEX));
// 创建音频输出设备
waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveFormat,
reinterpret_cast<DWORD_PTR>(PlayThreadCallback),
NULL, CALLBACK_FUNCTION);
pDlg = this;
// 开始播放
waveOutRestart(hWaveOut);
m_playBtn.SetWindowText(_T("Stop")); // 更新按钮文本
} else {
m_isPlaying = false;
m_playBtn.SetWindowText(_T("Play")); // 更新按钮文本
}
}
最后,在对话框的消息映射中添加以下代码:
BEGIN_MESSAGE_MAP(MyDialog, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_PLAY_BTN, &MyDialog::OnBnClickedPlayBtn)
END_MESSAGE_MAP()
这样,当用户点击按钮时,将会同时播放10个PCM音频数据,并在再次点击按钮时停止播放。
请注意,上述代码仅为示例,可能需要根据实际需求进行适当修改和扩展。同时,请确保引入了相应的库文件(如 winmm.lib)以支持音频播放相关函数。
希望这能帮助到你实现多线程精确同时播放多个PCM音频数据的功能!
内容由零声教学AI助手提供,问题来源于学员提问




