11// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22// See the LICENCE file in the repository root for full licence text.
3- using System . Diagnostics ;
4- using Microsoft . Maui . Devices ;
5- using osu . Android . Performance ;
6- using osu . Framework . Development ;
73
8- using Android . Content . PM ;
9- using osu . Game . Performance ;
10- using osu . Game . Updater ;
11- using System . Collections . Specialized ;
124using System ;
135using System . Collections ;
146using System . Collections . Generic ;
7+ using System . Collections . Specialized ;
158using Debug = System . Diagnostics . Debug ;
169using System . Linq ;
1710using System . Reflection ;
1811using System . Runtime . CompilerServices ;
19- using Context = global :: Android . Content . Context ;
20- using Android . Media ;
12+ using Android . App ;
13+ using Android . Content . PM ;
2114using Android . OS ;
2215using Android . Views ;
2316using osu . Android . Native ;
17+ using osu . Framework ;
2418using osu . Android . Input ;
2519using osu . Framework . Allocation ;
26- using AudioManager = osu . Framework . Audio . AudioManager ;
2720using osu . Framework . Bindables ;
28- using osu . Framework . Configuration ;
29- using osu . Framework . Extensions . IEnumerableExtensions ;
3021using osu . Framework . Graphics ;
31- using osu . Framework . Input ;
3222using osu . Framework . Platform ;
33- using osu . Framework . Threading ;
3423using osu . Game ;
3524using osu . Game . Configuration ;
3625using osu . Game . Overlays ;
37- using osu . Game . Overlays . Notifications ;
26+ using osu . Game . Overlays . Settings ;
3827using osu . Game . Screens ;
28+ using osu . Game . Screens . Play ;
29+ using osuTK ;
30+ using osu . Framework . Audio ;
31+ using osu . Framework . Audio . Mixing ;
32+ using osu . Framework . Threading ;
33+ using osu . Android . Performance ;
3934using osu . Game . Utils ;
40- using Vector2 = osuTK . Vector2 ;
35+ using osu . Game . Updater ;
36+ using osu . Game . Performance ;
4137
4238namespace osu . Android
4339{
@@ -89,15 +85,6 @@ public partial class OsuGameAndroid : OsuGame
8985 private Delegate ? activeMixersHandler ;
9086 private object ? activeMixersList ;
9187
92- /// <summary>
93- /// Boxed reference to the native bridge manager.
94- /// Declared as <c>object?</c> so that the runtime never resolves the concrete
95- /// AndroidNativeBridgeManager type (and its P/Invoke field types) during
96- /// OsuGameAndroid class initialisation — which would trigger
97- /// NativeLibrary.TryLoad before the framework is ready and crash on some
98- /// Samsung devices.
99- /// All access goes through [NoInlining] helpers below.
100- /// </summary>
10188 private object ? nativeBridges ;
10289
10390 public OsuGameAndroid ( OsuGameActivity activity )
@@ -158,42 +145,36 @@ private void load()
158145
159146 try
160147 {
161- // Use reflection to bind to collection changes of the internal activeMixers list in AudioManager.
162- FieldInfo ? field = typeof ( AudioManager ) . GetField ( "activeMixers" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
163- if ( field != null )
148+ Type ? audioType = typeof ( AudioManager ) ;
149+ activeMixersList = audioType . GetFields ( BindingFlags . Instance | BindingFlags . NonPublic | BindingFlags . Public )
150+ . FirstOrDefault ( f => f . FieldType . IsGenericType && f . FieldType . GetGenericArguments ( ) . Contains ( typeof ( AudioMixer ) ) )
151+ ? . GetValue ( Audio ) ;
152+
153+ if ( activeMixersList != null )
164154 {
165- activeMixersList = field . GetValue ( Audio ) ;
166- object ? val = field . GetValue ( Audio ) ;
167- if ( val != null )
155+ MethodInfo ? bindMethod = activeMixersList . GetType ( ) . GetMethod ( "BindCollectionChanged" , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
156+ if ( bindMethod != null )
168157 {
169- MethodInfo ? bindMethod = val . GetType ( ) . GetMethod ( "BindCollectionChanged" , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
170- if ( bindMethod != null )
171- {
172- var del = Delegate . CreateDelegate ( bindMethod . GetParameters ( ) [ 0 ] . ParameterType , this , typeof ( OsuGameAndroid ) . GetMethod ( nameof ( onActiveMixersChanged ) , BindingFlags . Instance | BindingFlags . NonPublic ) ! ) ;
173- bindMethod . Invoke ( val , new object [ ] { del , true } ) ;
174- }
158+ activeMixersHandler = new NotifyCollectionChangedEventHandler ( onActiveMixersChanged ) ;
159+ bindMethod . Invoke ( activeMixersList , new object [ ] { activeMixersHandler } ) ;
160+ Debug . WriteLine ( "[osu!] Oboe redirector: Successfully bound to ActiveMixers collection" ) ;
175161 }
176162 }
177163 }
178- catch ( Exception ex )
179- {
180- Debug . WriteLine ( $ "[osu!] Failed to bind to activeMixers via reflection: { ex . Message } ") ;
181- }
164+ catch ( Exception e ) { Debug . WriteLine ( $ "[osu!] Oboe redirector: Failed to bind to ActiveMixers: { e . Message } ") ; }
182165 }
183166
184- [ MethodImpl ( MethodImplOptions . AggressiveOptimization ) ]
167+ private void onActiveMixersChanged ( object ? sender , NotifyCollectionChangedEventArgs args ) => Schedule ( ( ) => { if ( lowLatencyAudio . Value ) audioRedirector ? . RefreshMixers ( 0 ) ; } ) ;
168+
185169 protected override void LoadComplete ( )
186170 {
187- // Pin the current thread (Update thread) to high-performance cores.
188- // On S23 Ultra, cores 3-7 are high-performance. Mask = 0xF8 (11111000 in binary)
189171 try
190172 {
191173 if ( OboeAudioBridge . nSetThreadAffinity ( 0xF8 ) != 0 )
192174 Debug . WriteLine ( "[osu!] Update thread pinned to big cores" ) ;
193175
194176 Scheduler . Add ( ( ) =>
195177 {
196- // Dispatch to the draw thread to pin it.
197178 Host . DrawThread . Scheduler . Add ( ( ) =>
198179 {
199180 try { if ( OboeAudioBridge . nSetThreadAffinity ( 0xF8 ) != 0 ) Debug . WriteLine ( "[osu!] Render thread pinned to big cores" ) ; } catch { }
@@ -261,8 +242,6 @@ protected override void LoadComplete()
261242 Debug . WriteLine ( $ "[osu!] Audio offset auto-suggested: { suggested : F1} ms (hardware latency={ latency : F1} ms)") ;
262243 } , audioRedirector != null ? audioRedirector . Provider : IntPtr . Zero , sampleRate =>
263244 {
264- // Only redirect audio once the Oboe stream has successfully started.
265- // This prevents silence if the bridge fails to initialize.
266245 audioRedirector ? . RefreshMixers ( sampleRate > 0 ? sampleRate : hardwareSampleRate ) ;
267246 Debug . WriteLine ( "[osu!] Audio redirector refreshed with hardware sample rate: " + sampleRate ) ;
268247 } ) ;
@@ -277,7 +256,6 @@ protected override void LoadComplete()
277256 {
278257 stopOboeBridge ( ) ;
279258 audioRedirector ? . Dispose ( ) ;
280- // Re-create the redirector instance so it's fresh if re-enabled.
281259 audioRedirector = new OboeAudioRedirector ( Audio ) ;
282260 }
283261 } , true ) ;
@@ -295,9 +273,8 @@ protected override void LoadComplete()
295273 {
296274 Debug . WriteLine ( $ "[osu!] Failed to toggle Vulkan probe: { ex . Message } ") ;
297275 }
298- } , false ) ; // Already started in load() if true.
276+ } , false ) ;
299277
300- // Apply unbuffered touch dispatch.
301278 try
302279 {
303280 if ( OperatingSystem . IsAndroidVersionAtLeast ( 31 ) )
@@ -332,8 +309,7 @@ private void applyPerformanceOptimizations(bool enabled)
332309
333310 if ( enabled )
334311 {
335- highPerformanceSession ? . Dispose ( ) ;
336- highPerformanceSession = highPerformanceSessionManager . BeginSession ( ) ;
312+ highPerformanceSession ??= highPerformanceSessionManager . BeginSession ( ) ;
337313 }
338314 else
339315 {
@@ -405,15 +381,22 @@ private void selectHighestRefreshRate()
405381
406382 public override string VulkanStatus => ( nativeBridges as AndroidNativeBridgeManager ) ? . GetVulkanStatus ( ) ?? string . Empty ;
407383
408-
409-
410384 public override bool IsOboeActive => ( nativeBridges as AndroidNativeBridgeManager ) ? . IsOboeActive ( ) ?? false ;
411385
412- public override string OboeStatus => ( nativeBridges as AndroidNativeBridgeManager ) ? . GetOboeStatus ( ) ?? string . Empty ;
386+ public override bool IsOboeEnabled => lowLatencyAudio . Value ;
413387
414- public override double OboeLatency => ( nativeBridges as AndroidNativeBridgeManager ) ? . GetMeasuredAudioLatencyMs ( ) ?? - 1 ;
388+ public override string OboeStatus
389+ {
390+ get
391+ {
392+ string status = ( nativeBridges as AndroidNativeBridgeManager ) ? . GetOboeStatus ( ) ?? ( IsOboeEnabled ? "Initializing..." : string . Empty ) ;
393+ if ( IsOboeEnabled && audioRedirector != null && ! audioRedirector . IsRedirecting && IsOboeActive )
394+ status += " [No Redirect]" ;
395+ return status ;
396+ }
397+ }
415398
416- private void onActiveMixersChanged ( object ? sender , NotifyCollectionChangedEventArgs args ) => Schedule ( ( ) => { if ( lowLatencyAudio . Value ) audioRedirector ? . RefreshMixers ( 0 ) ; } ) ;
399+ public override double OboeLatency => ( nativeBridges as AndroidNativeBridgeManager ) ? . GetMeasuredAudioLatencyMs ( ) ?? - 1 ;
417400
418401 public double GetMeasuredAudioLatencyMs ( ) => getMeasuredAudioLatencyFromBridge ( ) ;
419402
0 commit comments