65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 waveIn/waveOut API 的全双工音频播放器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (106投票s)

2003年9月1日

3分钟阅读

viewsIcon

2263441

downloadIcon

22361

一篇关于使用 C# 中的 P/Invoke 调用 waveIn/waveOut API 进行低级音频捕获和播放的文章。

Sample Image - cswavrec.gif

引言

正如我在文章 C# 低级音频播放器 中已经提到的,.NET 框架中没有内置类来处理声音。这不仅适用于音频播放,也适用于音频捕获。

不过需要注意的是,Managed DirectX 9 SDK 确实包含用于高级和低级音频操作的类。但是,有时您不希望应用程序依赖完整的 DX 9 运行时,仅仅是为了进行基本的音频播放和捕获,而且 Managed DirectSound 在某些方面也无能为力(例如,多通道音频播放和捕获)。

尽管如此,我强烈建议您使用 Managed DirectSound 进行音频播放和捕获,除非您有充分的理由不这样做。

本文介绍了一个示例应用程序,该应用程序通过 P/Invoke 在 C# 中使用 waveInwaveOut API,几乎同时从声卡输入捕获音频信号并进行播放。

使用代码

该示例代码重用了我文章 C# 低级音频播放器 中的 WaveOutPlayer 类。此示例中的新类是 WaveInRecorderFifoStream

FifoStream 类扩展了 System.IO.Stream,实现了字节的 FIFO(先进先出)队列。重写的 Write 方法将数据添加到 FIFO 的尾部,而 Read 方法从 FIFO 的头部窥探并移除数据。Length 属性在任何时候都返回缓冲数据的量。调用 Flush 将清除所有待处理的数据。

WaveInRecorder 类类似于 WaveOutPlayer 类。事实上,如果您查看源文件,您会发现这些类的实现非常相似。与 WaveOutPlayer 一样,该类的接口已被精简到最基本。

创建 WaveInRecorder 的实例将立即开始录制。以下是创建 WaveOutPlayerWaveInRecorder 实例的代码。

private void Start()
{
    Stop();
    try
    {
        WaveLib.WaveFormat fmt = new WaveLib.WaveFormat(44100, 16, 2);
        m_Player = new WaveLib.WaveOutPlayer(-1, fmt, 16384, 3, 
                        new WaveLib.BufferFillEventHandler(Filler));
        m_Recorder = new WaveLib.WaveInRecorder(-1, fmt, 16384, 3, 
                        new WaveLib.BufferDoneEventHandler(DataArrived));
    }
    catch
    {
        Stop();
        throw;
    }
}

WaveInRecorder 构造函数接受五个参数。除了最后一个参数外,它们的含义与 WaveOutPlayer 中的相同。

第一个参数是您想要使用的波形输入设备的 ID。值 -1 代表系统默认设备,但如果您的系统有多个声卡,则可以传递 0 到已安装声卡数量减一之间的任意数字来选择特定设备。

第二个参数是音频样本的格式。

第三个和第四个参数是内部波形缓冲器的大小和要分配的缓冲器数量。您应该将它们设置为合理的值。较小的缓冲器会带来较低的延迟,但如果您的计算机速度不够快,捕获的音频可能会出现间隙。

第五个(也是最后一个)参数是一个委托,当内部音频缓冲器充满捕获数据时,该委托会被定期调用。在示例应用程序中,我们只是将捕获的数据写入 FIFO,如下所示:

private void DataArrived(IntPtr data, int size)
{
    if (m_RecBuffer == null || m_RecBuffer.Length < size)
        m_RecBuffer = new byte[size];
    System.Runtime.InteropServices.Marshal.Copy(data, m_RecBuffer, 0, size);
    m_Fifo.Write(m_RecBuffer, 0, m_RecBuffer.Length);
}

同样,每次播放器需要更多数据时都会调用 Filler 方法。我们的实现只是从 FIFO 读取数据,如下所示:

private void Filler(IntPtr data, int size)
{
    if (m_PlayBuffer == null || m_PlayBuffer.Length < size)
        m_PlayBuffer = new byte[size];
    if (m_Fifo.Length >= size)
        m_Fifo.Read(m_PlayBuffer, 0, size);
    else
        for (int i = 0; i < m_PlayBuffer.Length; i++)
            m_PlayBuffer[i] = 0;
    System.Runtime.InteropServices.Marshal.Copy(m_PlayBuffer, 
                                                 0, data, size);
}

请注意,我们将临时缓冲器 m_RecBufferm_PlayBuffer 声明为成员字段,以通过节省一些垃圾回收来提高性能。

要停止流式传输,只需对播放器和捕获对象调用 Dispose。我们还需要刷新 FIFO,以便下次调用 Start 时没有要播放的剩余数据。

private void Stop()
{
    if (m_Player != null)
        try
        {
            m_Player.Dispose();
        }
        finally
        {
            m_Player = null;
        }
    if (m_Recorder != null)
        try
        {
            m_Recorder.Dispose();
        }
        finally
        {
            m_Recorder = null;
        }
    m_Fifo.Flush(); // clear all pending data
}

结论

此示例演示了如何在 C# 中结合使用 waveInwaveOut API。作为一项练习,您可以尝试将此代码与文章 C# 中的音频效果编程 中的音频效果框架结合起来,以实时应用效果到实时音频输入,尽管对于某些应用程序来说,延迟可能是一个问题。

© . All rights reserved.