|
7 | 7 |
|
8 | 8 | namespace ScreenRecorder.AudioSource |
9 | 9 | { |
| 10 | + /// <summary> |
| 11 | + /// Audio Mixer (2Ch, 16bit, 48000Hz) |
| 12 | + /// </summary> |
10 | 13 | public class AudioMixer : IAudioSource, IDisposable |
11 | 14 | { |
12 | | - private readonly IAudioSource[] audioSources; |
13 | | - private readonly CircularBuffer circularMixerBuffer; |
| 15 | + #region Fields |
14 | 16 |
|
15 | | - private Thread mixerThread, renderThread; |
16 | | - private ManualResetEvent needToStop; |
| 17 | + private readonly IAudioSource[] _audioSources; |
| 18 | + private readonly CircularBuffer _circularMixerBuffer; |
| 19 | + private readonly int _samplesPerFrame; |
| 20 | + private readonly int _samplesBytesPerFrame; |
| 21 | + private readonly int _framesPerAdditinalSample; |
| 22 | + |
| 23 | + private Thread _mixerThread, _renderThread; |
| 24 | + private ManualResetEvent _needToStop; |
| 25 | + |
| 26 | + #endregion |
| 27 | + |
| 28 | + #region Constructors |
17 | 29 |
|
18 | 30 | public AudioMixer(params IAudioSource[] audioSources) |
19 | 31 | { |
20 | | - this.audioSources = audioSources; |
21 | | - var framePerBytes = (int)(48000.0d / VideoClockEvent.Framerate * 4); |
22 | | - circularMixerBuffer = new CircularBuffer(framePerBytes * 6); |
| 32 | + _audioSources = audioSources; |
| 33 | + _samplesPerFrame = (int)(48000.0d / VideoClockEvent.Framerate); |
| 34 | + _samplesBytesPerFrame = _samplesPerFrame * 2 * 2; // 2Ch, 16bit |
| 35 | + |
| 36 | + var remainingSamples = 48000 - (_samplesPerFrame * VideoClockEvent.Framerate); |
| 37 | + _framesPerAdditinalSample = remainingSamples != 0 ? VideoClockEvent.Framerate / remainingSamples : 0; |
23 | 38 |
|
24 | | - needToStop = new ManualResetEvent(false); |
| 39 | + _circularMixerBuffer = new CircularBuffer(_samplesBytesPerFrame * 6); |
25 | 40 |
|
26 | | - mixerThread = new Thread(MixerThreadHandler) { Name = "AudioMixer_Mixer", IsBackground = true }; |
27 | | - mixerThread.Start(); |
| 41 | + _needToStop = new ManualResetEvent(false); |
28 | 42 |
|
29 | | - renderThread = new Thread(RenderThreadHandler) { Name = "AudioMixer_Render", IsBackground = true }; |
30 | | - renderThread.Start(); |
| 43 | + _mixerThread = new Thread(MixerThreadHandler) { Name = "AudioMixer_Mixer", IsBackground = true }; |
| 44 | + _mixerThread.Start(); |
| 45 | + |
| 46 | + _renderThread = new Thread(RenderThreadHandler) { Name = "AudioMixer_Render", IsBackground = true }; |
| 47 | + _renderThread.Start(); |
31 | 48 | } |
32 | 49 |
|
33 | | - public event NewAudioPacketEventHandler NewAudioPacket; |
| 50 | + #endregion |
| 51 | + |
| 52 | + #region Helpers |
| 53 | + |
| 54 | + [DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)] |
| 55 | + internal static extern void ZeroMemory(IntPtr dest, IntPtr size); |
34 | 56 |
|
35 | 57 | public void Dispose() |
36 | 58 | { |
37 | | - if (needToStop != null) |
| 59 | + if (_needToStop != null) |
38 | 60 | { |
39 | | - needToStop.Set(); |
| 61 | + _needToStop.Set(); |
40 | 62 | } |
41 | 63 |
|
42 | | - if (mixerThread != null) |
| 64 | + if (_mixerThread != null) |
43 | 65 | { |
44 | | - if (mixerThread.IsAlive && !mixerThread.Join(500)) |
| 66 | + if (_mixerThread.IsAlive && !_mixerThread.Join(500)) |
45 | 67 | { |
46 | | - mixerThread.Abort(); |
| 68 | + _mixerThread.Abort(); |
47 | 69 | } |
48 | 70 |
|
49 | | - mixerThread = null; |
| 71 | + _mixerThread = null; |
50 | 72 | } |
51 | 73 |
|
52 | | - if (renderThread != null) |
| 74 | + if (_renderThread != null) |
53 | 75 | { |
54 | | - if (renderThread.IsAlive && !renderThread.Join(500)) |
| 76 | + if (_renderThread.IsAlive && !_renderThread.Join(500)) |
55 | 77 | { |
56 | | - renderThread.Abort(); |
| 78 | + _renderThread.Abort(); |
57 | 79 | } |
58 | 80 |
|
59 | | - renderThread = null; |
| 81 | + _renderThread = null; |
60 | 82 | } |
61 | 83 |
|
62 | | - if (needToStop != null) |
| 84 | + if (_needToStop != null) |
63 | 85 | { |
64 | | - needToStop.Close(); |
| 86 | + _needToStop.Close(); |
65 | 87 | } |
66 | 88 |
|
67 | | - needToStop = null; |
| 89 | + _needToStop = null; |
68 | 90 | } |
69 | 91 |
|
70 | | - [DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = false)] |
71 | | - internal static extern void ZeroMemory(IntPtr dest, IntPtr size); |
72 | | - |
73 | 92 | private void MixerThreadHandler() |
74 | 93 | { |
75 | | - var framePerBytes = (int)(48000.0d / VideoClockEvent.Framerate * 4); |
76 | | - |
77 | | - var sources = audioSources.Select(source => new AudioSourceResampler(source, 2, SampleFormat.S16, 48000)) |
| 94 | + var sources = _audioSources.Select(source => new AudioSourceResampler(source, 2, SampleFormat.S16, 48000)) |
78 | 95 | .ToArray(); |
79 | 96 |
|
80 | | - var sample = Marshal.AllocHGlobal(framePerBytes); |
81 | | - var mixSample = Marshal.AllocHGlobal(framePerBytes); |
| 97 | + var sample = Marshal.AllocHGlobal(_samplesBytesPerFrame + 4); |
| 98 | + var mixSample = Marshal.AllocHGlobal(_samplesBytesPerFrame + 4); |
82 | 99 |
|
83 | 100 | using (var systemClockEvent = new VideoClockEvent()) |
84 | 101 | { |
85 | | - while (!needToStop.WaitOne(0, false)) |
| 102 | + long frames = 0; |
| 103 | + while (!_needToStop.WaitOne(0, false)) |
86 | 104 | { |
87 | 105 | if (systemClockEvent.WaitOne(10)) |
88 | 106 | { |
89 | | - var count = sources[0].Buffer.Read(mixSample, framePerBytes); |
90 | | - if (count < framePerBytes) |
| 107 | + var samplesBytesPerFrame = _samplesBytesPerFrame + (frames++ % 3 == 0 ? 4 : 0); |
| 108 | + |
| 109 | + var count = sources[0].Buffer.Read(mixSample, samplesBytesPerFrame); |
| 110 | + if (count < samplesBytesPerFrame) |
91 | 111 | { |
92 | | - ZeroMemory(mixSample + count, new IntPtr(framePerBytes - count)); |
| 112 | + ZeroMemory(mixSample + count, new IntPtr(samplesBytesPerFrame - count)); |
93 | 113 | } |
94 | 114 |
|
95 | 115 | for (var i = 1; i < sources.Length; i++) |
96 | 116 | { |
97 | 117 | if (sources[i].IsValidBuffer) |
98 | 118 | { |
99 | | - count = sources[i].Buffer.Read(sample, framePerBytes); |
100 | | - if (count < framePerBytes) |
| 119 | + count = sources[i].Buffer.Read(sample, samplesBytesPerFrame); |
| 120 | + if (count < samplesBytesPerFrame) |
101 | 121 | { |
102 | | - ZeroMemory(sample + count, new IntPtr(framePerBytes - count)); |
| 122 | + ZeroMemory(sample + count, new IntPtr(samplesBytesPerFrame - count)); |
103 | 123 | } |
104 | 124 |
|
105 | 125 | MixStereoSamples(sample, mixSample, mixSample, count / 4); |
106 | 126 | } |
107 | 127 | } |
108 | 128 |
|
109 | | - circularMixerBuffer.Write(mixSample, 0, framePerBytes); |
| 129 | + _circularMixerBuffer.Write(mixSample, 0, samplesBytesPerFrame); |
110 | 130 | } |
111 | 131 | } |
112 | 132 | } |
@@ -151,26 +171,39 @@ private void MixStereoSamples(IntPtr sample1, IntPtr sample2, IntPtr mix, int sa |
151 | 171 |
|
152 | 172 | private void RenderThreadHandler() |
153 | 173 | { |
154 | | - var samples = (int)(48000.0d / VideoClockEvent.Framerate); |
155 | | - |
156 | | - var mixerAudioBuffer = Marshal.AllocHGlobal(samples * 2 * 2); // 16bit 2channels |
| 174 | + var mixerAudioBuffer = Marshal.AllocHGlobal(_samplesBytesPerFrame + 4); // 16bit 2channels |
157 | 175 | using (var systemClockEvent = new VideoClockEvent()) |
158 | 176 | { |
159 | | - while (!needToStop.WaitOne(0, false)) |
| 177 | + long frames = 0; |
| 178 | + while (!_needToStop.WaitOne(0, false)) |
160 | 179 | { |
161 | 180 | if (systemClockEvent.WaitOne(10)) |
162 | 181 | { |
163 | | - if (circularMixerBuffer.Count >= samples * 2 * 2) |
| 182 | + var samplesBytesPerFrame = _samplesBytesPerFrame + (frames++ % 3 == 0 ? 4 : 0); |
| 183 | + |
| 184 | + if (_circularMixerBuffer.Count >= samplesBytesPerFrame) |
164 | 185 | { |
165 | | - circularMixerBuffer.Read(mixerAudioBuffer, samples * 2 * 2); |
166 | | - NewAudioPacket?.Invoke(this, |
167 | | - new NewAudioPacketEventArgs(48000, 2, SampleFormat.S16, samples, mixerAudioBuffer)); |
| 186 | + _circularMixerBuffer.Read(mixerAudioBuffer, samplesBytesPerFrame); |
| 187 | + OnNewAudioPacket(new NewAudioPacketEventArgs(48000, 2, SampleFormat.S16, samplesBytesPerFrame / 4, mixerAudioBuffer)); |
168 | 188 | } |
169 | 189 | } |
170 | 190 | } |
171 | 191 | } |
172 | 192 |
|
173 | 193 | Marshal.FreeHGlobal(mixerAudioBuffer); |
174 | 194 | } |
| 195 | + |
| 196 | + #endregion |
| 197 | + |
| 198 | + #region Events |
| 199 | + |
| 200 | + public event NewAudioPacketEventHandler NewAudioPacket; |
| 201 | + |
| 202 | + public void OnNewAudioPacket(NewAudioPacketEventArgs args) |
| 203 | + { |
| 204 | + NewAudioPacket?.Invoke(this, args); |
| 205 | + } |
| 206 | + |
| 207 | + #endregion |
175 | 208 | } |
176 | 209 | } |
0 commit comments