2019-10-11 08:54:29 -07:00
|
|
|
|
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; }
|
|
|
|
|
|
2020-08-18 12:03:55 -07:00
|
|
|
|
public int HardwareChannels { get; }
|
|
|
|
|
public int VirtualChannels { get; }
|
2020-11-20 12:59:01 -08:00
|
|
|
|
public uint BufferCount => (uint)_buffers.Count;
|
|
|
|
|
public ulong PlayedSampleCount { get; set; }
|
2020-08-18 12:03:55 -07:00
|
|
|
|
|
2019-10-11 08:54:29 -07:00
|
|
|
|
private ReleaseCallback _callback;
|
|
|
|
|
|
|
|
|
|
private ConcurrentDictionary<long, int> _buffers;
|
|
|
|
|
|
|
|
|
|
private Queue<long> _queuedTagsQueue;
|
|
|
|
|
private Queue<long> _releasedTagsQueue;
|
|
|
|
|
|
|
|
|
|
private bool _disposed;
|
|
|
|
|
|
2020-08-18 12:03:55 -07:00
|
|
|
|
public OpenALAudioTrack(int sampleRate, ALFormat format, int hardwareChannels, int virtualChannels, ReleaseCallback callback)
|
2019-10-11 08:54:29 -07:00
|
|
|
|
{
|
|
|
|
|
SampleRate = sampleRate;
|
|
|
|
|
Format = format;
|
|
|
|
|
State = PlaybackState.Stopped;
|
|
|
|
|
SourceId = AL.GenSource();
|
|
|
|
|
|
2020-08-18 12:03:55 -07:00
|
|
|
|
HardwareChannels = hardwareChannels;
|
|
|
|
|
VirtualChannels = virtualChannels;
|
|
|
|
|
|
2019-10-11 08:54:29 -07:00
|
|
|
|
_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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-20 12:59:01 -08:00
|
|
|
|
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)
|
|
|
|
|
{
|
2020-11-27 11:55:00 -08:00
|
|
|
|
AL.Source(SourceId, ALSourcef.Gain, volume);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public float GetVolume()
|
|
|
|
|
{
|
|
|
|
|
AL.GetSource(SourceId, ALSourcef.Gain, out float volume);
|
2020-11-20 12:59:01 -08:00
|
|
|
|
|
2020-11-27 11:55:00 -08:00
|
|
|
|
return volume;
|
2020-11-20 12:59:01 -08:00
|
|
|
|
}
|
|
|
|
|
|
2019-10-11 08:54:29 -07:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|