aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs')
-rw-r--r--Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs221
1 files changed, 221 insertions, 0 deletions
diff --git a/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs b/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs
new file mode 100644
index 00000000..90f6cd51
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs
@@ -0,0 +1,221 @@
+//
+// Copyright (c) 2019-2020 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Integration;
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public class AudioProcessor : IDisposable
+ {
+ private const int MaxBufferedFrames = 5;
+ private const int TargetBufferedFrames = 3;
+
+ private enum MailboxMessage : uint
+ {
+ Start,
+ Stop,
+ RenderStart,
+ RenderEnd
+ }
+
+ private class RendererSession
+ {
+ public CommandList CommandList;
+ public int RenderingLimit;
+ public ulong AppletResourceId;
+ }
+
+ private Mailbox<MailboxMessage> _mailbox;
+ private RendererSession[] _sessionCommandList;
+ private Thread _workerThread;
+ private HardwareDevice[] _outputDevices;
+
+ private long _lastTime;
+ private long _playbackEnds;
+ private ManualResetEvent _event;
+
+ public void SetOutputDevices(HardwareDevice[] outputDevices)
+ {
+ _outputDevices = outputDevices;
+ }
+
+ public void Start()
+ {
+ _mailbox = new Mailbox<MailboxMessage>();
+ _sessionCommandList = new RendererSession[RendererConstants.AudioRendererSessionCountMax];
+ _event = new ManualResetEvent(false);
+ _lastTime = PerformanceCounter.ElapsedNanoseconds;
+
+ StartThread();
+
+ _mailbox.SendMessage(MailboxMessage.Start);
+
+ if (_mailbox.ReceiveResponse() != MailboxMessage.Start)
+ {
+ throw new InvalidOperationException("Audio Processor Start response was invalid!");
+ }
+ }
+
+ public void Stop()
+ {
+ _mailbox.SendMessage(MailboxMessage.Stop);
+
+ if (_mailbox.ReceiveResponse() != MailboxMessage.Stop)
+ {
+ throw new InvalidOperationException("Audio Processor Stop response was invalid!");
+ }
+ }
+
+ public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId)
+ {
+ _sessionCommandList[sessionId] = new RendererSession
+ {
+ CommandList = commands,
+ RenderingLimit = renderingLimit,
+ AppletResourceId = appletResourceId
+ };
+ }
+
+ public void Signal()
+ {
+ _mailbox.SendMessage(MailboxMessage.RenderStart);
+ }
+
+ public void Wait()
+ {
+ if (_mailbox.ReceiveResponse() != MailboxMessage.RenderEnd)
+ {
+ throw new InvalidOperationException("Audio Processor Wait response was invalid!");
+ }
+
+ long increment = RendererConstants.AudioProcessorMaxUpdateTimeTarget;
+
+ long timeNow = PerformanceCounter.ElapsedNanoseconds;
+
+ if (timeNow > _playbackEnds)
+ {
+ // Playback has restarted.
+ _playbackEnds = timeNow;
+ }
+
+ _playbackEnds += increment;
+
+ // The number of frames we are behind where the timer says we should be.
+ long framesBehind = (timeNow - _lastTime) / increment;
+
+ // The number of frames yet to play on the backend.
+ long bufferedFrames = (_playbackEnds - timeNow) / increment + framesBehind;
+
+ // If we've entered a situation where a lot of buffers will be queued on the backend,
+ // Skip some audio frames so that playback can catch up.
+ if (bufferedFrames > MaxBufferedFrames)
+ {
+ // Skip a few frames so that we're not too far behind. (the target number of frames)
+ _lastTime += increment * (bufferedFrames - TargetBufferedFrames);
+ }
+
+ while (timeNow < _lastTime + increment)
+ {
+ _event.WaitOne(1);
+
+ timeNow = PerformanceCounter.ElapsedNanoseconds;
+ }
+
+ _lastTime += increment;
+ }
+
+ private void StartThread()
+ {
+ _workerThread = new Thread(Work)
+ {
+ Name = "AudioProcessor.Worker"
+ };
+
+ _workerThread.Start();
+ }
+
+ private void Work()
+ {
+ if (_mailbox.ReceiveMessage() != MailboxMessage.Start)
+ {
+ throw new InvalidOperationException("Audio Processor Start message was invalid!");
+ }
+
+ _mailbox.SendResponse(MailboxMessage.Start);
+ _mailbox.SendResponse(MailboxMessage.RenderEnd);
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio processor");
+
+ while (true)
+ {
+ MailboxMessage message = _mailbox.ReceiveMessage();
+
+ if (message == MailboxMessage.Stop)
+ {
+ break;
+ }
+
+ if (message == MailboxMessage.RenderStart)
+ {
+ long startTicks = PerformanceCounter.ElapsedNanoseconds;
+
+ for (int i = 0; i < _sessionCommandList.Length; i++)
+ {
+ if (_sessionCommandList[i] != null)
+ {
+ _sessionCommandList[i].CommandList.Process(_outputDevices[i]);
+ _sessionCommandList[i] = null;
+ }
+ }
+
+ long endTicks = PerformanceCounter.ElapsedNanoseconds;
+
+ long elapsedTime = endTicks - startTicks;
+
+ if (RendererConstants.AudioProcessorMaxUpdateTime < elapsedTime)
+ {
+ Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - RendererConstants.AudioProcessorMaxUpdateTime}ns)");
+ }
+
+ _mailbox.SendResponse(MailboxMessage.RenderEnd);
+ }
+ }
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor");
+ _mailbox.SendResponse(MailboxMessage.Stop);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _event.Dispose();
+ }
+ }
+ }
+}