forked from Mirror/Ryujinx
1825bd87b4
This is the first commit of a series of reformat around the codebase as discussed internally some weeks ago. This project being one that isn't touched that much, it shouldn't cause conflict with any opened PRs.
271 lines
No EOL
8.4 KiB
C#
271 lines
No EOL
8.4 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
} |