| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- //
- // Copyright (c) 2019-2021 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.Integration;
- using Ryujinx.Audio.Renderer.Dsp.Command;
- 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;
- public IHardwareDevice[] OutputDevices { get; private set; }
- private long _lastTime;
- private long _playbackEnds;
- private ManualResetEvent _event;
- private ManualResetEvent _pauseEvent;
- public AudioProcessor()
- {
- _event = new ManualResetEvent(false);
- }
- private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
- {
- // Get the real device driver (In case the compat layer is on top of it).
- deviceDriver = deviceDriver.GetRealDeviceDriver();
- if (deviceDriver.SupportsChannelCount(6))
- {
- return 6;
- }
- else
- {
- // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
- return 2;
- }
- }
- public void Start(IHardwareDeviceDriver deviceDriver, float volume)
- {
- OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
- // TODO: Before enabling this, we need up-mixing from stereo to 5.1.
- // uint channelCount = GetHardwareChannelCount(deviceDriver);
- uint channelCount = 2;
- for (int i = 0; i < OutputDevices.Length; i++)
- {
- // TODO: Don't hardcode sample rate.
- OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
- }
- _mailbox = new Mailbox<MailboxMessage>();
- _sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax];
- _event.Reset();
- _lastTime = PerformanceCounter.ElapsedNanoseconds;
- _pauseEvent = deviceDriver.GetPauseEvent();
- 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!");
- }
- foreach (IHardwareDevice device in OutputDevices)
- {
- device.Dispose();
- }
- }
- 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 = Constants.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)
- {
- _pauseEvent?.WaitOne();
- 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].CommandList.Dispose();
- _sessionCommandList[i] = null;
- }
- }
- long endTicks = PerformanceCounter.ElapsedNanoseconds;
- long elapsedTime = endTicks - startTicks;
- if (Constants.AudioProcessorMaxUpdateTime < elapsedTime)
- {
- Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - Constants.AudioProcessorMaxUpdateTime}ns)");
- }
- _mailbox.SendResponse(MailboxMessage.RenderEnd);
- }
- }
- Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor");
- _mailbox.SendResponse(MailboxMessage.Stop);
- }
- public float GetVolume()
- {
- if (OutputDevices != null)
- {
- foreach (IHardwareDevice outputDevice in OutputDevices)
- {
- if (outputDevice != null)
- {
- return outputDevice.GetVolume();
- }
- }
- }
- return 0f;
- }
- public void SetVolume(float volume)
- {
- if (OutputDevices != null)
- {
- foreach (IHardwareDevice outputDevice in OutputDevices)
- {
- outputDevice?.SetVolume(volume);
- }
- }
- }
- public void Dispose()
- {
- Dispose(true);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- _event.Dispose();
- }
- }
- }
- }
|