AudioProcessor.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. //
  2. // Copyright (c) 2019-2020 Ryujinx
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. //
  17. using Ryujinx.Audio.Renderer.Dsp.Command;
  18. using Ryujinx.Audio.Renderer.Integration;
  19. using Ryujinx.Audio.Renderer.Utils;
  20. using Ryujinx.Common;
  21. using Ryujinx.Common.Logging;
  22. using System;
  23. using System.Threading;
  24. namespace Ryujinx.Audio.Renderer.Dsp
  25. {
  26. public class AudioProcessor : IDisposable
  27. {
  28. private const int MaxBufferedFrames = 5;
  29. private const int TargetBufferedFrames = 3;
  30. private enum MailboxMessage : uint
  31. {
  32. Start,
  33. Stop,
  34. RenderStart,
  35. RenderEnd
  36. }
  37. private class RendererSession
  38. {
  39. public CommandList CommandList;
  40. public int RenderingLimit;
  41. public ulong AppletResourceId;
  42. }
  43. private Mailbox<MailboxMessage> _mailbox;
  44. private RendererSession[] _sessionCommandList;
  45. private Thread _workerThread;
  46. private HardwareDevice[] _outputDevices;
  47. private long _lastTime;
  48. private long _playbackEnds;
  49. private ManualResetEvent _event;
  50. public AudioProcessor()
  51. {
  52. _event = new ManualResetEvent(false);
  53. }
  54. public void SetOutputDevices(HardwareDevice[] outputDevices)
  55. {
  56. _outputDevices = outputDevices;
  57. }
  58. public void Start()
  59. {
  60. _mailbox = new Mailbox<MailboxMessage>();
  61. _sessionCommandList = new RendererSession[RendererConstants.AudioRendererSessionCountMax];
  62. _event.Reset();
  63. _lastTime = PerformanceCounter.ElapsedNanoseconds;
  64. StartThread();
  65. _mailbox.SendMessage(MailboxMessage.Start);
  66. if (_mailbox.ReceiveResponse() != MailboxMessage.Start)
  67. {
  68. throw new InvalidOperationException("Audio Processor Start response was invalid!");
  69. }
  70. }
  71. public void Stop()
  72. {
  73. _mailbox.SendMessage(MailboxMessage.Stop);
  74. if (_mailbox.ReceiveResponse() != MailboxMessage.Stop)
  75. {
  76. throw new InvalidOperationException("Audio Processor Stop response was invalid!");
  77. }
  78. }
  79. public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId)
  80. {
  81. _sessionCommandList[sessionId] = new RendererSession
  82. {
  83. CommandList = commands,
  84. RenderingLimit = renderingLimit,
  85. AppletResourceId = appletResourceId
  86. };
  87. }
  88. public void Signal()
  89. {
  90. _mailbox.SendMessage(MailboxMessage.RenderStart);
  91. }
  92. public void Wait()
  93. {
  94. if (_mailbox.ReceiveResponse() != MailboxMessage.RenderEnd)
  95. {
  96. throw new InvalidOperationException("Audio Processor Wait response was invalid!");
  97. }
  98. long increment = RendererConstants.AudioProcessorMaxUpdateTimeTarget;
  99. long timeNow = PerformanceCounter.ElapsedNanoseconds;
  100. if (timeNow > _playbackEnds)
  101. {
  102. // Playback has restarted.
  103. _playbackEnds = timeNow;
  104. }
  105. _playbackEnds += increment;
  106. // The number of frames we are behind where the timer says we should be.
  107. long framesBehind = (timeNow - _lastTime) / increment;
  108. // The number of frames yet to play on the backend.
  109. long bufferedFrames = (_playbackEnds - timeNow) / increment + framesBehind;
  110. // If we've entered a situation where a lot of buffers will be queued on the backend,
  111. // Skip some audio frames so that playback can catch up.
  112. if (bufferedFrames > MaxBufferedFrames)
  113. {
  114. // Skip a few frames so that we're not too far behind. (the target number of frames)
  115. _lastTime += increment * (bufferedFrames - TargetBufferedFrames);
  116. }
  117. while (timeNow < _lastTime + increment)
  118. {
  119. _event.WaitOne(1);
  120. timeNow = PerformanceCounter.ElapsedNanoseconds;
  121. }
  122. _lastTime += increment;
  123. }
  124. private void StartThread()
  125. {
  126. _workerThread = new Thread(Work)
  127. {
  128. Name = "AudioProcessor.Worker"
  129. };
  130. _workerThread.Start();
  131. }
  132. private void Work()
  133. {
  134. if (_mailbox.ReceiveMessage() != MailboxMessage.Start)
  135. {
  136. throw new InvalidOperationException("Audio Processor Start message was invalid!");
  137. }
  138. _mailbox.SendResponse(MailboxMessage.Start);
  139. _mailbox.SendResponse(MailboxMessage.RenderEnd);
  140. Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio processor");
  141. while (true)
  142. {
  143. MailboxMessage message = _mailbox.ReceiveMessage();
  144. if (message == MailboxMessage.Stop)
  145. {
  146. break;
  147. }
  148. if (message == MailboxMessage.RenderStart)
  149. {
  150. long startTicks = PerformanceCounter.ElapsedNanoseconds;
  151. for (int i = 0; i < _sessionCommandList.Length; i++)
  152. {
  153. if (_sessionCommandList[i] != null)
  154. {
  155. _sessionCommandList[i].CommandList.Process(_outputDevices[i]);
  156. _sessionCommandList[i] = null;
  157. }
  158. }
  159. long endTicks = PerformanceCounter.ElapsedNanoseconds;
  160. long elapsedTime = endTicks - startTicks;
  161. if (RendererConstants.AudioProcessorMaxUpdateTime < elapsedTime)
  162. {
  163. Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - RendererConstants.AudioProcessorMaxUpdateTime}ns)");
  164. }
  165. _mailbox.SendResponse(MailboxMessage.RenderEnd);
  166. }
  167. }
  168. Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor");
  169. _mailbox.SendResponse(MailboxMessage.Stop);
  170. }
  171. public void Dispose()
  172. {
  173. Dispose(true);
  174. }
  175. protected virtual void Dispose(bool disposing)
  176. {
  177. if (disposing)
  178. {
  179. _event.Dispose();
  180. }
  181. }
  182. }
  183. }