ZBLOG

MFC点击按钮WASAPI多个音多线程精确同时播放10个PCM音频数据代码实现

以下是一个简单的示例代码,演示了如何使用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助手提供,问题来源于学员提问

本站部分文章来源于网络,版权归原作者所有,如有侵权请联系站长删除。
转载请注明出处:https://golang.0voice.com/?id=12677

分享:
扫描分享到社交APP
上一篇
下一篇
发表列表
游客 游客
此处应有掌声~
评论列表

还没有评论,快来说点什么吧~

联系我们

在线咨询: 点击这里给我发消息

微信号:3007537140

上班时间: 10:30-22:30

关注我们
x

注册

已经有帐号?