forked from ppy/osu
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOboeAudioBridge.cs
More file actions
237 lines (209 loc) · 8.88 KB
/
Copy pathOboeAudioBridge.cs
File metadata and controls
237 lines (209 loc) · 8.88 KB
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Debug = System.Diagnostics.Debug;
namespace osu.Android.Native
{
/// <summary>
/// Managed wrapper around the native Oboe low-latency audio bridge.
/// Provides accurate audio output latency measurement for rhythm-game synchronisation.
/// </summary>
public sealed class OboeAudioBridge : IDisposable
{
private const string lib_name = "osu_native";
private IntPtr nativePtr;
private volatile bool disposed;
private static readonly bool native_loaded;
static OboeAudioBridge()
{
try
{
// Try safe load (null search path) first to avoid Samsung security crashes
bool success = NativeLibrary.TryLoad(lib_name, typeof(OboeAudioBridge).Assembly, null, out _);
if (!success)
{
Debug.WriteLine("[osu!] Primary native library load (safe path) failed, attempting standard load...");
// Fallback to standard library loading which might search more paths but is riskier on some devices
success = NativeLibrary.TryLoad(lib_name, out _);
}
native_loaded = success;
}
catch (Exception e)
{
Debug.WriteLine($"[osu!] Failed to load native library for Oboe: {e.Message}");
native_loaded = false;
}
if (!native_loaded)
Debug.WriteLine("[osu!] Native library not found, Oboe unavailable");
else
Debug.WriteLine("[osu!] Native library loaded successfully for Oboe");
}
public static OboeAudioBridge? Create(int sampleRate = 0)
{
if (!native_loaded)
{
Debug.WriteLine("[osu!] Oboe Create() skipped — native library not loaded");
return null;
}
try
{
IntPtr ptr = nOboeCreate(sampleRate);
if (ptr == IntPtr.Zero)
{
Debug.WriteLine($"[osu!] nOboeCreate({sampleRate}) returned null — stream open failed");
return null;
}
return new OboeAudioBridge(ptr);
}
catch (Exception e)
{
Debug.WriteLine($"[osu!] nOboeCreate failed: {e.Message}");
return null;
}
}
private OboeAudioBridge(IntPtr ptr) => nativePtr = ptr;
public bool Start()
{
if (disposed || nativePtr == IntPtr.Zero) return false;
try { return nOboeStart(nativePtr) != 0; }
catch { return false; }
}
public void Stop()
{
if (disposed || nativePtr == IntPtr.Zero) return;
try { nOboeStop(nativePtr); }
catch { }
}
public double GetOutputLatencyMs()
{
if (disposed || nativePtr == IntPtr.Zero) return -1;
try { return nOboeGetLatencyMs(nativePtr); }
catch { return -1; }
}
/// <summary>
/// Returns a fresh hardware-latency reading by calling calculateLatencyMillis()
/// directly on the stream, bypassing the cached value that onAudioReady only
/// refreshes every ~1 second. Use this for measurement loops that need
/// accurate per-poll readings.
/// </summary>
public double GetInstantLatencyMs()
{
if (disposed || nativePtr == IntPtr.Zero) return -1;
try { return nOboeGetInstantLatencyMs(nativePtr); }
catch { return -1; }
}
public string GetLastErrorMessage()
{
if (disposed || nativePtr == IntPtr.Zero) return "Not initialized";
try
{
IntPtr ptr = nOboeGetLastErrorMessage(nativePtr);
return ptr == IntPtr.Zero ? "Unknown" : Marshal.PtrToStringAnsi(ptr) ?? "Unknown";
}
catch { return "P/Invoke error"; }
}
public bool IsActive
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (disposed || nativePtr == IntPtr.Zero) return false;
try { return nOboeIsActive(nativePtr) != 0; }
catch { return false; }
}
}
public int SampleRate
{
get
{
if (disposed || nativePtr == IntPtr.Zero) return 0;
try { return nOboeGetSampleRate(nativePtr); }
catch { return 0; }
}
}
public int FramesPerBurst
{
get
{
if (disposed || nativePtr == IntPtr.Zero) return 0;
try { return nOboeGetFramesPerBurst(nativePtr); }
catch { return 0; }
}
}
public int BufferSizeInFrames
{
get
{
if (disposed || nativePtr == IntPtr.Zero) return 0;
try { return nOboeGetBufferSizeInFrames(nativePtr); }
catch { return 0; }
}
}
public bool IsAAudio
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (disposed || nativePtr == IntPtr.Zero) return false;
try { return nOboeIsAAudio(nativePtr) != 0; }
catch { return false; }
}
}
public bool IsMMap
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (disposed || nativePtr == IntPtr.Zero) return false;
try { return nOboeIsMMap(nativePtr) != 0; }
catch { return false; }
}
}
public void SetProvider(IntPtr provider)
{
if (disposed || nativePtr == IntPtr.Zero) return;
try { nOboeSetProvider(nativePtr, provider); }
catch { }
}
public void Dispose()
{
if (disposed) return;
disposed = true;
if (nativePtr != IntPtr.Zero)
{
try { nOboeDestroy(nativePtr); }
catch { }
nativePtr = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~OboeAudioBridge() => Dispose();
[DllImport(lib_name)] private static extern IntPtr nOboeCreate(int sampleRate);
[DllImport(lib_name)] private static extern void nOboeDestroy(IntPtr ptr);
[DllImport(lib_name)] private static extern byte nOboeStart(IntPtr ptr);
[DllImport(lib_name)] private static extern void nOboeStop(IntPtr ptr);
[DllImport(lib_name)] private static extern double nOboeGetLatencyMs(IntPtr ptr);
[DllImport(lib_name)] private static extern double nOboeGetInstantLatencyMs(IntPtr ptr);
[DllImport(lib_name)] private static extern byte nOboeIsActive(IntPtr ptr);
[DllImport(lib_name)] private static extern int nOboeGetSampleRate(IntPtr ptr);
[DllImport(lib_name)] private static extern int nOboeGetFramesPerBurst(IntPtr ptr);
[DllImport(lib_name)] private static extern int nOboeGetBufferSizeInFrames(IntPtr ptr);
[DllImport(lib_name)] private static extern byte nOboeIsAAudio(IntPtr ptr);
[DllImport(lib_name)] private static extern byte nOboeIsMMap(IntPtr ptr);
[DllImport(lib_name)] private static extern void nOboeSetProvider(IntPtr ptr, IntPtr provider);
[DllImport(lib_name)] private static extern IntPtr nOboeGetLastErrorMessage(IntPtr ptr);
[DllImport(lib_name)] internal static extern byte nSetThreadAffinity(int coreMask);
[DllImport(lib_name)] internal static extern int nGetBigCoreMask();
[DllImport(lib_name)] internal static extern int nTameBackgroundThreads(int littleCoreMask);
[DllImport(lib_name)] internal static extern IntPtr nADPFCreateSession(long targetDurationNanos);
[DllImport(lib_name)] internal static extern void nADPFReportActualDuration(IntPtr sessionPtr, long actualDurationNanos);
[DllImport(lib_name)] internal static extern void nADPFUpdateTargetDuration(IntPtr sessionPtr, long targetDurationNanos);
[DllImport(lib_name)] internal static extern void nADPFCloseSession(IntPtr sessionPtr);
[DllImport(lib_name)] internal static extern void nADPFSetPreferPowerEfficiency(IntPtr sessionPtr, byte preferEfficiency);
[DllImport(lib_name)] internal static extern void nInstallCrashHandler([MarshalAs(UnmanagedType.LPUTF8Str)] string? logPath);
[DllImport(lib_name)] internal static extern void nReinstallCrashHandler();
}
}