在WINDOWS下,音频函数有多种类型,如MCI、多媒体OLE控制、高级音频等,使用方法都比较简单。但如果想编写一个功能较强大的音频处理程序,那就必须使用低级音频函数和多媒体文件I/O来控制音频设备的输入和输出。因为低级音频函数可直接与音频驱动程序交互,通过窗口消息或回调(CALL BACK)函数来管理音频数据块的记录和播放,控制非常灵活。重要的一点是,低级音频函数为我们提供了一个设备无关的接口。
 WINDOWS下音频的采集,播放有三种模式:
(1)通过高级音频函数、媒体控制接口MCI[1、2]设备驱动程序。
(2)低级音频函数MIDI Mapper、低级音频设备驱动(WaveXAPI)。
(3)利用DirectX中的DirectSound。
 使用MCI的方法极其简便,灵活性较差;使用低级音频函数的方法相对来说难一点,但是能够对音频数据进行灵活的操控;而采用DirectSound的方法,控制声音数据灵活,效果比前二者都好,但实现起来是三者中最难的。
DirectSound是DirectXAudio的一个较底层的部件,提供了丰富的接口函数,实现.wav格式的波形声音数据的播放控制。与一般的WindowsAPI提供的声音播放函数不同,DirectSound可实现多个声音的混合播放。DirectSound可充分使用声卡的内存资源,同时也提供了3D声效算法,模拟出真实的3D立体声。
使用 Wave API 进行Windows音频编程可以保持很大的自由度,而且与Linux中的OSS编程模式很像。这里我们主要介绍Wave API。
使用waveOutGetNumDevs和waveOutGetDevCaps来获取波形输出设备的个数和能力。只有在确定设备存在之后,才可以打开设备、使用设备。当waveInOpen/waveOutOpen的第二个参数为WAVE_MAPPER时,函数会自动挑选最适合播放给定的数据格式的设备。当有多种波形输出设备时,建议使用WAVE_MAPPER常数作为设备ID。
录音:
waveInOpen -> waveInPrepareHeader -> waveInAddBuffer -> waveInStart -> waveInStop -> waveInReset ->waveInUnprepareHeader -> waveInClose
播放:
waveOutOpen -> waveOutPrepareHeader -> waveOutWrite -> waveOutReset -> waveOutUnprepareHeader -> waveOutClose
1、查询设备数目和能力
使用waveOutGetNumDevs和waveOutGetDevCaps来获取波形输出设备的个数和能力。只有在确定设备存在之后,才可以打开设备、使用设备。
2、打开波形输出设备
使用waveInOpen/waveOutOpen为进行重放操作打开特定的波形设备。该函数打开与指定的设备ID相关联的设备,并以给出指定内存句柄的方法返回打开波形设备的句柄。
3、准备音频数据块
在波形捕获/播放之前,要准备好音频数据块,里面包含捕获所需的缓冲区地址和播放所需的数据地址。将数据块传递给设备驱动程序就实现了捕获/播放。使用的函数是waveInPrepareHeader /waveOutPerpareHeader。在用完数据块之后,必须用waveInUnprepareHeader/waveOutUnPrepareHeader函数来清除对波形数据块的准备。
4、发送音频数据块
在成功打开波形输出设备之后,就可以进行波形重放,使用waveOutWrite函数。在调用该函数后,必须等到设备驱动程序使用完音频数据块之后才可以把该数据块释放掉。
5、管理波形播放
在使用低级音频函数播放音频时,应用程序必须不断地向设备驱动程序提供数据块,直到播放结束。WINDOWS提供两种方法管理波形重放:一是使用窗口消息管理,二是使用低级回调函数管理。另外,通过使用waveOutPause、waveOutRestart和waveOutReset来进行暂停、重新启动和停止播放。
6、开始音频捕获
在成功打开波形输入设备之后,使用waveInStart函数。在调用该函数后,开始捕获输入设备的音频数据拷贝至音频数据块中的缓冲区地址中。
7、停止音频捕获
调用waveInStop函数停止在指定的波形输入设备上的输入
8、关闭波形设备
用完设备之后,必须调用waveInClose/waveOutClose函数关闭波形设备,以便其他程序可以使用设备。
更多函数见附录。
下面给出实例代码:
PS:工程在链接中使用了winmm.lib
#include "stdafx.h"
#include
#include
 
#define MUTE_LENGTH  128
 
HWAVEIN hWaveIn;//输入设备
HWAVEOUT hWaveOut;//输出设备
WAVEFORMATEX waveform;//采集音频的格式,结构体
BYTE *pBuffer;//采集音频时的数据缓存
WAVEHDR wHdr;//采集音频时包含数据缓存的结构体
FILE *pf;
char mute[MUTE_LENGTH];//静音符号串
 
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE wait;
waveform.wFormatTag = WAVE_FORMAT_PCM;//声音格式为PCM
waveform.nSamplesPerSec = 8000;//采样率,8000次/秒
waveform.wBitsPerSample = 16;//采样比特,16bits/次
waveform.nChannels = 1;//采样声道数,单声道
waveform.nAvgBytesPerSec = 16000;//每秒的数据率,就是每秒能采集多少字节的数据
waveform.nBlockAlign = 2;//一个块的大小,采样bit的字节数乘以声道数/8
waveform.cbSize = 0;
 
wait = CreateEvent(NULL,0,0,NULL);
 
FillMemory(mute,MUTE_LENGTH,(BYTE)0xFE);
//用静音符号填充.
//pcm表示的是时域信号,有震动才有声音,所以为静音并不一定表示都为0,如果都为1同样也是静音,所以只要是每个样本值没有变化都是静音 
 
DWORD bufsize = 1024*1024;//每次开辟10k的缓存存储录音数据
int i = 20;//录音时间
#if 0
    // Device   
    int nReturn = waveInGetNumDevs();  
    printf("输入设备数目:%d\n", nReturn);  
    for (int i=0; i
    {  
        WAVEINCAPS wic;  
        waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS));  
        printf("%d\t设备名:%s\n", i, wic.szPname);  
    }  
//使用waveInOpen函数开启音频采集
waveInOpen(&hWaveIn,WAVE_MAPPER,&waveform,(DWORD_PTR)wait,0L,CALLBACK_EVENT);
WAVEINCAPS wic;  
    waveInGetDevCaps((UINT_PTR)hWaveIn, &wic, sizeof(WAVEINCAPS));  
    printf("打开的输入设备:%s\n", wic.szPname); 
fopen_s(&pf,"录音测试.pcm","wb");
pBuffer = new BYTE[bufsize];
wHdr.lpData = (LPSTR)pBuffer;
wHdr.dwBufferLength = bufsize;
wHdr.dwBytesRecorded = 0;
wHdr.dwUser = 0;
wHdr.dwFlags = 0;
wHdr.dwLoops = 1;
waveInPrepareHeader(hWaveIn, &wHdr, sizeof(WAVEHDR));
//准备一个波形数据块头用于录音
waveInAddBuffer(hWaveIn,&wHdr,sizeof(WAVEHDR));
//指定波形数据块为录音输入缓存
waveInStart(hWaveIn);//开始录音
while(--i)
{
Sleep(1000);
printf("%ds\n",i);
}
waveInStop(hWaveIn);  
    waveInReset(hWaveIn); //停止录音
waveInClose(hWaveIn);
fwrite(pBuffer,1,wHdr.dwBytesRecorded,pf);
delete pBuffer;
fclose(pf);
#else
    // Device   
    int nReturn = waveOutGetNumDevs();  
    printf("\n输出设备数目:%d\n", nReturn);  
    for (int i=0; i
    {  
        WAVEOUTCAPS woc;  
        waveOutGetDevCaps(i, &woc, sizeof(WAVEOUTCAPS));  
        printf("#%d\t设备名:%s\n", i, woc.szPname);  
    }  
pBuffer = new BYTE[bufsize];
wHdr.lpData = mute;
wHdr.dwBufferLength = MUTE_LENGTH;
wHdr.dwBytesRecorded = 0;
wHdr.dwUser = 0;
wHdr.dwFlags = 0;
wHdr.dwLoops = 1;
waveOutOpen (&hWaveOut, WAVE_MAPPER, &waveform, (DWORD)wait, 0, CALLBACK_EVENT);
waveOutPrepareHeader (hWaveOut, &wHdr, sizeof(WAVEHDR));
while(1)
{
waveOutWrite (hWaveOut, &wHdr, sizeof (WAVEHDR)) ;
printf(".");
//Sleep(1000);
WaitForSingleObject(wait,INFINITE);
}
    waveOutReset(hWaveOut);  
    waveOutUnprepareHeader(hWaveOut, &wHdr, sizeof(WAVEHDR));  
    waveOutClose(hWaveOut);  
#endif
return 0;
}
附录:

waveInGetNumDevs

返回系统中存在的波形输入设备的数量

waveInAddBuffer

向波形输入设备添加一个输入缓冲区

waveInGetDevCaps

查询指定的波形输入设备以确定其性能

waveInGetErrorText

检取由指定的错误代码标识的文本说明

waveInGetID

获取指定的波形输入设备的标识符

waveInGetPosition

检取指定波形输入设备的当前位置

waveInMessage

发送一条消息给波形输入设备的驱动器

waveInOpen

为录音而打开一个波形输入设备

waveInPrepareHeader

为波形输入准备一个输入缓冲区

waveInStart

启动在指定的波形输入设备的输入

waveInReset

停止给定的波形输入设备的输入,且将当前位置清零

waveInStop

停止在指定的波形输入设备上的输入

waveInUnprepareHeader

清除由waveInPrepareHeader函数实现的准备

WaveInClose

关闭指定的波形输入设置

waveOutBreakLoop

中断给定的波形输出设备上一个循环,并允许播放驱动取列表中的下一个块

waveOutClose

关闭指定的波形输出设备

waveOutGetDevCaps

查询一个指定的波形输出设备以确定其性能

waveOutGetErrorText

检取由指定的错误代码标识的文本说明

waveOutGetID

检取指定的波形输出设备的标识符

waveOutGetNumDevs

检取系统中存在的波形输出设备的数量

waveOutGetPitch

查询一个波形输出设备的当前音调设置

waveOutGetPlaybackRate

查询一个波形输出设备当前播放的速度

waveOutGetPosition

检取指定波形输出设备的当前播放位置

waveOutGetVolume

查询指定波形输出设备的当前音量设置

waveOutMessage

发送一条消息给一个波形输出设备的驱动器

waveOutOpen

为播放打开一个波形输出设备

waveOutPause

暂停指定波形输出设备上的播放

waveOutPrepareHeader

为播放准备一个波形缓冲区

waveOutRestart

重新启动一个被暂停的波形输出设备

waveOutSetPitch

设置一个波形输出设备的音调

waveOutSetPlaybackRate

设置指定波形输出设备的速度

waveOutSetVolume

设置指定的波形输出设备的音量

waveOutUnprepareHeader

清除由waveOutPrepareHeader函数实现的准备

waveOutWrite

向指定的波形输出设备发送一个数据块

Windows PCM音频捕获与播放实现的更多相关文章

  1. Windows 下音频数据采集和播放

    音频操作所需头文件和链接库 #include<mmsystem.h>#include<mmreg.h>#pragma  comment(lib, "winmm.lib ...

  2. 使用AudioTrack播放PCM音频数据(android)

    众所周知,Android的MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用MediaPlayer实现的.MediaPl ...

  3. 使用WindowsAPI实现播放PCM音频的方法

    这篇文章主要介绍了使用WindowsAPI实现播放PCM音频的方法,很实用的一个功能,需要的朋友可以参考下 本文介绍了使用WindowsAPI实现播放PCM音频的方法,同前面一篇使用WindowsAP ...

  4. AudioRecord 录制播放PCM音频

    AudioRecord 与 MediaRecorder 区别 AudioRecord 基于字节流录制,输出的是pcm数据,未进行压缩,直接保存的pcm文件不能被播放器识别播放. 可以对音频文件进行实时 ...

  5. 简单实用的PCM音频播放器--沉寂几年之后回归的第一份笔记

    ---恢复内容开始--- PCM音频网络流播放,至于用处,就不多解释了. 一个简单的类,基于NAudio,一个简单的拼装类,实例化时三个参数,依次是采样率,系统播放设备Index,播放声道,调用Pla ...

  6. 视音频数据处理入门:PCM音频采样数据处理

    ===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...

  7. Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据

    一.实现说明 OpenSL ES的录音要比播放简单一些,在创建好引擎后,再创建好录音接口基本就可以录音了.在这里我们做的是流式录音,所以需要用至少2个buffer来缓存录制好的PCM数据,这里我们可以 ...

  8. Android OpenSL ES 开发:OpenSL ES利用SoundTouch实现PCM音频的变速和变调

    缘由 OpenSL ES 学习到现在已经知道 OpenSL ES 不仅能播放和录制PCM音频数据,还能改变声音大小.设置左声道或右声道播放.还能变速播放,可谓是播放音频的王者.但是变速有一点不好的就是 ...

  9. C++ 调节PCM音频音量大小

    在用解码器解码音频数据得到PCM音频数据块之后,可以在将数据送给声卡播放之前调节其音量大小,具体的实现函数如下: void RaiseVolume(char* buf, UINT32 size, UI ...

随机推荐

  1. Linux文件查找命令

    1. find find是最常见和最强大的查找命令,你可以用它找到任何你想找的文件. find的使用格式如下: $ find <指定目录> <指定条件> <指定动作> ...

  2. CentOS 7.x samba 服务器安装

    以下以root用户执行 1.安装: # yum install samba samba-client -y   2.设置开机启动: # systemctl enable smb.service ln ...

  3. SpringBoot启动流程分析(六):IoC容器依赖注入

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. [笔记]我的Linux入门之路 - 02.***-Qt5配置

    作为一个学习中的程序员,查wiki等,***肯定是刚需.况且没有它很多东西都下不下来.我在windows环境下使用的是shadowsocks,那么在linux下也使用它. 一.SS版本 SS版本众多, ...

  5. 九度OJ 1337:寻找最长合法括号序列 (DP)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:839 解决:179 题目描述: 给你一个长度为N的,由'('和')'组成的括号序列,你能找出这个序列中最长的合法括号子序列么?合法括号序列的 ...

  6. Java和js的区别,以及Java和c的区别

    刚开始的时候我们也搞不清这些概念,不过后来就慢慢清晰了,首先和大家谈谈Java和js的区别,最简单的区别就是一个是后端,一个是前端.   java是纯面向对象语言,javascrip其实和Java是完 ...

  7. 【python】-- 递归函数、高阶函数、嵌套函数、匿名函数

    递归函数 在函数内部,可以调用其他函数.但是在一个函数在内部调用自身,这个函数被称为递归函数 def calc(n): print(n) if int(n/2) == 0: #结束符 return n ...

  8. [note]fhq_treap

    fhq_treap 这东西据说是某个叫范浩强的神仙搞出来的, 他的这种treap可以不用旋转并且资磁很多平衡树操作, 复杂度通过随机的键值来保证(树大致平衡,期望一次操作复杂度\(logn\)) 依靠 ...

  9. Django 事务

    Django事务 事务是通过将一组相关操作组合为一个,要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠.事务具有4个特性:原子性.一致性.隔离性.持久性. 默认情况下,在Djang ...

  10. 我的Android进阶之旅------>Android关于TextWatcher的初步了解

    首先来看一下TextWatcher的源代码 package android.text; /** * When an object of a type is attached to an Editabl ...