Ryujinx/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs

183 lines
4.8 KiB
C#
Raw Normal View History

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; }
public uint BufferCount => (uint)_buffers.Count;
public ulong PlayedSampleCount { get; set; }
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 bool FlushBuffers()
{
while (_queuedTagsQueue.TryDequeue(out long tag))
{
_releasedTagsQueue.Enqueue(tag);
}
_callback();
foreach (var buffer in _buffers)
{
AL.DeleteBuffer(buffer.Value);
}
bool heldBuffers = _buffers.Count > 0;
_buffers.Clear();
return heldBuffers;
}
public void SetVolume(float volume)
{
AL.Source(SourceId, ALSourcef.Gain, volume);
}
public float GetVolume()
{
AL.GetSource(SourceId, ALSourcef.Gain, out float volume);
return volume;
}
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);
}
}
}
}
}