| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- //
- // 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 AudioProcessor()
- {
- _event = new ManualResetEvent(false);
- }
- public void SetOutputDevices(HardwareDevice[] outputDevices)
- {
- _outputDevices = outputDevices;
- }
- public void Start()
- {
- _mailbox = new Mailbox<MailboxMessage>();
- _sessionCommandList = new RendererSession[RendererConstants.AudioRendererSessionCountMax];
- _event.Reset();
- _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();
- }
- }
- }
- }
|