@@ -165,64 +165,11 @@ public actor StreamingAsrManager {
165165 throw error
166166 }
167167
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 =
174- maxTimestampFrame > 0
175- ? ( maxTimestampFrame + 1 ) * ASRConstants. samplesPerEncoderFrame : 0
176- let finalSampleCount = max ( totalSamplesProcessed, derivedSampleCount)
177- let finalProcessingTime = finalSampleCount > 0 ? max ( elapsedTime, minimumProcessingTime) : elapsedTime
178-
179- // Convert final accumulated tokens to ASRResult (proper way to avoid duplicates)
180- let finalResult : ASRResult
181- if let asrManager = asrManager, !accumulatedTokens. isEmpty {
182- let timestamps : [ Int ]
183- if accumulatedTokenTimestamps. count == accumulatedTokens. count {
184- timestamps = accumulatedTokenTimestamps
185- } else {
186- if !accumulatedTokenTimestamps. isEmpty {
187- logger. warning (
188- " Final token timestamp count ( \( accumulatedTokenTimestamps. count) ) does not match token count ( \( accumulatedTokens. count) ); omitting token timings "
189- )
190- }
191- timestamps = [ ]
192- }
193-
194- let confidences : [ Float ]
195- if accumulatedTokenConfidences. count == accumulatedTokens. count {
196- confidences = accumulatedTokenConfidences
197- } else {
198- if !accumulatedTokenConfidences. isEmpty {
199- logger. warning (
200- " Final token confidence count ( \( accumulatedTokenConfidences. count) ) does not match token count ( \( accumulatedTokens. count) ); omitting confidence data "
201- )
202- }
203- confidences = [ ]
204- }
205-
206- finalResult = asrManager. processTranscriptionResult (
207- tokenIds: accumulatedTokens,
208- timestamps: timestamps,
209- confidences: confidences,
210- encoderSequenceLength: 0 ,
211- audioSamples: [ ] , // No need to retain audio samples for final aggregation
212- processingTime: finalProcessingTime,
213- totalSampleCount: finalSampleCount
214- )
215- } else {
216- // Fallback to text concatenation if no tokens available
217- let fallbackText = confirmedTranscript + volatileTranscript
218- finalResult = ASRResult (
219- text: fallbackText,
220- confidence: 1.0 ,
221- duration: TimeInterval ( finalSampleCount) / TimeInterval( config. asrConfig. sampleRate) ,
222- processingTime: finalProcessingTime,
223- tokenTimings: nil
224- )
225- }
168+ let metrics = resolveFinalMetrics ( at: Date ( ) )
169+ let finalResult = buildFinalResult (
170+ processingTime: metrics. processingTime,
171+ sampleCount: metrics. sampleCount
172+ )
226173
227174 logger. info ( " Final transcription: \( finalResult. text. count) characters " )
228175 return finalResult
@@ -282,21 +229,123 @@ public actor StreamingAsrManager {
282229
283230 accumulatedTokens. append ( contentsOf: tokens)
284231
285- if timestamps. count == tokens. count {
286- accumulatedTokenTimestamps. append ( contentsOf: timestamps)
287- } else if !timestamps. isEmpty {
232+ appendTokenMetadata (
233+ timestamps,
234+ expectedCount: tokens. count,
235+ into: & accumulatedTokenTimestamps,
236+ label: " timestamp "
237+ )
238+
239+ appendTokenMetadata (
240+ confidences,
241+ expectedCount: tokens. count,
242+ into: & accumulatedTokenConfidences,
243+ label: " confidence "
244+ )
245+ }
246+
247+ private func appendTokenMetadata< Value> (
248+ _ values: [ Value ] ,
249+ expectedCount: Int ,
250+ into storage: inout [ Value ] ,
251+ label: String
252+ ) {
253+ guard !values. isEmpty else { return }
254+
255+ guard values. count == expectedCount else {
288256 logger. warning (
289- " Token timestamp count ( \( timestamps. count) ) does not match token count ( \( tokens. count) ) "
257+ " Token \( label) count ( \( values. count) ) does not match token count ( \( expectedCount) ) "
258+ )
259+ return
260+ }
261+
262+ storage. append ( contentsOf: values)
263+ }
264+
265+ private func resolveFinalMetrics( at now: Date ) -> ( sampleCount: Int , processingTime: TimeInterval ) {
266+ let elapsedTime = startTime. map { now. timeIntervalSince ( $0) } ?? 0
267+ let minimumProcessingTime : TimeInterval = 1e-6
268+
269+ let maxTimestampFrame = accumulatedTokenTimestamps. max ( ) ?? 0
270+ let derivedSampleCount = maxTimestampFrame > 0
271+ ? ( maxTimestampFrame + 1 ) * ASRConstants. samplesPerEncoderFrame : 0
272+ let sampleCount = max ( totalSamplesProcessed, derivedSampleCount)
273+ let processingTime = sampleCount > 0 ? max ( elapsedTime, minimumProcessingTime) : elapsedTime
274+
275+ return ( sampleCount, processingTime)
276+ }
277+
278+ private func buildFinalResult( processingTime: TimeInterval , sampleCount: Int ) -> ASRResult {
279+ guard let asrManager = asrManager, !accumulatedTokens. isEmpty else {
280+ let duration = TimeInterval ( sampleCount) / TimeInterval( config. asrConfig. sampleRate)
281+ return ASRResult (
282+ text: finalTranscriptText ( ) ,
283+ confidence: 1.0 ,
284+ duration: duration,
285+ processingTime: processingTime,
286+ tokenTimings: nil
290287 )
291288 }
292289
293- if confidences. count == tokens. count {
294- accumulatedTokenConfidences. append ( contentsOf: confidences)
295- } else if !confidences. isEmpty {
290+ let metadata = finalTokenMetadata ( forTokenCount: accumulatedTokens. count)
291+
292+ return asrManager. processTranscriptionResult (
293+ tokenIds: accumulatedTokens,
294+ timestamps: metadata. timestamps,
295+ confidences: metadata. confidences,
296+ encoderSequenceLength: 0 ,
297+ audioSamples: [ ] ,
298+ processingTime: processingTime,
299+ totalSampleCount: sampleCount
300+ )
301+ }
302+
303+ private func finalTokenMetadata( forTokenCount count: Int ) -> ( timestamps: [ Int ] , confidences: [ Float ] ) {
304+ let timestamps = sanitizedFinalValues (
305+ accumulatedTokenTimestamps,
306+ expectedCount: count,
307+ label: " timestamp " ,
308+ omissionDetail: " omitting token timings "
309+ )
310+
311+ let confidences = sanitizedFinalValues (
312+ accumulatedTokenConfidences,
313+ expectedCount: count,
314+ label: " confidence " ,
315+ omissionDetail: " omitting confidence data "
316+ )
317+
318+ return ( timestamps, confidences)
319+ }
320+
321+ private func sanitizedFinalValues< Value> (
322+ _ values: [ Value ] ,
323+ expectedCount: Int ,
324+ label: String ,
325+ omissionDetail: String
326+ ) -> [ Value ] {
327+ guard !values. isEmpty else { return [ ] }
328+
329+ guard values. count == expectedCount else {
296330 logger. warning (
297- " Token confidence count (\( confidences . count) ) does not match token count ( \( tokens . count ) ) "
331+ " Final token \( label ) count (\( values . count) ) does not match token count ( \( expectedCount ) ); \( omissionDetail ) "
298332 )
333+ return [ ]
334+ }
335+
336+ return values
337+ }
338+
339+ private func finalTranscriptText( ) -> String {
340+ var components : [ String ] = [ ]
341+ if !confirmedTranscript. isEmpty {
342+ components. append ( confirmedTranscript)
343+ }
344+ if !volatileTranscript. isEmpty {
345+ components. append ( volatileTranscript)
299346 }
347+ return components. joined ( separator: " " )
348+ . trimmingCharacters ( in: . whitespaces)
300349 }
301350
302351 // MARK: - Private Methods
@@ -324,10 +373,8 @@ public actor StreamingAsrManager {
324373 }
325374
326375 let window = Array ( sampleBuffer [ startIdx..< endIdx] )
327- let actualLeftSecs = Double ( nextWindowCenterStart - leftStartAbs) / Double( sampleRate)
328376 await processWindow (
329377 window,
330- actualLeftSeconds: actualLeftSecs,
331378 windowStartSample: leftStartAbs
332379 )
333380
@@ -366,10 +413,8 @@ public actor StreamingAsrManager {
366413 if startIdx < 0 || endIdx > sampleBuffer. count || startIdx >= endIdx { break }
367414
368415 let window = Array ( sampleBuffer [ startIdx..< endIdx] )
369- let actualLeftSecs = Double ( nextWindowCenterStart - leftStartAbs) / Double( sampleRate)
370416 await processWindow (
371417 window,
372- actualLeftSeconds: actualLeftSecs,
373418 windowStartSample: leftStartAbs
374419 )
375420
@@ -390,7 +435,6 @@ public actor StreamingAsrManager {
390435 /// Process a single assembled window: [left, chunk, right]
391436 private func processWindow(
392437 _ windowSamples: [ Float ] ,
393- actualLeftSeconds _: Double ,
394438 windowStartSample: Int
395439 ) async {
396440 guard let asrManager = asrManager else { return }
0 commit comments