【问题标题】:How to record audio with WasapiLoopbackCapture when no voice is coming out from speaker in c#?当 C# 中的扬声器没有声音时,如何使用 WasapiLoopbackCapture 录制音频?
【发布时间】:2021-04-25 22:19:08
【问题描述】:

以下是我录制来自扬声器的音频的示例代码。它工作正常。但只有当扬声器发出一些音频时,它才会录制音频。如果没有音频,那么它不是在录制。实际上我正在用系统的声音进行屏幕录制。 wasapiloop 录制的音频长度与屏幕录制长度不匹配,因为 wasapiloop 仅在扬声器有声音时才录制音频。

WasapiCapture waveLoop = new WasapiLoopbackCapture();
waveLoop.Initialize();
waveLoop.DataAvailable += waveLoop_DataAvailable;
waveLoop.Stopped += waveLoop_Stopped;
waveLoop.Start();

我见过一个类似的堆栈溢出问题,但我没有完全理解。

CSCore loopback recording when muted

非常感谢任何帮助。

【问题讨论】:

标签: c# audio naudio wasapi


【解决方案1】:

根据设计,WASAPI 环回捕获仅在实际播放时生成数据 - 您已经知道这一点。如果您的目标是产生连续的数据流,您有责任为环回捕获生成的数据之间的间隙生成静音音频字节。环回捕获数据带有时间戳和不连续标志,因此您可以应用简单的数学运算并确定要添加的零/静音字节数。

更新。在检查了 NAudio 代码(尤其是 here)后,我在 DataAvailable 事件中发现了一个关于静音字节计算的准确数学问题:NAudio 忽略了 DataDiscontinuity 也称为 AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY 由环回捕获报告的标志。

当端点上有播放数据时,环回捕获将产生特定持续时间(例如 10 毫秒长)的音频数据包。这些数据包向您公开,以便通过WasapiLoopbackCapture 接口读取。没有缓冲,只要您读取的数据是连续的,GetBuffer 调用就不会引发DataDiscontinuity 标志。也就是说,您的第一个数据包带有标志,然后直到您下次将数据视为连续数据时才看到它。

当播放停止并且环回捕获停止生成数据时,GetBuffer 没有新数据。但是,您知道数据包的大小/持续时间,并且在没有数据的情况下,您的计时器需要用静音字节替换此丢失的数据(最好按照相应大小的静音数据包处理序列中部分填充的最后一个数据包)。您将注入静默字节,直到GetBuffer 调用成功并为您提供可用的新数据。您将在那里有DataDiscontinuity 标志以指示新的数据序列。

从那时起,您应该停止沉默并再次使用读取捕获的数据。此外,由于您知道不涉及缓冲,因此最好使用不连续数据包来更新您的计时记录:当您拥有数据包时,您可以假设它的最后一个字节对应于GetBuffer 成功的时刻。从那里您可以得出要填充的静音字节数,以实现组合流的完美连续性。

所以想法是从 NAudio 获取DataDiscontinuity 标志,这样它就不会丢失,添加一个计时器以及时生成静音字节并将所有内容放在一起检查连续性,在计时器回调中添加静音字节并将所有内容组合成连续的饲料。

【讨论】:

  • 您能否提供一些示例,我们如何计算静音字节?
  • @chindiralasampathkumar 只是在输出设备上播放一些静音,这样 WASAPI 不会停止发送数据。
【解决方案2】:

播放静音。 Wasapi 会认为有东西要录制,并将音频发送到环回设备。

https://mathewsachin.github.io/blog/2017/07/28/mixing-audio.html

另外,@Hans Passant 在评论中写了这个链接:

https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/

【讨论】:

    【解决方案3】:

    由于没有公认的答案,我将尝试解释问题和最简单的解决方案。

    WASAPI 只会在您正在录制的设备上有播放内容时发送数据(触发 NAudio 上的 DataAvailable 事件)。

    您可以执行另一个答案中提到的操作,该答案正在执行复杂且易变的数学运算,以尝试估计在何处用静默填充数据中断。但是有一个更简单的解决方案。

    简单的解决方案:

    为您指定的 MMDevice 创建一个 WasapiLoopbackCapture。在下面的示例中,我得到了默认的音频输出端点:

    var device = new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
    

    使用 MMDevice 检索到的 WaveFormat 创建一个 SilenceProvider:

    var silenceProvider = new SilenceProvider(device.WaveFormat);
    

    在传递SilenceProvider的给定设备上初始化一个WasapiOut播放器,然后调用Play()

    using (var wasapiOut = new WasapiOut(device, AudioClientShareMode.Shared, false, 250))
    {
        wasapiOut.Init(silenceProvider);
        wasapiOut.Play();
    }
    

    最好您想在另一个线程上执行所有这些操作并监听一些退出布尔值是否变为真,例如 while(!exit) { Thread.Sleep(250); }

    关于MMDevice!的注意事项:

    MMDevice 只能在实例化它的线程上使用,在该线程外使用它会导致 COM 异常(请参阅this post)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多