How to record audio with WasapiLoopbackCapture when no voice is coming out from speaker in c#?
Asked Answered
C

3

10

The below is my sample code to record audio coming from a speaker. It is working fine. But it is recording audio only when some audio is coming out from speaker. If there is no audio then it is not recording. Actually i am doing screen recording with system's voice. The length of audio recorded by wasapiloop is mismatching with screen recording length because wasapiloop is recording audio only when there is sound from speaker.

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

I have seen one similar stack over flow question but i did not understand it fully.

CSCore loopback recording when muted

Any help is greatly appreciated.

Crassulaceous answered 15/9, 2018 at 14:27 Comment(1)
G
5

By design WASAPI loopback capture only produces data when there is actual playback - you already figured this out. If your goal is to produce continuous data stream you are responsible to generate silence audio bytes for the gaps between data generated by loopback capture. Loopback capture data comes with time stamps and discontinuity flags, so you can apply simple math and identify number of zero/silence bytes to add.

UPDATE. Having checked NAudio code (esp. here) I see a problem for accurate math for silence byte calculation in DataAvailable event: NAudio ignores DataDiscontinuity also known as AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY flag reported by loopback capture.

Loopback capture will produce audio packets of specific duration (e.g. 10 ms long) when there is playback data on the endpoint. These packets are exposed to you to be read via WasapiLoopbackCapture interface. There is no buffering and as long as data you read is continuous, GetBuffer call does not raise the DataDiscontinuity flag. That is, you have the flag with your first packet and then until you see it next time you treat the data as continuous.

When playback stops and loopback capture stops producing data, there is no new data from GetBuffer. However you know the size/duration of the packet and in absence of data your timer needs to substitute this missing data with silence bytes (it is also a good idea to take care of partially filled last packet in a sequence with respective size of silence packet). You will be injecting the silence bytes until GetBuffer call succeeds and gets you new data available. You will have DataDiscontinuity flag there to indicate a new data sequence.

From that point you are supposed to stop doing silence and use read captured data again. Also since you know there is no buffering involved, it would be a good idea to use the discontinuity packet to update your timing records: when you have the packet you can assume that its last byte corresponds to moment in time when GetBuffer succeeded. From there you can derive the number of silence bytes to fill for perfect continuity of your combined stream.

So the idea is to take DataDiscontinuity flag from NAudio so that it does not get lost, add a timer for timely silence byte generation and get everything together with checking continuity, adding silence bytes in timer callback and combining everything into continuous feed.

Goosander answered 15/9, 2018 at 15:15 Comment(2)
Can you please provide some example how can we calculate silence bytes?Crassulaceous
@chindiralasampathkumar Just play some silence on the output device, that way WASAPI does not stop sending data.Intermediary
I
2

Due to there being no accepted answer, I'll try and explain the problem and the easiest solution.

WASAPI will only send data (triggering the DataAvailable event on NAudio) if there is something playing on the device you are recording.

You can do what is mentioned on the other answer which is performing complicated and volatile math to try and estimate where to fill in the breaks in data with silence. However there is a much simpler solution.

The Simple Solution:

Create a WasapiLoopbackCapture for your specified MMDevice. In the example below I get the default audio output endpoint:

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

Create a SilenceProvider using the WaveFormat retrieved by the MMDevice:

var silenceProvider = new SilenceProvider(device.WaveFormat);

Initialize a WasapiOut player on the given device passing the SilenceProvider, then invoke Play():

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

Preferrably you want to do all this on another thread and listen out for some exit boolean to become true, something like while(!exit) { Thread.Sleep(250); }

NOTE REGARDING THE MMDevice!:

An MMDevice can only be used on the Thread that it was instantiated on, using it outside that thread will cause a COM exception (see this post).

Intermediary answered 25/4, 2021 at 14:19 Comment(0)
A
0

Play silence. Wasapi will think there is something to record and will send audio to the loopback device.

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

Also, @Hans Passant wrote this link in a comment:

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

Audun answered 1/4, 2019 at 7:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.