下面就是一篇详细讲解“c# 通过WinAPI播放PCM声音”的完整攻略。
1. 背景知识
在开始讲解之前,我们需要了解一些背景知识:PCM(Pulse Code Modulation)脉冲编码调制,是一种数字音频编码方式,将模拟声音信号转换为数字信号,并以数字的形式进行存储和传输。在WinAPI中,我们可以使用waveOut API来播放PCM声音。
2. WaveOut API
我们可以使用waveOut API来播放PCM声音,该API提供了一些函数来完成播放操作,如以下几个函数:
- waveOutOpen:打开设备,以便播放声音。
- waveOutClose:关闭设备。
- waveOutPrepareHeader:准备音频数据并将其添加到播放缓冲区。
- waveOutUnprepareHeader:回收缓冲区。
- waveOutWrite:将准备好的音频数据写入缓冲区并播放。
3. 实现步骤
下面是通过WinAPI播放PCM声音的实现步骤:
-
调用waveOutOpen函数打开设备,这个函数需要传入一个WAVEFORMATEX结构体参数,这个结构体包含了声音的格式信息,如每秒采样率、每个采样点占用的位数、每个采样点的声道数等。
-
准备音频数据并将其添加到播放缓冲区中,需要以下几个步骤:
a. 先使用GlobalAlloc函数在进程的全局堆中分配一块内存。
b. 填充WAVEHDR结构体,并将数据保存到这个结构体中。这里需要注意的是,每个缓冲区的大小要小于等于设备的缓冲区大小,不然会导致声音卡顿或者播放不了。
c. 调用waveOutPrepareHeader函数准备音频数据,并将其添加到播放缓冲区中。
-
调用waveOutWrite函数将缓冲区的音频数据写入设备,并开始播放声音。
-
播放完声音后,调用waveOutUnprepareHeader函数回收音频缓冲区和WAVEHDR结构体的内存。
-
最后,调用waveOutClose函数关闭设备,释放相应资源。
4. 示例说明
下面是两个示例来说明如何使用WinAPI播放PCM声音。
示例一
这个示例演示如何从一个PCM文件中读取音频数据并播放,具体的实现步骤如下:
- 打开PCM文件并读取其中的音频数据。
csharp
byte[] data = File.ReadAllBytes("test.pcm");
- 调用waveOutOpen函数打开设备,并填写WAVEFORMATEX结构体参数。
```csharp
WAVEFORMATEX format = new WAVEFORMATEX();
format.nSamplesPerSec = 44100;
format.wBitsPerSample = 16;
format.nChannels = 2;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nBlockAlign = (short)(format.nChannels * format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
IntPtr hWaveOut = IntPtr.Zero;
waveOutOpen(out hWaveOut, WAVE_MAPPER, format, IntPtr.Zero, 0, CALLBACK_NULL);
```
- 将音频数据准备好,并填充到WAVEHDR结构体中。
```csharp
IntPtr pData = Marshal.AllocHGlobal(data.Length);
Marshal.Copy(data, 0, pData, data.Length);
var header = new WAVEHDR();
header.dwBufferLength = data.Length;
header.lpData = pData;
waveOutPrepareHeader(hWaveOut, ref header, Marshal.SizeOf(header));
```
- 调用waveOutWrite函数将缓冲区的音频数据写入设备,开始播放声音。
csharp
waveOutWrite(hWaveOut, ref header, Marshal.SizeOf(header));
- 播放完声音后,释放相应资源。
csharp
waveOutReset(hWaveOut);
waveOutUnprepareHeader(hWaveOut, ref header, Marshal.SizeOf(header));
Marshal.FreeHGlobal(pData);
waveOutClose(hWaveOut);
示例二
这个示例演示如何使用录音设备将麦克风的声音录制下来,并使用WinAPI进行实时播放。具体的实现步骤如下:
- 打开录音设备,并填写WAVEFORMATEX结构体参数。
```csharp
WAVEFORMATEX format = new WAVEFORMATEX();
format.nSamplesPerSec = 44100;
format.wBitsPerSample = 16;
format.nChannels = 2;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nBlockAlign = (short)(format.nChannels * format.wBitsPerSample / 8);
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
IntPtr hWaveIn = IntPtr.Zero;
waveInOpen(out hWaveIn, WAVE_MAPPER, format, IntPtr.Zero, 0, CALLBACK_NULL);
```
- 准备音频数据并将其添加到录音缓冲区中。
```csharp
const int BUFFER_SIZE = 16384; // 缓冲区大小
byte[] data = new byte[BUFFER_SIZE];
GCHandle hData = GCHandle.Alloc(data, GCHandleType.Pinned);
var header = new WAVEHDR();
header.lpData = hData.AddrOfPinnedObject();
header.dwBufferLength = BUFFER_SIZE;
header.dwFlags = 0;
waveInPrepareHeader(hWaveIn, ref header, Marshal.SizeOf(header));
waveInAddBuffer(hWaveIn, ref header, Marshal.SizeOf(header));
```
- 开始录音。
csharp
waveInStart(hWaveIn);
-
调用waveOutOpen函数打开播放设备,并填写WAVEFORMATEX结构体参数。
csharp
IntPtr hWaveOut = IntPtr.Zero;
waveOutOpen(out hWaveOut, WAVE_MAPPER, format, IntPtr.Zero, 0, CALLBACK_NULL); -
在录音过程中,不断从录音缓冲区中获取音频数据,并将数据添加到播放缓冲区中。
```csharp
while (true)
{
uint flags = 0;
waveInUnprepareHeader(hWaveIn, ref header, Marshal.SizeOf(header));
waveInPrepareHeader(hWaveIn, ref header, Marshal.SizeOf(header));
waveInAddBuffer(hWaveIn, ref header, Marshal.SizeOf(header));
waveInGetPosition(hWaveIn, out uint position, Marshal.SizeOf<uint>());
while ((flags & WHDR_DONE) == 0)
{
waveOutGetPosition(hWaveOut, out uint waveOutPosition, Marshal.SizeOf<uint>());
if (waveOutPosition - position >= BUFFER_SIZE)
continue;
waveOutWrite(hWaveOut, ref header, Marshal.SizeOf(header));
waveOutGetPosition(hWaveOut, out waveOutPosition, Marshal.SizeOf<uint>());
waveInGetErrorText(waveInGetError(hWaveIn), sb, sb.Capacity);
Console.WriteLine(sb.ToString().Trim());
flags = header.dwFlags;
}
}
```
- 在录音完成后,释放相应资源。
```csharp
waveInStop(hWaveIn);
waveInReset(hWaveIn);
waveInUnprepareHeader(hWaveIn, ref header, Marshal.SizeOf(header));
waveOutReset(hWaveOut);
waveOutUnprepareHeader(hWaveOut, ref header, Marshal.SizeOf(header));
waveOutClose(hWaveOut);
hData.Free();
```
以上就是使用WinAPI播放PCM声音的完整攻略,示例代码仅供参考。如果你需要播放其他格式的声音,比如MP3、WAV等,可以使用相应的解码库先将其解码成PCM数据再进行播放。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:c# 通过WinAPI播放PCM声音 - Python技术站