C++ 使用waveOut实现声音播放

article/2025/11/9 17:37:11

文章目录

  • 前言
  • 一、需要的对象及方法
    • 1.对象
    • 2.方法
  • 二、整体流程
  • 三、关键实现
    • 1.声音格式
    • 2.对象池
  • 四、封装成对象
    • 1.接口设计
    • 2.具体实现
  • 五、使用示例
  • 总结


前言

在Windows上实现声音播放比较简单的方法是使用winmm,其中的waveOut模块就可以打开声音设备,播放PCM数据。本文将介绍waveOut声音播放的具体实现,其实现相较于waveIn的采集简单很多,不需要通过开启子线程避免死锁,对于消息也只需要监听WOM_DONE。


一、需要的对象及方法

需要用到的头文件

#include"windows.h"
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib ")  

1.对象

//声音播放对象
HWAVEOUT _waveOut;
//声音数据的缓存
WAVEHDR  _wavehdrs[2];
//声音格式
WAVEFORMATEX _waveFormat;

2.方法

//打开声音播放设备
waveOutOpen
//注册缓冲区
waveOutPrepareHeader
//注销缓冲区
waveOutUnprepareHeader
//缓冲区加入使用
waveOutWrite
//重置数据
waveOutReset
//关闭设备
waveOutClose

二、整体流程

整体流程大致如下:
在这里插入图片描述


三、关键实现

1.声音格式

 WAVEFORMATEX WaveInitFormat(WORD nCh, DWORD  nSampleRate, WORD  bitsPerSample)
{WAVEFORMATEX waveFormat;waveFormat.wFormatTag = WAVE_FORMAT_PCM;waveFormat.nChannels = nCh;waveFormat.nSamplesPerSec = nSampleRate;waveFormat.nAvgBytesPerSec = nSampleRate * nCh * bitsPerSample / 8;waveFormat.nBlockAlign = nCh * bitsPerSample / 8;waveFormat.wBitsPerSample = bitsPerSample;waveFormat.cbSize = 0;return waveFormat;
}

2.对象池

由于写入需要使用多个缓存WAVEHDR,为了不让内存不受控的增长,需要对缓存数量加以限定,这就需要用到对象池的概念了,对象池可以复用固定数量的对象。关于对象池可以参考:《C++ 实现对象池》

(1)初始化
初始化缓存和对象池,如下使用了10个WAVEHDR缓存

ObjectPoolGeneric<WAVEHDR>_opg;
WAVEHDR  _wavehdrs[10];
//构造方法:初始化对象池,使用对象池管理_wavehdrs数组,参数:数组对象,数组长度
SoundPlay() :_opg(_wavehdrs,10){
}

(2)申请缓存
写入时需要申请缓存

void Write(unsigned char* data, int length)
{	//_opg为对象池,Applicate方法在对象池中申请一个对象,当对象池为空时会等待,直到有对象才返回。WAVEHDR* whd = _opg.Applicate(timeoutms);		whd->dwBufferLength = length;memcpy(whd->lpData, data, length);waveOutWrite(_waveOut, whd, sizeof(WAVEHDR));	
}

(3)归还缓存
播放完成时归还缓存

static	void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{switch (uMsg){case WOM_OPEN:break;case WOM_DONE:{WAVEHDR* whd = (WAVEHDR*)dwParam1;//将对象归还给对象池		_this->_opg.ReturnBack(whd);}break;case WOM_CLOSE:break;}
}

四、封装成对象

将采集功能封装成一个通用工具,方便在任意地方使用。

1.接口设计

接口设计如下:

#pragma once
#include<string>
#include<functional>
#include<vector>
/************************************************************************
* @Project:  	AC::SoundPlay
* @Decription:  音频播放工具
* @Verision:  	v1.0.0.0
* @Author:  	Xin Nie
* @Create:  	2022/1/8 13:05:00
* @LastUpdate:  2022/1/13 17:02:00
************************************************************************
* Copyright @ 2023. All rights reserved.
************************************************************************/
namespace AC {/// <summary>/// 声音格式/// </summary>class SoundFormat {public:/// <summary>/// 声道数/// </summary>int  Channels;/// <summary>/// 采样率/// </summary>int  SampleRate;/// <summary>/// 位深/// </summary>int  BitsPerSample;};/// <summary>/// 声音采集设备/// </summary>class  SoundDevice {public:/// <summary>/// 设备Id/// </summary>int Id;/// <summary>/// 设备名称/// </summary>std::string Name;/// <summary>/// 声道数/// </summary>int Channels;/// <summary>/// 支持的格式/// </summary>std::vector<SoundFormat> SupportedFormats;};/// <summary>/// 声音播放对象/// </summary>class  SoundPlay{public:/// <summary>/// 错误事件参数参数/// </summary>class  ErrorEventArgs{public:/// <summary>/// 错误内容/// </summary>std::string Message;};/// <summary>/// 播放开始事件参数参数/// </summary>class  StartedEventArgs {public:/// <summary>/// 播放声音数据的格式/// </summary>SoundFormat Format;};/// <summary>/// 播放数据到达事件参数/// </summary>class  DataArrivedEventArgs :public StartedEventArgs{public:/// <summary>/// 声音数据/// </summary>unsigned char* Data;/// <summary>/// 数据长度/// </summary>int DataLength;};			/// <summary>/// 打开事件/// </summary>std::function<void(void*, StartedEventArgs*)> Opened;/// <summary>/// 播放数据完成事件/// </summary>std::function<void(void*, DataArrivedEventArgs*)> DataDone;/// <summary>/// 关闭事件/// </summary>std::function<void(void*, void*)> Closed;/// <summary>/// 错误事件/// </summary>std::function<void(void*, ErrorEventArgs*)> Error;SoundPlay();SoundPlay(int deviceId);~SoundPlay();/// <summary>/// 打开播放设备/// </summary>/// <param name="channels">声道数</param>/// <param name="sampleRate">采样率</param>/// <param name="bitsPerSample">位深</param>bool Open(int channels, int  sampleRate, int  bitsPerSample);/// <summary>/// 关闭/// 不可以在DataDone事件中调用/// </summary>void Close();/// <summary>/// 写入数据/// 如果缓冲区满了则会等待,超时会返回false/// 不可以在DataDone事件中调用/// </summary>/// <param name="data">声音数据</param>/// <param name="length">数据长度</param>/// <param name="timeoutms">超时时间,-1为永不超时</param>/// <returns>是否写入成功</returns>bool Write(unsigned char*data,int length,int timeoutms=30000);/// <summary>/// 获取声道数/// </summary>/// <returns>声道数</returns>int GetChannels();/// <summary>/// 获取采样率/// </summary>/// <returns>采样率,单位:hz</returns>int GetSampleRate();/// <summary>/// 获取位深/// </summary>/// <returns>位深,单位:bits</returns>int GetBitsPerSample();/// <summary>/// 获取设备是否已开启/// </summary>/// <returns>是否已开启</returns>bool GetIsOpened();/// <summary>/// 获取当前设备Id/// </summary>/// <returns>设备Id</returns>int GetDeviceId();/// <summary>/// 获取声音设备列表/// </summary>/// <returns>设备列表</returns>static std::vector<SoundDevice> GetDeives();private:void* _implement;};
}

2.具体实现

下面连接的资源包含了上述接口的具体实现,及测试程序和使用示例。
https://download.csdn.net/download/u013113678/75702230


五、使用示例

播放wav文件,其中的WavFileReader 对象参考《C++ 读取wav文件中的PCM数据》

#include"WavFileReader.h"
#include"SoundPlay.h"
int main(int argc, char** argv) {AC::SoundPlay sp;AC::WavFileReader read;unsigned char buf[1024];//打开wav文件if (read.OpenWavFile("test_music.wav")){//注册事件sp.Opened = [&](auto s, auto e) {printf("打开设备:Channels %d SampleRate %d BitsPerSample %d\n", e->Format.Channels, e->Format.SampleRate, e->Format.BitsPerSample);};sp.DataDone = [&](auto s, auto e) {printf("%p数据播放完成:长度%d\n",e->Data,e->DataLength);};sp.Closed = [&](auto s, auto e) {printf("关闭播放\n");};sp.Error = [&](auto s, auto e) {printf("%s\n",e->Message.c_str());};//打开设备sp.Open(read.GetChannels(), read.GetSampleRate(), read.GetBitsPerSample());int size;do{//读取音频数据size = read.ReadData(buf, 1024);if (size > 0){//写入播放设备sp.Write(buf, size);}} while (size);	}return 0;
}

总结

以上就是今天要讲的内容,使用waveOut实现声音播放,实现过程还是相对较简单的,但还是有些细节需要注意,比如使用对象池管理缓存。waveOut出现死锁的情况较少,所及基本不用特殊实现处理,只需要确保避免一些调用方式即可。总得来说,用waveOut使用的播放功能还是可以使用的,对于一般的音频文件的播放是满足的,对于实时流则有待验证。


http://chatgpt.dhexx.cn/article/VObLF8FM.shtml

相关文章

windows下使用waveOut播放音频pcm

目录 一、前言 二、waveOut主要API介绍 三、waveOut播放PCM音频框架 《Windows平台使用waveOut播放PCM数据代码实现》链接&#xff1a; https://edu.csdn.net/learn/38258/606147?spm1003.2001.3001.4157 一、前言 在<windows下directShow音视频采集>一篇文章中就介…

双缓冲方法实现waveOut的连续无卡顿播放以及即收即放

1. waveOut基本使用方法 waveOut是一套历史悠久的Windows音频API&#xff0c;虽然古老&#xff0c;但至今仍运行良好&#xff0c;且支持老旧系统&#xff08;原生支持Windows XP&#xff09;。 waveOut虽然不像DirectSound那样自带混音功能&#xff0c;但也可以通过同时开多个…

清华牛逼!

如果让你用尽可能多的词语形容一名女子的美貌&#xff0c;你会怎么说&#xff1f; “这女孩太美了&#xff0c;闭月羞花、沉鱼落雁不足以形容起美貌。那身段令貂婵蒙羞&#xff0c;那脸蛋令月亮失色。那眼睛&#xff0c;那鼻子&#xff0c;那小嘴&#xff0c;那胸脯&#xff0c…

博士申请 | 上海交通大学叶南阳老师招收计算机视觉/机器学习方向博士生

合适的工作难找&#xff1f;最新的招聘信息也不知道&#xff1f; AI 求职为大家精选人工智能领域最新鲜的招聘信息&#xff0c;助你先人一步投递&#xff0c;快人一步入职&#xff01; 上海交通大学 上海交通大学约翰霍普克罗夫特计算机科学中心于 2017 年 1 月正式成立&#x…

硬核实力!西安交大人工智能学科到底有多强?

人工智能 是时下最火的科技词汇之一 人工智能 作为引领未来的战略性技术 正默默改变着人类的生产生活 西安交大人工智能研究是国内高校的先驱 拥有强大的学术实力 科研成绩斐然 斩获国内外多项荣誉 今天 跟着小编一起走进交大人工智能 漫步科学殿堂 西安交通大学人工智能学科发…

清华大学交叉信息研究院姚班修改培养模式:姚班、智班、量信班全合并

机器之心报道 编辑&#xff1a;杜伟、泽南 拓宽优秀学生的选择方向。 清华大学交叉信息研究院宣布了优化本科培养模式的新计划&#xff1a; 为进一步拓宽学生基础学养&#xff0c;并优化专业方向选择&#xff0c;特调整本科生培养模式&#xff0c;具体要点如下&#xff1a; 1、…

我的2020推免之路:清叉、TBSI、贵系、中山、国防科大、自动化所

我的2020推免之路&#xff1a;清叉、TBSI、贵系、中山、国防科大、自动化所 一.前言二.个人情况三.夏令营清华大学交叉信息学院&#xff08;6.13~6.14&#xff09;清华-伯克利深圳学院&#xff08;7.2~7.9&#xff09;清华大学计算机系&#xff08;7.8~7.9&#xff09;中山大学…

清北浙交大比拼,南大强势上榜,AI到底哪家强?

作者 | 阿司匹林 编者注&#xff1a;近日&#xff0c;上海交通大学和武书连编制的两份比较有影响力的大学排行榜先后发布。这两份榜单的最终排名虽然存在一定的差异&#xff0c;但是都将清华大学、北京大学、浙江大学、上海交通大学、以及复旦大学排在了前五&#xff08;先后顺…

2022 ACM杰出会员揭榜:清华黄隆波、刘世霞,北大郝丹、刘譞哲等23位华人学者入选!...

来源&#xff1a;新智元 2022年度ACM杰出会员&#xff08;Distinguished Member&#xff09;名单公布&#xff01; 本次评选设有三个奖项&#xff0c;分别表彰在计算机领域做出的教育贡献、工程贡献和科学贡献。 今年共有67名会员入选。其中有23位是华人&#xff08;中国大陆6人…

科研实习 | 清华大学交叉信息研究院弋力老师课题组招收科研实习生

合适的工作难找&#xff1f;最新的招聘信息也不知道&#xff1f; AI 求职为大家精选人工智能领域最新鲜的招聘信息&#xff0c;助你先人一步投递&#xff0c;快人一步入职&#xff01; 清华大学 弋力博士于2021年7月加入清华大学交叉信息研究院任助理教授&#xff0c;博士生导师…

数说CS | 北大叉院推免生源大起底!

写在前面 北京大学前沿交叉学科研究院是怎样的一个存在&#xff1f;研究方向与其他学院有什么不同&#xff1f;学术氛围如何&#xff1f; 今天&#xff0c;岛主将会带领各位一同了解北大叉院&#xff0c;从学院介绍、培养特色、优营生源分析等板块为大家深入剖析&#xff01;…

北理工通报方岱宁院士处理结果

点击进入—>3D视觉工坊学习交流群 12月7日&#xff0c;北京理工大学发布《情况通报》&#xff0c;通报“教师方岱宁相关视频事件”处理结果。 据通报&#xff0c;经查核&#xff0c;视频内容是方岱宁今年7月初在线参加学术会议时发生的事情&#xff0c;因其行为失范&#xf…

2020cs线上保研之路 清华北大交大复旦自动化所计算所

2020计算机线上保研 清华北大交大复旦自动化所计算所 无聊在飞机上编辑的这几个月来的经历吧。我真的感觉好累&#xff0c;巨累。该死的疫情。 今年保研&#xff0c;往届的学长学姐的去路都没有什么参考性了&#xff0c;一个原因在于cs内卷更严重了&#xff0c;另一个因为疫情…

直播预告 | 清华叉院助理教授赵行、在读博士生孙桥:自动驾驶中的行为交互

本期为TechBeat人工智能社区第421期线上Talk&#xff0c;响应大家希望实时交流的需求&#xff0c;本次Talk是&#xff01;直&#xff01;播&#xff01; 北京时间7月13日(周三)20:00&#xff0c;我们很开心邀请到老朋友——清华叉院助理教授赵行老师和他的学生孙桥&#xff0c;…

清华大学交叉信息研究院招收计算机图形学、机器学习方向博士

来源&#xff1a;机器之心 课题组简介杜韬博士于 2022 年秋季加入清华大学交叉信息研究院担任助理教授&#xff0c;领导课题组开展计算机图形学和机器学习相关方向的研究。课题组常年招收博士生、科研助理、实习生、访问学生、助理研究员等&#xff0c;部分职位工作地点可选北京…

生源985占比100%,北大叉院这个专业本校学生也心仪~

1、院校介绍 北京大学前沿交叉学科研究院下的大数据研究中心是中国交叉学科的先锋&#xff0c;主要学科为数据科学&#xff0c;现设数据挖掘方法、大数据软件技术、大数据安全技术、健康医疗大数据以及交通大数据等9个研究方向。 北大叉院大数据研究中心科研实力强大&#xf…

【2019保研经验】清华贵系、清华软院、北大叉院、中科院自动化所等

2019年9月29日23点00分&#xff0c;我收到了清华大学计算机科学与技术系的待录取通知&#xff0c;也意味着我几个月的保研工作彻底结束&#xff0c;并且取得了我所能取得的最好成绩。如今&#xff0c;全部的保研工作已全部结束&#xff0c;我也是时候要摆脱懒惰的借口&#xff…

清华数据女神评选结果:第一竟然是叉院大神...?

在刚刚过去的3月7日女生节&#xff0c;清华学大数据的男生们发起了一个数据女神评选活动&#xff0c;希望在大家的参与下评选出数据女神&#xff0c;送上女生节的礼物。 今天是公布评选结果的时刻&#xff1a; 恭喜来自交叉信息研究院的陈立杰和来自美术学院的张玉萍同学成为我…

清华叉院弋力:从谷歌研究科学家到清华任教,我想看远一点

原来他们是这样走过来的&#xff01; 【AI红人荟】——这里是TechBeat人工智能社区为优秀的AI工作者开设的人物专访栏目。从膜拜“红人”到成为“红人”&#xff0c;TechBeat与你一起&#xff0c;在AI进阶之路上&#xff0c;升级打怪、完美通关~ 本篇人物——来自清华交叉信息研…

青源LIVE第29期|清华叉院高阳:使用1/500数据掌控Atari游戏-EfficientZero算法详解

当前强化学习已在许多应用中取得了巨大成功。但样本效率仍是强化学习中一个重大挑战&#xff0c;重要的方法需要数百万&#xff08;甚至数十亿&#xff09;的环境步骤来训练。虽然&#xff0c;当前在基于图像的样本高效RL算法方面取得了重大进展&#xff1b;但是&#xff0c;在…