Win32下的录音编程

我的工程师爸爸 - 音频应用DIY系列之六:录音棚 文章展示了利用lark来编程解决生活中遇到的音频问题。 lark是一个轻量级但功能强大的软件音频DSP。它提供了一种灵活可扩展的方法来设计高性能、低MCPS、低延时的音频路由,让您可以像搭积木一样构建音频系统。 ......... 阅读详情

引言

在Win32 APIs基础上编写录音程序繁琐易错,使用封装好的类是个不错的注意。不幸的是所谓封装好的类对你而言,往往是代码罗嗦且功能不足,因此尽管你可能希望在某个项目上因使用封装好的类而避开Win32 APIs,可最终你发现你还得面对它。不是为了编写自己的类,就是为了修改别人的代码。

Win32 APIs中有一组被称成多媒体控制接口(即MCI)的函数,该接口提供了多媒体编程所需的系统级APIs。对绝大多数C/C++程序员而言,这些函数也就是Windows多媒体编程的最低层接口。

由于录音代码直接操作真实的录音设备而非单纯的逻辑过程,因此会遇到一些“意外”或与时序有关的困难,从而使编写健壮的代码成了一件困难的事。

录音的目的往往是将声音写入文件保存下来或是通过网络发送,这两类需求对录音代码影响较大。前者无实时性要求,一般也不限制数据量,代码较易编写,而后者既对实时性又要求,又对数据量(也就是音频格式)有要求,且网络系统的稳定性也难比文件系统。期望在本文中试图解决所有的问题是不现实的,诸如如何压缩音频数据以减小网络带宽需求或如何通过网络传输音频数据之类的问题,读者需参阅专业著作,本文只讲述如何获得适宜某类需求的原始音频数据。

本文中的示例代码均是一个名为CWaveRecord的录音类的成员函数,该类是一个网络电话程序的核心类。为减小篇幅或简单明了,作了简化处理等。

2      基本过程

MCI按打开设备、配置设备、实现功能(或曰发送命令)、撤销配置、关闭设备的标准次序组织APIs。对于录音编程而言,其要点在于根据音频格式打开对应的设备、配置录音所需的参数(主要是设置数据区以及根据数据接收方式设置回调函数或消息)、按一定次序发送命令给设备、接收数据并配置参数以继续录音、停止录音释放资源、关闭设备等几个步骤上。所需的函数说明于mmsystem.h,引入库是winmm.lib。

2.1    打开设备

调用waveInOpen()打开录音设备,打开成功后函数返回MMSYSERR_NOERROR,而第一个参数返回设备句柄。

调用该函数时,必须指定设备表示符(可能有多个设备)、音频格式以及返回音频数据的方式。

你可以将设备表示符,即第二个参数设为WAVE_MAPPER。这意味着,你不在意具体使用哪个设备,由系统决定。

第三个参数是个WAVEFORMATEX结构指针,对应的结构指定音频格式。一个音频格式结构的例子:

PCMWAVEFORMAT wf_PCM8S11K =

{

       {

                     WAVE_FORMAT_PCM, // PCM格式

                     1,                                       // 单声道

              11025L,                             // 11.025KHZ

                     11025L,                             // 11025字节/s

                     1                                        // 字节对齐

       },

              8                                                 // 8位采样

};

后面的三个参数用于指定获取音频数据的方式:事件方式、线程方式、窗口方式以及回调函数方式。最后一个参数,即第六个参数指定方式,而第四个参数和第五个参数指定该方式所需的信息。

调用示例:

waveInOpen( &m_hWaveIn, dwDeviceID, m_pwf,

 (DWORD)waveInProc, (DWORD)this, CALLBACK_FUNCTION );

该示例使用回调函数方式获取音频数据。其中dwDeviceID通常即为WAVE_MAPPER,m_pwf指向音频格式结构,缺省值即wf_PCM8S11K的地址,waveInProc是回调函数(内部细节后面论述)。值得注意的是,this指针被设为实例数据,这是一个编程小技巧,方便waveInProc调用处理函数。

2.2    配置设备

按指定音频格式成功打开录音设备之后,需要配置录音设备,也即为音频数据分配数据区。需要考虑的第一个问题是数据块的个数和大小。

系统一般在填满一个数据块后将其返回,这意味着大的数据块导致获取数据的频率降低。如果试图通过网络传输这些数据,这意味着传输次数降低,但每次传输的数据量较大,这将导致音频的实时性降低。另外,在网络传输过程中,数据包(指每次发送的UDP或TCP数据包)的开始一般需设置一个信息头,纪录本数据包所含音频数据的长度、录制起始时间、录制时间、发送时间、识别用的标识符以及其他一些信息,方便接受者处理。如果设置的音频数据块太小,比如100字节,将导致低的网络传输效率。这意味着网络将为小量数据启动传输过程,而这小量数据中又有相当一部分属于附加信息,这是不合算的。

数据块的个数相对来说要好处理的多。较多的数据块只有一个问题,那就是浪费,但这对Windows这样的操作系统而言,浪费几百K的内存根本不是问题。数据块个数不能太少,这是因为在你处理上次返回的数据块时,录音设备正在工作,需要内存。如果在某一段时间内,比如说网络传输变坏,你处理数据的速度也将变慢,而录音设备却如常工作。这导致你无法迅速地返回当前内存供下一次录音使用,因此需要一些备用的内存块以备不时之需。

调用waveInAddBuffer为录音设备配置数据块,而在此之前需调用waveInPrepareHeader准备数据块。

WaveInPrepareHeader的参数有三个,就是录音设备句柄、一个描述数据块的结构WAVEHDR和该结构的大小。

WAVEHDR结构是录音编程的常用数据结构,不同的使用环境设置不同的参数,具体内容参见MSDN。

下面的示例为录音设备添加4个大小为4096字节的数据块:

       for( i=0; i<4; i++ )

       {

              pwh = (WAVEHDR*)malloc( 4096 + sizeof(WAVEHDR) )

              ZeroMemory( pwh, sizeof(WAVEHDR) );

       pwh->dwBufferLength       = 4096;

       pwh->lpData                   = (LPSTR)(pwh + 1);

              waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR) );

              waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );

}

每次添加数据块都需一个WAVEHDR(其他操作也如此),能否让这些数据块共用一个WAVEHDR或使用局部变量以减少内存消耗呢?最好不要这么做,因为你是在与实际设备打交道。很可能当调用返回时,系统内部却还在使用WAVEHDR结构。如果共用或使用局部变量将导致信息混乱或非法内存操作。这一点MSDN没有强调,我花了一星期才得到如此教训。其他录音操作也类似于此,切记!

配置好内存以后就可启动录音了:

waveInStart( hWaveIn );

如果所有操作均正确,很快你将得到第一块音频数据。

2.3    处理音频数据块

获取音频数据块的方式有多种(详见MSDN),处理方式大同小异,本文以回调函数方式为例说明处理过程。如上所述,waveInProc在waveInOpen调用中被设置为回调函数。

waveInProc通过处理WIM_OPEN(打开设备)、WIM_DATA(音频数据)、WIM_CLOSE(设备关闭)消息的方式与系统交互,通常只需处理WIM_DATA消息,其他两条消息可以忽略。

当系统返回一个音频数据块时,系统调用回调函数。此时,回调函数第一个参数被设置为录音设备句柄;第二个参数是消息,即WIM_DATA;第三个参数则是在waveInOpen第五个参数中给出的实例数据;其他参数是与消息有关的参数,参见MSDN。

       处理WIM_DATA的任务由成员函数OnWaveInData完成,因此waveInProc及其简单:

       CWaveRecord *pObject = (CWaveRecord *)dwInstance;

       if( uMsg == WIM_DATA )

              pObject->OnWaveInData( (WAVEHDR*)dwParam1 );

       在OnWaveInData中需要完成两件事:一是保存系统返回的音频数据,二是给录音设备添加数据块以继续录音,否则会因数据块耗尽而停止录音。

在保存系统返回的音频数据块之前,需要调用waveInUnprepareHeader发送一个通知告诉设备驱动程序该数据块已不能用于录音。保存音频数据块是个单纯的数据保存问题,不需多说,只是有三个要点需要特别关注:

一是MSDN中注明在回调函数中严格限制系统调用,因此你不能随心所欲地设计方案,比如说直接将音频数据写入文件是不允许的。通常,你需要设计一个数据块链表结构进行缓冲,这多半涉及多线程编程。特别是如果你使用MFC中的CSocket类,那么必须清楚CSocket依赖一个名为CSocketWnd的内部类,而该类与TLS(线程局部存贮)有关,这意味着CSocket类是不支持多线程共享的。然而,Win32的SOCKET句柄是全局性。因此,获取CSocketWnd类对象的窗口句柄之后,我通过直接调用Win32 APIs使用CSocket::m_hSocket避开这个由MFC封装引起的问题。

二是系统是在另一个线程(不是你自己创建的线程)中调用回调函数,这会给使用TLS机制的程序带来一些微妙的影响。如果你使用MFC,AfxGetApp之类的函数返回值不正确。

三是尽可能地快速。

至于给录音设备添加数据块以继续录音,其步骤与初始化过程一样:

waveInPrepareHeader( hWaveIn, pwh, sizeof(WAVEHDR));

waveInAddBuffer( hWaveIn, pwh, sizeof(WAVEHDR) );

2.4    关闭设备释放资源

简单的调用waveInClose即可关闭设备。不过,在关闭时必须保证所有录音数据块已全部返回,这有点麻烦。我的解决方法是设置一个停止录音标志和一个录音数据块计数。当用户发出停止录音的命令后,该标志置为TRUE,而OnWaveInData(在回调函数中被调用)检查该标志,在标志置位的情况下,OnWaveInData不添加录音数据块。每添加一个录音数据块计数增加,每返回一个则减少(均在OnWaveInData中实现)。执行停止录音命令时,先将停止标志值位,等待计数变为零,然后才调用waveInClose。需要注意的是,计数涉及多线程数据共享,应使用线程互斥机制,在单CPU的机子上使用InterlockedIncrement和InterlockedDecrement是个简单的选择。

3      编程技巧

3.1    简单的声音检测

如果你通过网络发送音频数据,自然需要进行音频处理。专业级的处理难度相当高,如果你是在一个局域网上进行通话,那么无需进行音频压缩也可以将就使用(性能自然不高)。但如果你不说话,程序还不停地发送音频数据(静音或噪音),那就太说不过去了。

你可以定义一个静音区间,一旦音频强度进入该区间就意味着没有声音。统计音频数据块中不是静音采样点的个数,当总数超过预设的限制时,才发送该数据块。

对于8位采样,127是静音点。简单的代码如下:

       for( i=0; i<nMax; i++ )

       {

              uValue = (UCHAR)data[i];

              if( uValue<=125 || uValue>=129 )

                     uHasCnt++;

 

              if( uHasCnt >= uLestNum )

                     return TRUE;

       }

return FALSE;

3.2    创建音频文件

创建任意格式的音频文件在编程上不是一件轻松的事,但对于8位单声道音频数据而言较容易。定义文件信息头结构:

// wav文件头结构, 对齐方式为1字节

typedef struct

{

char       RiffId[4];                                   // "RIFF"

       DWORD       dwFileDataSize;           // file size - 8

       char       WaveId[4];                            // "WAVE"

       char       FMTId[4];                            // "fmt "

       DWORD dwFmtSize;                     // 16

       WORD       wFormatTag;                  // WAVE_FORMAT_PCM

       WORD    wChannels;                     // 1

       DWORD       dwSamplesPerSec;          // 11025

       DWORD       dwAvgBytesPerSec; // 11025

       WORD       wBlockAlign;                  // 1

       WORD       wBitsPerSample;             // 8

       char       DataId[4];                            // "data"

       DWORD       dwDataSize;

} WAVEFILEHDR, *PWAVEFILEHDR;

打开文件时,预留该结构大小的空间:

SetFilePointer(hFile,sizeof(WAVEFILEHDR), NULL, FILE_BEGIN );

然后就如写一般文件一样将接收的音频数据写入文件:

       WriteFile(hFile,pwh->lpData,pwh->dwBytesRecorded,&n, NULL );

       m_dwSizeWrite += dwSize;

最后一行代码是将写入的数据量统计下来用于信息头填写。

在关闭音频文件时填写信息头:

       WAVEFILEHDR wfd =

       {

              'R', 'I', 'F', 'F', 0,

             'W', 'A', 'V', 'E',

             'f', 'm', 't', ' ', 16,

              WAVE_FORMAT_PCM, 1, 11025, 11025, 1, 8,

              'd', 'a', 't', 'a', 0

       };

       wfd.dwFileDataSize = dwBytes + sizeof(WAVEFILEHDR)- 8;

       wfd.dwDataSize             = dwBytes;

       ::SetFilePointer( hFile, 0, NULL, FILE_BEGIN );

       ::WriteFile( hFile, &wfd, sizeof(wfd), &dwSize, NULL );

参考文献

1 Visual C++ 6.0 MSDN

2 Win32程序员开发指南(二)

3 Visual C++ 6.0附带MFC源码

 

 
Windows下纯C写的录音小工具:实时收音+波形图显示(带完整源码和可执行文件) 这个工具用标准C语言和Win32 API开发,能在Windows系统上直接调用声卡进行实时音频采集,并同步画出动态波形图。不需要安装额外驱动、运行库或第三方框架,双击snd_wave.exe就能运行。里面包含全部源码:核心录音逻辑在SND_WAVE.C里,界面资源由SND_WAVE.RC和snd_wave.res定义,头文件snd_wavewiz.h封装了关键结构,MAKEFILE支持命令行编译,还附带一个Linux版本的参考实现(snd_wave_linux.c)。项目曾用LCC编译器构建,代码轻量、逻辑 阅读详情

相关推荐

windows下获取音频信息(C++实现).

网上找的一些代码,用C++实现windows下获取音频信息(音量,是否静音).

WINDOWS下对音频的处理过程

WINDOWS下对音频的处理,大致可分为两部分,即音频的输入、输出,和ACM压缩处理。  一般情况下在WINDOWS下可以调用诸如sndPlaySound等API(MCI)来播放一个WAV文件,但那显然不是我们需要做的。必须能够直接对音频数据流进行处理。在WINDOWS下,也为之提供了一系列的API,以waveIn和waveOut开头的一组API就是干这个的。  先说输入吧。常用的相关AP

alien75的专栏 1456

手机也能跑大模型?QNN框架实战:从模型量化到Android部署全流程

本文详细介绍了如何利用QNN框架将大模型部署到Android手机端进行端侧推理。通过实战演示了从模型量化(如INT4/INT8混合精度)到Android应用集成的全流程,包括内存优化、性能调优及常见问题排查,帮助开发者实现大模型在资源受限设备上的高效本地运行。

joy55的博客 501

Windows上的音频采集技术

    1.Windows上的音频采集技术 转载:http://blog.youkuaiyun.com/wxl1986622/article/details/44230149 谢谢版主   前一段时间接到一个任务,需要采集到声卡的输出信号,以便与麦克风的输入信号进行混音。 之前一直没有研究过音频的相关技术,这次就顺便抽出一点时间去了解了一下Windows上采集音频的相关技术。 对于音频处理的技术...

xiaojun11的专栏 5582

UG NX二次开发(C#)-建模-获取圆柱面信息(轴向、半径、深度)

获取圆柱面的信息前言一、在UG NX中面的类型有哪些?二、获取圆柱面信息的函数1.获取半径和轴向2、计算圆柱面的深度值3、测试用例总结 文章目录前言一、在UG NX中面的类型有哪些?二、获取圆柱面信息的函数1.获取半径和轴向2、计算圆柱面的深度值3、测试用例总结 前言 在一个项目中,需要遍历曲面,获得所有的圆柱面和圆柱面的信息,包括轴向、半径和圆柱面的深度。经过研究,采用uFModl.AskFaceData(face.Tag, out faceTypeInt, point, dir, box, out

GimiGimmy 4145

3D医学影像开发入门<一>:VS2019+DCMTK3.6.8编译及环境配置

3D医学影像开发入门:VS2019+DCMTK3.6.8编译及环境配置

欧特GO 1308

Windows音频编程:Win32 Wave API 的使用

//Windows音频编程:Win32 Wave API 的使用//下面是一个Win32控制台程序的源码,环境VS2008,新建一个空的Win32控制台项目,加入此文件编译即可,功能为录制5秒并回放。注意不要选择Unicode字符集,不然设备名会出现乱码。// // WaveAPI.cpp // // by: 知秋一叶 // // 转载:http://b...

weixin_34183910的博客 321

AE-扭曲-变形-弧

一些AE效果的着色器实现记录

What_can_you_do的博客 1175

Windows音频编程:Win32 Wave API 的使用

[转自:http://blog.youkuaiyun.com/zhi_qiu_yi_ye/article/details/6663366]     使用 Wave API 进行Windows音频编程可以保持很大的自由度,而且与Linux中的OSS编程模式很像。下面我简单介绍函数的调用顺序,具体的用法参见MSDN(VS2008)。     录音:         waveInOpen -> waveI

skywalker_leo的专栏 5313

CADENCE 25.1打开元件库ERRORCannot perform the operation because of an exception.

摘要:解决CADENCE25.1打开元件库报错"ERRORCannotperformtheoperationbecauseofanexception"的方法:该错误通常由软件中文设置导致。解决方案为:进入Options→Preferences→MorePreferences,取消勾选两个本地语言选项后重启软件即可恢复正常。此方法简单有效,能快速解决因语言设置导致的元件库访问异常问题。(98字)

0 1019

CAN(2)-GD32

GD32中CAN的特征。

小柚子的博客 553

C++ MCI录音机开发指南:Windows音频编程入门与实践

音频编程是多媒体开发的基础领域,其核心在于通过软件接口控制硬件设备进行声音的采集、处理和播放。在Windows平台上,MCI(Media Control Interface)接口以其高层次的抽象和命令化的控制方式,显著降低了音频编程的入门门槛,尤其适合快速实现录音、播放等常见功能。其技术价值在于平衡了开发效率与功能需求,通过简单的字符串命令即可完成设备管理、参数设置和文件保存等复杂操作,避免了直接操作底层API(如WASAPI)所需的繁琐步骤。这一特性使其在轻量级工具开发、教学演示和需要快速集成音频功能的桌

weixin_30572613的博客 398

Python爬虫实战:手把手教你如何抓取农作物品种名录,构建标准化种业索引数据库!

我长期专注 Python 爬虫工程化实战,主理专栏 《Python爬虫实战》:从采集策略到反爬对抗,从数据清洗到分布式调度,持续输出可复用的方法论与可落地案例。内容主打一个“能跑、能用、能扩展”,让数据价值真正做到——抓得到、洗得净、用得上。

喵手的博客 291

Skype录音答录机 编程思路阐述和源代码下载

【版   本】1.0.3【操作系统】Windows 桌面操作系统【作   者】谢红伟·chrys ·chrys@163.com ·http://www.viction.net/【开发日期】2006-11-20 03:05【文档日期】2008-06-24 18:33【软件说明】   1、对skype拨入拨出的通话自动录音,并自动保存为mp3文件,录音模

谢红伟的专栏 2265

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Skype录音答录机编程思路和代码

原文地址:http://www.vckbase.com/index.php/wv/1537操  作:下载源文件 文章概要:我常常使用Skype和台湾同胞还有国外的朋友进行联系,有时因为业务需要需要将语音通话录音并保留下来,在我有这个想法的那个时候(2006年)Skype官方并没有提供录音功能,咱们是做程序的嘛,没有的功能可以自己来添加啊,这也是为什么我酷爱编程的原因。 Sk

多媒体编程、网络编程、系统编程、网络安全编程、驱动编程 1157

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

win32 hook 详解

WIN32钩子 摘译自Win32 Hooks 作者:Kyle Marsh         本文描述钩子及其子MS Win32应用编程接口中的使用,讨论钩子函数、 过滤函数、以及以下类型的钩子: WH_CALLWNDPROC WH_CBT WH_DEBUG WH_FOREGROUNDIDLE WH_GETMESSAGE WH_JOURNALPLAYBACK WH_JOURNA

jklfjsdj79hiofo的专栏 4105

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

WIN32钩子

摘译自Win32 Hooks 作者:Kyle Marsh 本文描述钩子及其子MS Win32应用编程接口中的使用,讨论钩子函数、 过滤函数、以及以下类型的钩子: WH_CALLWNDPROC WH_CBT WH_DEBUG WH_FOREGROUNDIDLE WH_GETMESSAGE WH_JOURNALPLAYBA...

weixin_33717298的博客 710

使用树莓派实现翻译机

使用树莓派实现翻译机 只用一根网线实现树莓派和PC的链接 硬件: 树莓派套件(树莓派3B、16G内存卡、读卡器、充电器) 一台可以上网PC(我的操作系统版本为Windows10 1709) 一个USB耳机(有麦) 一根网线 软件 树莓派操作系统(我使用的是Raspbian) Putty(安装在Windows端,用来设置树莓派端) VNCViewer(安装在Windows端,用来链...

s1455364690的博客 3327

Python调用Windows API函数编写录音机和音乐播放器

送书活动火爆进行中:董老师又双叒叕送书啦,20本《Python数据分析、挖掘与可视化》近期会议、培训通知:培训通知|第12届高校师资培训班(Python数据采集、分析与可视化)会议通知|...

Python小屋 574
上一篇: Windows进程中的内存结构
下一篇: DLL共享数据段
ALENTAM
博客等级 码龄21年 17粉丝 2原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值