1- #if ANDROID
1+ #if ANDROID
22using Java . Nio ;
3+ using Java . Nio . Channels ;
34using Microsoft . ML . OnnxRuntime . Tensors ;
45using System . Reflection ;
56using Xamarin . TensorFlow . Lite ;
@@ -14,6 +15,9 @@ public sealed class MLKitInfer : IMLInfer, IDisposable
1415 private bool _disposed ;
1516 private Interpreter ? _interpreter ;
1617 private byte [ ] ? _modelBytes ;
18+ #pragma warning disable CS0414 // Field is assigned intentionally to keep the mapped model buffer alive for the interpreter lifetime.
19+ private ByteBuffer ? _modelBuffer ;
20+ #pragma warning restore CS0414
1721
1822 public void Dispose ( )
1923 {
@@ -44,21 +48,55 @@ public async Task LoadModelAsync(Stream modelStream, CancellationToken cancellat
4448 Initialize ( ms . ToArray ( ) ) ;
4549 }
4650
47- public async Task LoadModelFromAssetAsync ( string assetName , CancellationToken cancellationToken = default )
51+ public Task LoadModelFromAssetAsync ( string assetName , CancellationToken cancellationToken = default )
4852 {
4953 if ( string . IsNullOrWhiteSpace ( assetName ) )
5054 throw new ArgumentException ( "Asset name cannot be null or empty" , nameof ( assetName ) ) ;
51- try
52- {
53- await using var assetStream = Application . Context . Assets ? . Open ( assetName )
54- ?? throw new FileNotFoundException (
55- $ "Asset '{ assetName } ' not found in Android assets.") ;
56- await LoadModelAsync ( assetStream , cancellationToken ) . ConfigureAwait ( false ) ;
57- }
58- catch ( Exception ex )
55+
56+ return Task . Run ( ( ) =>
5957 {
60- throw new InvalidOperationException ( $ "Failed to load TFLite model asset '{ assetName } ': { ex . Message } ", ex ) ;
61- }
58+ cancellationToken . ThrowIfCancellationRequested ( ) ;
59+
60+ try
61+ {
62+ var assets = Application . Context . Assets
63+ ?? throw new InvalidOperationException ( "Android AssetManager is unavailable." ) ;
64+
65+ try
66+ {
67+ using var afd = assets . OpenFd ( assetName )
68+ ?? throw new FileNotFoundException (
69+ $ "Asset '{ assetName } ' not found in Android assets.") ;
70+
71+ using var inputStream = new Java . IO . FileInputStream ( afd . FileDescriptor ) ;
72+
73+ var channel = inputStream . Channel
74+ ?? throw new InvalidOperationException ( "Could not get FileChannel for asset." ) ;
75+
76+ var mappedBuffer = channel . Map (
77+ FileChannel . MapMode . ReadOnly ! ,
78+ afd . StartOffset ,
79+ afd . DeclaredLength ) ;
80+
81+ Initialize ( mappedBuffer ) ;
82+ }
83+ catch ( Exception ex ) when ( ex is not OperationCanceledException )
84+ {
85+ cancellationToken . ThrowIfCancellationRequested ( ) ;
86+
87+ using var assetStream = assets . Open ( assetName )
88+ ?? throw new FileNotFoundException ( $ "Asset '{ assetName } ' not found in Android assets.", ex ) ;
89+ using var ms = new System . IO . MemoryStream ( ) ;
90+ assetStream . CopyTo ( ms ) ;
91+ Initialize ( ms . ToArray ( ) ) ;
92+ }
93+ }
94+ catch ( Exception ex ) when ( ex is not OperationCanceledException )
95+ {
96+ throw new InvalidOperationException (
97+ $ "Failed to load TFLite model asset '{ assetName } ': { ex . Message } ", ex ) ;
98+ }
99+ } , cancellationToken ) ;
62100 }
63101
64102 public Task < Dictionary < string , Tensor < float > > > RunInferenceAsync ( Dictionary < string , Tensor < float > > inputs ,
@@ -126,20 +164,71 @@ public Task<Dictionary<string, Tensor<float>>> RunInferenceAsync(Dictionary<stri
126164 }
127165
128166 public Task < Dictionary < string , Tensor < float > > > RunInferenceLongInputsAsync ( Dictionary < string , Tensor < long > > inputs ,
129- CancellationToken cancellationToken = default )
167+ CancellationToken cancellationToken = default )
130168 {
169+ if ( ! IsModelLoaded ) throw new InvalidOperationException ( "No TFLite model loaded. Call LoadModelAsync first." ) ;
131170 if ( inputs == null || inputs . Count == 0 )
132171 throw new ArgumentException ( "Inputs cannot be null or empty" , nameof ( inputs ) ) ;
133- var floatInputs = new Dictionary < string , Tensor < float > > ( ) ;
134- foreach ( var ( k , v ) in inputs )
172+
173+ return Task . Run ( ( ) =>
135174 {
136- var cast = new DenseTensor < float > ( v . Dimensions . ToArray ( ) ) ;
137- var arr = v . ToArray ( ) ;
138- for ( var i = 0 ; i < arr . Length ; i ++ ) cast . Buffer . Span [ i ] = arr [ i ] ;
139- floatInputs [ k ] = cast ;
140- }
175+ lock ( _sync )
176+ {
177+ if ( _interpreter == null ) throw new InvalidOperationException ( "Interpreter disposed." ) ;
178+
179+ var first = inputs . First ( ) ;
180+ var inputTensor = first . Value ;
181+ var inputIndex = 0 ; // assume single input
182+
183+ var flat = inputTensor . ToArray ( ) ;
184+ var inputShape = inputTensor . Dimensions . ToArray ( ) ;
185+
186+ try
187+ {
188+ _interpreter . ResizeInput ( inputIndex , inputShape ) ;
189+ }
190+ catch
191+ {
192+ /* ignore if immutable */
193+ }
194+
195+ _interpreter . AllocateTensors ( ) ;
196+
197+ var nativeOrder = ByteOrder . NativeOrder ( ) ?? ByteOrder . BigEndian ;
198+
199+ // INT64 input buffer
200+ var inputData = ByteBuffer . AllocateDirect ( flat . Length * 8 ) . Order ( nativeOrder ! ) ;
201+ inputData . AsLongBuffer ( ) ! . Put ( flat ) ;
202+ inputData . Rewind ( ) ;
203+
204+ var outputTensor = _interpreter . GetOutputTensor ( 0 ) ;
205+ if ( outputTensor == null )
206+ throw new InvalidOperationException ( "Failed to retrieve output tensor." ) ;
207+
208+ var oshape = outputTensor . Shape ( ) ;
209+ if ( oshape == null )
210+ throw new InvalidOperationException ( "Failed to retrieve output shape." ) ;
211+
212+ var outCount = 1 ;
213+ foreach ( var d in oshape ) outCount *= d ;
141214
142- return RunInferenceAsync ( floatInputs , cancellationToken ) ;
215+ // FLOAT32 output buffer
216+ var outputData = ByteBuffer . AllocateDirect ( outCount * 4 ) . Order ( nativeOrder ! ) ;
217+
218+ _interpreter . Run ( inputData , outputData ) ;
219+
220+ outputData . Rewind ( ) ;
221+
222+ var outputArray = new float [ outCount ] ;
223+ outputData . AsFloatBuffer ( ) ! . Get ( outputArray ) ;
224+
225+ var dense = new DenseTensor < float > ( oshape . Select ( i => i ) . ToArray ( ) ) ;
226+ var span = dense . Buffer . Span ;
227+ for ( var i = 0 ; i < outputArray . Length ; i ++ ) span [ i ] = outputArray [ i ] ;
228+
229+ return new Dictionary < string , Tensor < float > > { [ "output0" ] = dense } ;
230+ }
231+ } , cancellationToken ) ;
143232 }
144233
145234 public Dictionary < string , MLNodeMetadata > GetInputMetadata ( )
@@ -174,6 +263,7 @@ public void UnloadModel()
174263 _interpreter ? . Dispose ( ) ;
175264 _interpreter = null ;
176265 _modelBytes = null ;
266+ _modelBuffer = null ;
177267 }
178268 }
179269
@@ -193,6 +283,23 @@ private void Initialize(byte[] bytes)
193283 _interpreter . AllocateTensors ( ) ;
194284 }
195285 }
286+ private void Initialize ( ByteBuffer modelBuffer )
287+ {
288+ lock ( _sync )
289+ {
290+ UnloadModel ( ) ;
291+
292+ _modelBytes = null ;
293+ _modelBuffer = modelBuffer ;
294+
295+ _interpreter = new Interpreter (
296+ modelBuffer ,
297+ ( Interpreter . Options ? ) new Interpreter . Options ( ) . SetNumThreads (
298+ Math . Max ( 1 , Environment . ProcessorCount - 1 ) ) ) ;
299+
300+ _interpreter . AllocateTensors ( ) ;
301+ }
302+ }
196303
197304 private static Dictionary < string , MLNodeMetadata > GetTensorMetadata ( Interpreter interpreter , bool isInput )
198305 {
0 commit comments