|
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