在C#中没有声音从扬声器发出声音时如何使用WasapiLoopbackCapture录制音频?

时间:2018-09-15 14:27:29

标签: c# audio naudio wasapi

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

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

我在流量问题上看到过一个类似的问题,但是我没有完全理解。

CSCore loopback recording when muted

任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:2)

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

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

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

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

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

因此,想法是从NAudio取得DataDiscontinuity标志,以使其不会丢失,添加一个计时器以及时生成静默字节,并结合检查连续性来获取所有内容,在计时器回调中添加静默字节并合并所有内容进入连续饲料。

答案 1 :(得分:0)

播放静音。 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/

答案 2 :(得分:0)

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

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)。