forked from ppy/osu-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AudioThread.cs
162 lines (131 loc) · 5.41 KB
/
AudioThread.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Statistics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ManagedBass;
using osu.Framework.Audio;
using osu.Framework.Development;
using osu.Framework.Platform.Linux.Native;
namespace osu.Framework.Threading
{
public class AudioThread : GameThread
{
public AudioThread()
: base(name: "Audio")
{
OnNewFrame += onNewFrame;
PreloadBass();
}
public override bool IsCurrent => ThreadSafety.IsAudioThread;
internal sealed override void MakeCurrent()
{
base.MakeCurrent();
ThreadSafety.IsAudioThread = true;
}
internal override IEnumerable<StatisticsCounterType> StatisticsCounters => new[]
{
StatisticsCounterType.TasksRun,
StatisticsCounterType.Tracks,
StatisticsCounterType.Samples,
StatisticsCounterType.SChannels,
StatisticsCounterType.Components,
StatisticsCounterType.MixChannels,
};
private readonly List<AudioManager> managers = new List<AudioManager>();
private static readonly HashSet<int> initialised_devices = new HashSet<int>();
private static readonly GlobalStatistic<double> cpu_usage = GlobalStatistics.Get<double>("Audio", "Bass CPU%");
private void onNewFrame()
{
cpu_usage.Value = Bass.CPUUsage;
lock (managers)
{
for (int i = 0; i < managers.Count; i++)
{
var m = managers[i];
m.Update();
}
}
}
internal void RegisterManager(AudioManager manager)
{
lock (managers)
{
if (managers.Contains(manager))
throw new InvalidOperationException($"{manager} was already registered");
managers.Add(manager);
}
}
internal void UnregisterManager(AudioManager manager)
{
lock (managers)
managers.Remove(manager);
}
protected override void OnExit()
{
base.OnExit();
lock (managers)
{
// AudioManagers are iterated over backwards since disposal will unregister and remove them from the list.
for (int i = managers.Count - 1; i >= 0; i--)
{
var m = managers[i];
m.Dispose();
// Audio component disposal (including the AudioManager itself) is scheduled and only runs when the AudioThread updates.
// But the AudioThread won't run another update since it's exiting, so an update must be performed manually in order to finish the disposal.
m.Update();
}
managers.Clear();
}
// Safety net to ensure we have freed all devices before exiting.
// This is mainly required for device-lost scenarios.
// See https://github.com/ppy/osu-framework/pull/3378 for further discussion.
foreach (int d in initialised_devices.ToArray())
FreeDevice(d);
}
internal static bool InitDevice(int deviceId)
{
Debug.Assert(ThreadSafety.IsAudioThread);
Trace.Assert(deviceId != -1); // The real device ID should always be used, as the -1 device has special cases which are hard to work with.
// Try to initialise the device, or request a re-initialise.
if (Bass.Init(deviceId, Flags: (DeviceInitFlags)128)) // 128 == BASS_DEVICE_REINIT
{
initialised_devices.Add(deviceId);
return true;
}
return false;
}
internal static void FreeDevice(int deviceId)
{
Debug.Assert(ThreadSafety.IsAudioThread);
int selectedDevice = Bass.CurrentDevice;
if (canFreeDevice(deviceId) && canSelectDevice(deviceId))
{
Bass.CurrentDevice = deviceId;
Bass.Free();
}
if (selectedDevice != deviceId && canSelectDevice(selectedDevice))
Bass.CurrentDevice = selectedDevice;
initialised_devices.Remove(deviceId);
static bool canSelectDevice(int deviceId) => Bass.GetDeviceInfo(deviceId, out var deviceInfo) && deviceInfo.IsInitialized;
}
/// <summary>
/// Makes BASS available to be consumed.
/// </summary>
internal static void PreloadBass()
{
if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
{
// required for the time being to address libbass_fx.so load failures (see https://github.com/ppy/osu/issues/2852)
Library.Load("libbass.so", Library.LoadFlags.RTLD_LAZY | Library.LoadFlags.RTLD_GLOBAL);
}
}
/// <summary>
/// Whether a device can be freed.
/// On Linux, freeing device 0 is disallowed as it can cause deadlocks which don't surface immediately.
/// </summary>
private static bool canFreeDevice(int deviceId) => deviceId != 0 || RuntimeInfo.OS != RuntimeInfo.Platform.Linux;
}
}