Skip to content

Commit

Permalink
[#37] Fixes an issue that caused loud noises to be recorded in the au…
Browse files Browse the repository at this point in the history
…dio when recording at 144fps.
  • Loading branch information
akon47 committed Oct 3, 2022
1 parent 4585ae4 commit 300e9ab
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 230 deletions.
133 changes: 83 additions & 50 deletions ScreenRecorder/AudioSource/AudioMixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,106 +7,126 @@

namespace ScreenRecorder.AudioSource
{
/// <summary>
/// Audio Mixer (2Ch, 16bit, 48000Hz)
/// </summary>
public class AudioMixer : IAudioSource, IDisposable
{
private readonly IAudioSource[] audioSources;
private readonly CircularBuffer circularMixerBuffer;
#region Fields

private Thread mixerThread, renderThread;
private ManualResetEvent needToStop;
private readonly IAudioSource[] _audioSources;
private readonly CircularBuffer _circularMixerBuffer;
private readonly int _samplesPerFrame;
private readonly int _samplesBytesPerFrame;
private readonly int _framesPerAdditinalSample;

private Thread _mixerThread, _renderThread;
private ManualResetEvent _needToStop;

#endregion

#region Constructors

public AudioMixer(params IAudioSource[] audioSources)
{
this.audioSources = audioSources;
var framePerBytes = (int)(48000.0d / VideoClockEvent.Framerate * 4);
circularMixerBuffer = new CircularBuffer(framePerBytes * 6);
_audioSources = audioSources;
_samplesPerFrame = (int)(48000.0d / VideoClockEvent.Framerate);
_samplesBytesPerFrame = _samplesPerFrame * 2 * 2; // 2Ch, 16bit

var remainingSamples = 48000 - (_samplesPerFrame * VideoClockEvent.Framerate);
_framesPerAdditinalSample = remainingSamples != 0 ? VideoClockEvent.Framerate / remainingSamples : 0;

needToStop = new ManualResetEvent(false);
_circularMixerBuffer = new CircularBuffer(_samplesBytesPerFrame * 6);

mixerThread = new Thread(MixerThreadHandler) { Name = "AudioMixer_Mixer", IsBackground = true };
mixerThread.Start();
_needToStop = new ManualResetEvent(false);

renderThread = new Thread(RenderThreadHandler) { Name = "AudioMixer_Render", IsBackground = true };
renderThread.Start();
_mixerThread = new Thread(MixerThreadHandler) { Name = "AudioMixer_Mixer", IsBackground = true };
_mixerThread.Start();

_renderThread = new Thread(RenderThreadHandler) { Name = "AudioMixer_Render", IsBackground = true };
_renderThread.Start();
}

public event NewAudioPacketEventHandler NewAudioPacket;
#endregion

#region Helpers

[DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)]
internal static extern void ZeroMemory(IntPtr dest, IntPtr size);

public void Dispose()
{
if (needToStop != null)
if (_needToStop != null)
{
needToStop.Set();
_needToStop.Set();
}

if (mixerThread != null)
if (_mixerThread != null)
{
if (mixerThread.IsAlive && !mixerThread.Join(500))
if (_mixerThread.IsAlive && !_mixerThread.Join(500))
{
mixerThread.Abort();
_mixerThread.Abort();
}

mixerThread = null;
_mixerThread = null;
}

if (renderThread != null)
if (_renderThread != null)
{
if (renderThread.IsAlive && !renderThread.Join(500))
if (_renderThread.IsAlive && !_renderThread.Join(500))
{
renderThread.Abort();
_renderThread.Abort();
}

renderThread = null;
_renderThread = null;
}

if (needToStop != null)
if (_needToStop != null)
{
needToStop.Close();
_needToStop.Close();
}

needToStop = null;
_needToStop = null;
}

[DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)]
internal static extern void ZeroMemory(IntPtr dest, IntPtr size);

private void MixerThreadHandler()
{
var framePerBytes = (int)(48000.0d / VideoClockEvent.Framerate * 4);

var sources = audioSources.Select(source => new AudioSourceResampler(source, 2, SampleFormat.S16, 48000))
var sources = _audioSources.Select(source => new AudioSourceResampler(source, 2, SampleFormat.S16, 48000))
.ToArray();

var sample = Marshal.AllocHGlobal(framePerBytes);
var mixSample = Marshal.AllocHGlobal(framePerBytes);
var sample = Marshal.AllocHGlobal(_samplesBytesPerFrame + 4);
var mixSample = Marshal.AllocHGlobal(_samplesBytesPerFrame + 4);

using (var systemClockEvent = new VideoClockEvent())
{
while (!needToStop.WaitOne(0, false))
long frames = 0;
while (!_needToStop.WaitOne(0, false))
{
if (systemClockEvent.WaitOne(10))
{
var count = sources[0].Buffer.Read(mixSample, framePerBytes);
if (count < framePerBytes)
var samplesBytesPerFrame = _samplesBytesPerFrame + (frames++ % 3 == 0 ? 4 : 0);

var count = sources[0].Buffer.Read(mixSample, samplesBytesPerFrame);
if (count < samplesBytesPerFrame)
{
ZeroMemory(mixSample + count, new IntPtr(framePerBytes - count));
ZeroMemory(mixSample + count, new IntPtr(samplesBytesPerFrame - count));
}

for (var i = 1; i < sources.Length; i++)
{
if (sources[i].IsValidBuffer)
{
count = sources[i].Buffer.Read(sample, framePerBytes);
if (count < framePerBytes)
count = sources[i].Buffer.Read(sample, samplesBytesPerFrame);
if (count < samplesBytesPerFrame)
{
ZeroMemory(sample + count, new IntPtr(framePerBytes - count));
ZeroMemory(sample + count, new IntPtr(samplesBytesPerFrame - count));
}

MixStereoSamples(sample, mixSample, mixSample, count / 4);
}
}

circularMixerBuffer.Write(mixSample, 0, framePerBytes);
_circularMixerBuffer.Write(mixSample, 0, samplesBytesPerFrame);
}
}
}
Expand Down Expand Up @@ -151,26 +171,39 @@ private void MixStereoSamples(IntPtr sample1, IntPtr sample2, IntPtr mix, int sa

private void RenderThreadHandler()
{
var samples = (int)(48000.0d / VideoClockEvent.Framerate);

var mixerAudioBuffer = Marshal.AllocHGlobal(samples * 2 * 2); // 16bit 2channels
var mixerAudioBuffer = Marshal.AllocHGlobal(_samplesBytesPerFrame + 4); // 16bit 2channels
using (var systemClockEvent = new VideoClockEvent())
{
while (!needToStop.WaitOne(0, false))
long frames = 0;
while (!_needToStop.WaitOne(0, false))
{
if (systemClockEvent.WaitOne(10))
{
if (circularMixerBuffer.Count >= samples * 2 * 2)
var samplesBytesPerFrame = _samplesBytesPerFrame + (frames++ % 3 == 0 ? 4 : 0);

if (_circularMixerBuffer.Count >= samplesBytesPerFrame)
{
circularMixerBuffer.Read(mixerAudioBuffer, samples * 2 * 2);
NewAudioPacket?.Invoke(this,
new NewAudioPacketEventArgs(48000, 2, SampleFormat.S16, samples, mixerAudioBuffer));
_circularMixerBuffer.Read(mixerAudioBuffer, samplesBytesPerFrame);
OnNewAudioPacket(new NewAudioPacketEventArgs(48000, 2, SampleFormat.S16, samplesBytesPerFrame / 4, mixerAudioBuffer));
}
}
}
}

Marshal.FreeHGlobal(mixerAudioBuffer);
}

#endregion

#region Events

public event NewAudioPacketEventHandler NewAudioPacket;

public void OnNewAudioPacket(NewAudioPacketEventArgs args)
{
NewAudioPacket?.Invoke(this, args);
}

#endregion
}
}

0 comments on commit 300e9ab

Please sign in to comment.