forked from Mirror/Ryujinx
5b26e4ef94
Changes: Implement software surround downmixing (fix #796). Fix a crash when no audio renderer were created when stopping emulation. NOTE: This PR also disable support of 5.1 surround on the OpenAL backend as we cannot detect if the hardware directly support it. (the downmixing applied by OpenAL on Windows is terribly slow)
148 lines
No EOL
4 KiB
C#
148 lines
No EOL
4 KiB
C#
using OpenTK.Audio.OpenAL;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Ryujinx.Audio
|
|
{
|
|
internal class OpenALAudioTrack : IDisposable
|
|
{
|
|
public int SourceId { get; private set; }
|
|
public int SampleRate { get; private set; }
|
|
public ALFormat Format { get; private set; }
|
|
public PlaybackState State { get; set; }
|
|
|
|
public int HardwareChannels { get; }
|
|
public int VirtualChannels { get; }
|
|
|
|
private ReleaseCallback _callback;
|
|
|
|
private ConcurrentDictionary<long, int> _buffers;
|
|
|
|
private Queue<long> _queuedTagsQueue;
|
|
private Queue<long> _releasedTagsQueue;
|
|
|
|
private bool _disposed;
|
|
|
|
public OpenALAudioTrack(int sampleRate, ALFormat format, int hardwareChannels, int virtualChannels, ReleaseCallback callback)
|
|
{
|
|
SampleRate = sampleRate;
|
|
Format = format;
|
|
State = PlaybackState.Stopped;
|
|
SourceId = AL.GenSource();
|
|
|
|
HardwareChannels = hardwareChannels;
|
|
VirtualChannels = virtualChannels;
|
|
|
|
_callback = callback;
|
|
|
|
_buffers = new ConcurrentDictionary<long, int>();
|
|
|
|
_queuedTagsQueue = new Queue<long>();
|
|
_releasedTagsQueue = new Queue<long>();
|
|
}
|
|
|
|
public bool ContainsBuffer(long tag)
|
|
{
|
|
foreach (long queuedTag in _queuedTagsQueue)
|
|
{
|
|
if (queuedTag == tag)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public long[] GetReleasedBuffers(int count)
|
|
{
|
|
AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount);
|
|
|
|
releasedCount += _releasedTagsQueue.Count;
|
|
|
|
if (count > releasedCount)
|
|
{
|
|
count = releasedCount;
|
|
}
|
|
|
|
List<long> tags = new List<long>();
|
|
|
|
while (count-- > 0 && _releasedTagsQueue.TryDequeue(out long tag))
|
|
{
|
|
tags.Add(tag);
|
|
}
|
|
|
|
while (count-- > 0 && _queuedTagsQueue.TryDequeue(out long tag))
|
|
{
|
|
AL.SourceUnqueueBuffers(SourceId, 1);
|
|
|
|
tags.Add(tag);
|
|
}
|
|
|
|
return tags.ToArray();
|
|
}
|
|
|
|
public int AppendBuffer(long tag)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
throw new ObjectDisposedException(GetType().Name);
|
|
}
|
|
|
|
int id = AL.GenBuffer();
|
|
|
|
_buffers.AddOrUpdate(tag, id, (key, oldId) =>
|
|
{
|
|
AL.DeleteBuffer(oldId);
|
|
|
|
return id;
|
|
});
|
|
|
|
_queuedTagsQueue.Enqueue(tag);
|
|
|
|
return id;
|
|
}
|
|
|
|
public void CallReleaseCallbackIfNeeded()
|
|
{
|
|
AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount);
|
|
|
|
if (releasedCount > 0)
|
|
{
|
|
// If we signal, then we also need to have released buffers available
|
|
// to return when GetReleasedBuffers is called.
|
|
// If playback needs to be re-started due to all buffers being processed,
|
|
// then OpenAL zeros the counts (ReleasedCount), so we keep it on the queue.
|
|
while (releasedCount-- > 0 && _queuedTagsQueue.TryDequeue(out long tag))
|
|
{
|
|
AL.SourceUnqueueBuffers(SourceId, 1);
|
|
|
|
_releasedTagsQueue.Enqueue(tag);
|
|
}
|
|
|
|
_callback();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing && !_disposed)
|
|
{
|
|
_disposed = true;
|
|
|
|
AL.DeleteSource(SourceId);
|
|
|
|
foreach (int id in _buffers.Values)
|
|
{
|
|
AL.DeleteBuffer(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |