| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- using OpenTK.Audio;
- using OpenTK.Audio.OpenAL;
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Threading;
- namespace Ryujinx.Audio.OpenAL
- {
- public class OpenALAudioOut : IAalOutput
- {
- private const int MaxTracks = 256;
- private const int MaxReleased = 32;
- private AudioContext Context;
- private class Track : IDisposable
- {
- public int SourceId { get; private set; }
- public int SampleRate { get; private set; }
-
- public ALFormat Format { get; private set; }
- private ReleaseCallback Callback;
- public PlaybackState State { get; set; }
- private bool ShouldCallReleaseCallback;
- private ConcurrentDictionary<long, int> Buffers;
- private Queue<long> QueuedTagsQueue;
- private Queue<long> ReleasedTagsQueue;
- private int LastReleasedCount;
- private bool Disposed;
- public Track(int SampleRate, ALFormat Format, ReleaseCallback Callback)
- {
- this.SampleRate = SampleRate;
- this.Format = Format;
- this.Callback = Callback;
- State = PlaybackState.Stopped;
- SourceId = AL.GenSource();
- Buffers = new ConcurrentDictionary<long, int>();
- QueuedTagsQueue = new Queue<long>();
- ReleasedTagsQueue = new Queue<long>();
- }
- public bool ContainsBuffer(long Tag)
- {
- SyncQueuedTags();
- foreach (long QueuedTag in QueuedTagsQueue)
- {
- if (QueuedTag == Tag)
- {
- return true;
- }
- }
- return false;
- }
- public long[] GetReleasedBuffers(int MaxCount)
- {
- ClearReleased();
- List<long> Tags = new List<long>();
- HashSet<long> Unique = new HashSet<long>();
- while (MaxCount-- > 0 && ReleasedTagsQueue.TryDequeue(out long Tag))
- {
- if (Unique.Add(Tag))
- {
- Tags.Add(Tag);
- }
- }
- return Tags.ToArray();
- }
- public int AppendBuffer(long Tag)
- {
- if (Disposed)
- {
- throw new ObjectDisposedException(nameof(Track));
- }
- int Id = AL.GenBuffer();
- Buffers.AddOrUpdate(Tag, Id, (Key, OldId) =>
- {
- AL.DeleteBuffer(OldId);
- return Id;
- });
- QueuedTagsQueue.Enqueue(Tag);
- return Id;
- }
- public void ClearReleased()
- {
- SyncQueuedTags();
- AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount);
- CheckReleaseChanges(ReleasedCount);
- if (ReleasedCount > 0)
- {
- AL.SourceUnqueueBuffers(SourceId, ReleasedCount);
- }
- }
- public void CallReleaseCallbackIfNeeded()
- {
- CheckReleaseChanges();
- if (ShouldCallReleaseCallback)
- {
- ShouldCallReleaseCallback = false;
- Callback();
- }
- }
- private void CheckReleaseChanges()
- {
- AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount);
- CheckReleaseChanges(ReleasedCount);
- }
- private void CheckReleaseChanges(int NewReleasedCount)
- {
- if (LastReleasedCount != NewReleasedCount)
- {
- LastReleasedCount = NewReleasedCount;
- ShouldCallReleaseCallback = true;
- }
- }
-
- private void SyncQueuedTags()
- {
- AL.GetSource(SourceId, ALGetSourcei.BuffersQueued, out int QueuedCount);
- AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount);
- QueuedCount -= ReleasedCount;
- while (QueuedTagsQueue.Count > QueuedCount)
- {
- ReleasedTagsQueue.Enqueue(QueuedTagsQueue.Dequeue());
- }
- while (ReleasedTagsQueue.Count > MaxReleased)
- {
- ReleasedTagsQueue.Dequeue();
- }
- }
- 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);
- }
- }
- }
- }
- private ConcurrentDictionary<int, Track> Tracks;
- private Thread AudioPollerThread;
- private bool KeepPolling;
- public OpenALAudioOut()
- {
- Context = new AudioContext();
- Tracks = new ConcurrentDictionary<int, Track>();
- KeepPolling = true;
- AudioPollerThread = new Thread(AudioPollerWork);
- AudioPollerThread.Start();
- }
- private void AudioPollerWork()
- {
- do
- {
- foreach (Track Td in Tracks.Values)
- {
- Td.CallReleaseCallbackIfNeeded();
- }
- Thread.Yield();
- }
- while (KeepPolling);
- }
- public int OpenTrack(
- int SampleRate,
- int Channels,
- ReleaseCallback Callback,
- out AudioFormat Format)
- {
- Format = AudioFormat.PcmInt16;
- Track Td = new Track(SampleRate, GetALFormat(Channels, Format), Callback);
- for (int Id = 0; Id < MaxTracks; Id++)
- {
- if (Tracks.TryAdd(Id, Td))
- {
- return Id;
- }
- }
- return -1;
- }
- private ALFormat GetALFormat(int Channels, AudioFormat Format)
- {
- if (Channels < 1 || Channels > 2)
- {
- throw new ArgumentOutOfRangeException(nameof(Channels));
- }
- if (Channels == 1)
- {
- switch (Format)
- {
- case AudioFormat.PcmInt8: return ALFormat.Mono8;
- case AudioFormat.PcmInt16: return ALFormat.Mono16;
- }
- }
- else /* if (Channels == 2) */
- {
- switch (Format)
- {
- case AudioFormat.PcmInt8: return ALFormat.Stereo8;
- case AudioFormat.PcmInt16: return ALFormat.Stereo16;
- }
- }
- throw new ArgumentException(nameof(Format));
- }
- public void CloseTrack(int Track)
- {
- if (Tracks.TryRemove(Track, out Track Td))
- {
- Td.Dispose();
- }
- }
- public bool ContainsBuffer(int Track, long Tag)
- {
- if (Tracks.TryGetValue(Track, out Track Td))
- {
- return Td.ContainsBuffer(Tag);
- }
-
- return false;
- }
- public long[] GetReleasedBuffers(int Track, int MaxCount)
- {
- if (Tracks.TryGetValue(Track, out Track Td))
- {
- return Td.GetReleasedBuffers(MaxCount);
- }
-
- return null;
- }
- public void AppendBuffer(int Track, long Tag, byte[] Buffer)
- {
- if (Tracks.TryGetValue(Track, out Track Td))
- {
- int BufferId = Td.AppendBuffer(Tag);
- AL.BufferData(BufferId, Td.Format, Buffer, Buffer.Length, Td.SampleRate);
- AL.SourceQueueBuffer(Td.SourceId, BufferId);
- StartPlaybackIfNeeded(Td);
- }
- }
- public void Start(int Track)
- {
- if (Tracks.TryGetValue(Track, out Track Td))
- {
- Td.State = PlaybackState.Playing;
- StartPlaybackIfNeeded(Td);
- }
- }
- private void StartPlaybackIfNeeded(Track Td)
- {
- AL.GetSource(Td.SourceId, ALGetSourcei.SourceState, out int StateInt);
- ALSourceState State = (ALSourceState)StateInt;
- if (State != ALSourceState.Playing && Td.State == PlaybackState.Playing)
- {
- Td.ClearReleased();
- AL.SourcePlay(Td.SourceId);
- }
- }
- public void Stop(int Track)
- {
- if (Tracks.TryGetValue(Track, out Track Td))
- {
- Td.State = PlaybackState.Stopped;
- AL.SourceStop(Td.SourceId);
- }
- }
- public PlaybackState GetState(int Track)
- {
- if (Tracks.TryGetValue(Track, out Track Td))
- {
- return Td.State;
- }
- return PlaybackState.Stopped;
- }
- }
- }
|