@@ -28,6 +28,7 @@ public actor StreamingAsrManager {
2828 private var accumulatedTokens : [ Int ] = [ ]
2929 private var accumulatedTokenTimestamps : [ Int ] = [ ]
3030 private var accumulatedTokenConfidences : [ Float ] = [ ]
31+ private var totalSamplesProcessed : Int = 0
3132
3233 // Raw sample buffer for sliding-window assembly (absolute indexing)
3334 private var sampleBuffer : [ Float ] = [ ]
@@ -95,6 +96,7 @@ public actor StreamingAsrManager {
9596 segmentIndex = 0
9697 lastProcessedFrame = 0
9798 resetAccumulatedMetadata ( )
99+ totalSamplesProcessed = 0
98100
99101 startTime = Date ( )
100102
@@ -163,6 +165,16 @@ public actor StreamingAsrManager {
163165 throw error
164166 }
165167
168+ let now = Date ( )
169+ let elapsedTime = startTime. map { now. timeIntervalSince ( $0) } ?? 0
170+ let minimumProcessingTime : TimeInterval = 1e-6
171+
172+ let maxTimestampFrame = accumulatedTokenTimestamps. max ( ) ?? 0
173+ let derivedSampleCount = maxTimestampFrame > 0
174+ ? ( maxTimestampFrame + 1 ) * ASRConstants. samplesPerEncoderFrame : 0
175+ let finalSampleCount = max ( totalSamplesProcessed, derivedSampleCount)
176+ let finalProcessingTime = finalSampleCount > 0 ? max ( elapsedTime, minimumProcessingTime) : elapsedTime
177+
166178 // Convert final accumulated tokens to ASRResult (proper way to avoid duplicates)
167179 let finalResult : ASRResult
168180 if let asrManager = asrManager, !accumulatedTokens. isEmpty {
@@ -195,17 +207,18 @@ public actor StreamingAsrManager {
195207 timestamps: timestamps,
196208 confidences: confidences,
197209 encoderSequenceLength: 0 ,
198- audioSamples: [ ] , // Not needed for final text conversion
199- processingTime: 0
210+ audioSamples: [ ] , // No need to retain audio samples for final aggregation
211+ processingTime: finalProcessingTime,
212+ totalSampleCount: finalSampleCount
200213 )
201214 } else {
202215 // Fallback to text concatenation if no tokens available
203216 let fallbackText = confirmedTranscript + volatileTranscript
204217 finalResult = ASRResult (
205218 text: fallbackText,
206219 confidence: 1.0 ,
207- duration: 0 ,
208- processingTime: 0 ,
220+ duration: TimeInterval ( finalSampleCount ) / TimeInterval ( config . asrConfig . sampleRate ) ,
221+ processingTime: finalProcessingTime ,
209222 tokenTimings: nil
210223 )
211224 }
@@ -233,6 +246,7 @@ public actor StreamingAsrManager {
233246 segmentIndex = 0
234247 lastProcessedFrame = 0
235248 resetAccumulatedMetadata ( )
249+ totalSamplesProcessed = 0
236250
237251 logger. info ( " StreamingAsrManager reset for source: \( String ( describing: self . audioSource) ) " )
238252 }
@@ -257,6 +271,11 @@ public actor StreamingAsrManager {
257271 accumulatedTokenConfidences. removeAll ( )
258272 }
259273
274+ private func calculateGlobalFrameOffset( for sampleIndex: Int ) -> Int {
275+ guard sampleIndex > 0 else { return 0 }
276+ return sampleIndex / ASRConstants. samplesPerEncoderFrame
277+ }
278+
260279 func accumulateTokenMetadata( tokens: [ Int ] , timestamps: [ Int ] , confidences: [ Float ] ) {
261280 guard !tokens. isEmpty else { return }
262281
@@ -285,6 +304,7 @@ public actor StreamingAsrManager {
285304 private func appendSamplesAndProcess( _ samples: [ Float ] ) async {
286305 // Append samples to buffer
287306 sampleBuffer. append ( contentsOf: samples)
307+ totalSamplesProcessed += samples. count
288308
289309 // Process while we have at least chunk + right ahead of the current center start
290310 let chunk = config. chunkSamples
@@ -304,7 +324,11 @@ public actor StreamingAsrManager {
304324
305325 let window = Array ( sampleBuffer [ startIdx..< endIdx] )
306326 let actualLeftSecs = Double ( nextWindowCenterStart - leftStartAbs) / Double( sampleRate)
307- await processWindow ( window, actualLeftSeconds: actualLeftSecs)
327+ await processWindow (
328+ window,
329+ actualLeftSeconds: actualLeftSecs,
330+ windowStartSample: leftStartAbs
331+ )
308332
309333 // Advance by chunk size
310334 nextWindowCenterStart += chunk
@@ -342,7 +366,11 @@ public actor StreamingAsrManager {
342366
343367 let window = Array ( sampleBuffer [ startIdx..< endIdx] )
344368 let actualLeftSecs = Double ( nextWindowCenterStart - leftStartAbs) / Double( sampleRate)
345- await processWindow ( window, actualLeftSeconds: actualLeftSecs)
369+ await processWindow (
370+ window,
371+ actualLeftSeconds: actualLeftSecs,
372+ windowStartSample: leftStartAbs
373+ )
346374
347375 nextWindowCenterStart += effectiveChunk
348376
@@ -359,7 +387,11 @@ public actor StreamingAsrManager {
359387 }
360388
361389 /// Process a single assembled window: [left, chunk, right]
362- private func processWindow( _ windowSamples: [ Float ] , actualLeftSeconds: Double ) async {
390+ private func processWindow(
391+ _ windowSamples: [ Float ] ,
392+ actualLeftSeconds _: Double ,
393+ windowStartSample: Int
394+ ) async {
363395 guard let asrManager = asrManager else { return }
364396
365397 do {
@@ -374,9 +406,12 @@ public actor StreamingAsrManager {
374406 previousTokens: accumulatedTokens
375407 )
376408
409+ let frameOffset = calculateGlobalFrameOffset ( for: windowStartSample)
410+ let adjustedTimestamps = timestamps. map { $0 + frameOffset }
411+
377412 // Update state
378- accumulateTokenMetadata ( tokens: tokens, timestamps: timestamps , confidences: confidences)
379- lastProcessedFrame = max ( lastProcessedFrame, timestamps . max ( ) ?? 0 )
413+ accumulateTokenMetadata ( tokens: tokens, timestamps: adjustedTimestamps , confidences: confidences)
414+ lastProcessedFrame = max ( lastProcessedFrame, adjustedTimestamps . max ( ) ?? 0 )
380415 segmentIndex += 1
381416
382417 let processingTime = Date ( ) . timeIntervalSince ( chunkStartTime)
@@ -386,7 +421,7 @@ public actor StreamingAsrManager {
386421 // The final result will use all accumulated tokens for proper deduplication
387422 let interim = asrManager. processTranscriptionResult (
388423 tokenIds: tokens, // Only current chunk tokens for progress updates
389- timestamps: timestamps ,
424+ timestamps: adjustedTimestamps ,
390425 confidences: confidences,
391426 encoderSequenceLength: 0 ,
392427 audioSamples: windowSamples,
@@ -524,6 +559,10 @@ extension StreamingAsrManager {
524559 internal func setAsrManagerForTesting( _ manager: AsrManager ? ) {
525560 asrManager = manager
526561 }
562+
563+ internal func setTotalSamplesProcessedForTesting( _ count: Int ) {
564+ totalSamplesProcessed = count
565+ }
527566}
528567#endif
529568
0 commit comments