@@ -109,6 +109,29 @@ static const char * const prediction[] = TFLM_CATEGORY_DATA;
109109#define TFLM_MFCC_HOP_MS 20
110110#define TFLM_INFERENCE_STRIDE_HOPS 25
111111
112+ /* Soft mel-log AGC (units: Q9.23, matches MFCC output). One decade = +10 dB.
113+ * Target 0 dB, floor -20 dB. Attack: instant clamp so peak+gain never exceeds
114+ * MEL_CLIP_MAX_Q23 (+1.0, +10 dB). Release: additive step per hop during active
115+ * speech (vad_flag == 1), chosen so recovery is ~0.5 dB/sec (hop=20 ms, 50 hops/sec).
116+ */
117+ #define TFLM_AGC_GAIN_TARGET_Q23 0 /* 0 dB */
118+ #define TFLM_AGC_GAIN_FLOOR_Q23 -16777216 /* -20 dB, int32(-20 * 0.1 * 2^23) */
119+ #define TFLM_AGC_RELEASE_STEP_Q23 8389 /* 0.5 dB/s, int32((0.5 * 0.1 / 50) * 2^23) */
120+
121+ /* This needs to match with sof_tflm_train.py and sof_tflm_verify.py.
122+ *
123+ * The range -1.0 to +1.0 of Q9.23 Mel values is scaled to +/-1.0 Q1.7.
124+ *
125+ * in = [-1.0 1.0]; out = [-1 1];
126+ * offset = -(in(1) + in(2)) / 2 = 0
127+ * scale = (out(2) - out(1)) / (in(2) - in(1)) = 1.0
128+ */
129+ #define MEL_OFFSET_Q23 0 /* 0 */
130+ #define MEL_SCALE_Q30 (1 << 30) /* 1.0 in Q30 */
131+ #define MEL_CLIP_MAX_Q23 (1 << 23) /* +1.0 in Q23 */
132+ #define MEL_CLIP_MAX_Q7 127
133+ #define MEL_CLIP_MIN_Q7 -128
134+
112135struct tflm_comp_data {
113136 struct comp_data_blob_handler * model_handler ;
114137 struct tf_classify tfc ;
@@ -122,6 +145,10 @@ struct tflm_comp_data {
122145 /* Per-instance sliding window and inference cadence. */
123146 int8_t feature_buf [TFLM_FEATURE_ELEM_COUNT ];
124147 int frame_counter ;
148+ /* Bitmask of VAD flags for the TFLM_FEATURE_COUNT frames in feature_buf. */
149+ uint64_t vad_history ;
150+ /* Persistent AGC gain applied to every Q9.23 mel value (see AGC defines). */
151+ int32_t agc_gain_q23 ;
125152 /* Per-instance shutdown-summary counters. */
126153 uint32_t category_totals [TFLM_CATEGORY_COUNT ];
127154 uint32_t total_inferences ;
@@ -190,15 +217,11 @@ static __maybe_unused void tflm_send_keyword_notification(struct processing_modu
190217
191218/* Shared TFLM backend state. speech.cc has one static arena/model/interpreter,
192219 * so TF_SetModel/TF_InitOps must run only once no matter how many tflmcly
193- * instances the topology creates; secondary instances copy the resolved input
194- * quant params from the first-init cache below .
220+ * instances the topology creates; secondary instances attach to the first-init
221+ * instance .
195222 */
196223static bool g_tflm_initialized ;
197224static int g_tflm_instance_count ;
198- static float g_tflm_shared_input_scale ;
199- static int g_tflm_shared_input_zero_point ;
200- static int32_t g_tflm_shared_input_mult ;
201- static int32_t g_tflm_shared_input_shift ;
202225static int g_tflm_shared_categories ;
203226
204227__cold static void tflm_log_summary_at_shutdown (struct processing_module * mod )
@@ -251,6 +274,7 @@ __cold static int tflm_init(struct processing_module *mod)
251274 md -> private = cd ;
252275 cd -> tfc .categories = TFLM_CATEGORY_COUNT ;
253276 cd -> drain_req_ms = TFLM_KPB_DRAIN_REQ_MS ;
277+ cd -> agc_gain_q23 = TFLM_AGC_GAIN_TARGET_Q23 ;
254278#if CONFIG_AMS
255279 cd -> kpd_uuid_id = AMS_INVALID_MSG_TYPE ;
256280#endif
@@ -283,39 +307,6 @@ __cold static int tflm_set_config(struct processing_module *mod, uint32_t param_
283307 return 0 ;
284308}
285309
286- /*
287- * MFCC's mel-log output is Q9.23 fixed point, normalized by the mel40.conf
288- * profile's mel_offset/mel_scale/top_db tuning to approximately the 0..1
289- * range (see the dynamic_mmax clamp + offset + scale in mfcc_common.c).
290- * We requantize that into int8 features using the model's own input tensor
291- * scale/zero_point (populated by TF_InitOps()/Init_Interpreter() in speech.cc).
292- *
293- * The float-domain operation is
294- * int8_pre = round((mel_q23 / 2^23) / input_scale) + input_zero_point
295- * which we execute in integer math using a pre-decomposed multiplier and
296- * right-shift so this per-sample hot path (40 features x ~50 hops/sec)
297- * needs no float divides or FPU support:
298- * int8_pre = round((int64)mel_q23 * input_mult >> input_shift) + input_zero_point
299- * with input_mult/input_shift set once at model prepare time.
300- */
301- static inline int8_t mfcc_mel_q23_to_int8 (int32_t mel_q23 , int32_t input_mult ,
302- int input_shift , int input_zero_point )
303- {
304- int64_t prod = (int64_t )mel_q23 * (int64_t )input_mult ;
305- int64_t abs_prod = prod >= 0 ? prod : - prod ;
306- int64_t rounding = (int64_t )1 << (input_shift - 1 );
307- int32_t abs_scaled = (int32_t )((abs_prod + rounding ) >> input_shift );
308- int32_t scaled = prod >= 0 ? abs_scaled : - abs_scaled ;
309-
310- scaled += input_zero_point ;
311- if (scaled > 127 )
312- scaled = 127 ;
313- else if (scaled < -128 )
314- scaled = -128 ;
315-
316- return (int8_t )scaled ;
317- }
318-
319310/*
320311 * This expects features from 16kHz mono 16 bit input stream.
321312 *
@@ -332,9 +323,6 @@ static inline int8_t mfcc_mel_q23_to_int8(int32_t mel_q23, int32_t input_mult,
332323 * features in the input buffer.
333324 */
334325
335-
336-
337-
338326static int tflm_process (struct processing_module * mod ,
339327 struct sof_source * * sources , int num_of_sources ,
340328 struct sof_sink * * sinks , int num_of_sinks )
@@ -396,45 +384,61 @@ static int tflm_process(struct processing_module *mod,
396384 * TFLM_FEATURE_SIZE int32 Q9.23 mel-log values into
397385 * int8 features for this hop.
398386 */
387+ const struct mfcc_data_header * hdr =
388+ (const struct mfcc_data_header * )hop_scratch ;
399389 const int32_t * mel = (const int32_t * )
400390 (hop_scratch + sizeof (struct mfcc_data_header ));
401391 int8_t hop_features [TFLM_FEATURE_SIZE ];
402392
403- /* Match training preprocessing exactly: clip Q9.23 mel to
404- * [-1.0, +4.0] and then rescale to [-1.0, +1.0] centered
405- * on 0 (np.clip + (X - 1.5) / 2.5 in train_wov_tflm.py).
406- * TFLite calibration on that range picks
407- * input_scale=1/128, input_zero_point=0, so the model
408- * consumes the full int8 dynamic range.
409- */
410- enum {
411- MEL_CLIP_MIN_Q23 = - (1 << 23 ), /* -1.0 */
412- MEL_CLIP_MAX_Q23 = (4 << 23 ), /* +4.0 */
413- MEL_CENTER_Q23 = (3 << 22 ), /* +1.5 */
414- TWO_FIFTHS_Q31 = 858993459 , /* round(0.4 * (1<<31)) */
415- };
416-
417- for (int i = 0 ; i < TFLM_FEATURE_SIZE ; i ++ ) {
418- int32_t mel_c = mel [i ];
419-
420- if (mel_c < MEL_CLIP_MIN_Q23 )
421- mel_c = MEL_CLIP_MIN_Q23 ;
422- else if (mel_c > MEL_CLIP_MAX_Q23 )
423- mel_c = MEL_CLIP_MAX_Q23 ;
424-
425- /* Fold (mel - 1.5) * (2/5) into one Q1.31 multiply. */
426- mel_c = Q_MULTSR_32X32 ((int64_t )(mel_c - MEL_CENTER_Q23 ),
427- TWO_FIFTHS_Q31 , 23 , 31 , 23 );
428-
429- hop_features [i ] = mfcc_mel_q23_to_int8 (mel_c ,
430- cd -> tfc .input_mult , cd -> tfc .input_shift ,
431- cd -> tfc .input_zero_point );
393+ /* Update VAD history bitmask (tracks last TFLM_FEATURE_COUNT hops). */
394+ cd -> vad_history = ((cd -> vad_history << 1 ) | (hdr -> vad_flag ? 1ULL : 0ULL )) &
395+ ((1ULL << TFLM_FEATURE_COUNT ) - 1 );
396+
397+ /* AGC: attack on this hop's peak (always track energy). */
398+ int32_t hop_peak_q23 = mel [0 ];
399+ for (int i = 1 ; i < TFLM_FEATURE_SIZE ; i ++ )
400+ if (mel [i ] > hop_peak_q23 )
401+ hop_peak_q23 = mel [i ];
402+
403+ int32_t clip_headroom_q23 = MEL_CLIP_MAX_Q23 - hop_peak_q23 ;
404+ if (cd -> agc_gain_q23 > clip_headroom_q23 )
405+ cd -> agc_gain_q23 = clip_headroom_q23 ;
406+ if (cd -> agc_gain_q23 < TFLM_AGC_GAIN_FLOOR_Q23 )
407+ cd -> agc_gain_q23 = TFLM_AGC_GAIN_FLOOR_Q23 ;
408+
409+ int32_t agc_gain_q23 = cd -> agc_gain_q23 ;
410+
411+ comp_info (mod -> dev , "tflm agc: peak_q23=%d gain_q23=%d" ,
412+ hop_peak_q23 , agc_gain_q23 );
413+
414+ /* Requantize to int8: skip if no speech in the 49-hop window (fill with silence) */
415+ if (cd -> vad_history ) {
416+ for (int i = 0 ; i < TFLM_FEATURE_SIZE ; i ++ ) {
417+ int32_t mel_c = mel [i ] + agc_gain_q23 ;
418+
419+ /* Rescale asymmetrically with offset and gain. */
420+ mel_c = Q_MULTSR_32X32 ((int64_t )(mel_c + MEL_OFFSET_Q23 ),
421+ MEL_SCALE_Q30 , 23 , 30 , 7 );
422+ if (mel_c > MEL_CLIP_MAX_Q7 )
423+ mel_c = MEL_CLIP_MAX_Q7 ;
424+ else if (mel_c < MEL_CLIP_MIN_Q7 )
425+ mel_c = MEL_CLIP_MIN_Q7 ;
426+
427+ hop_features [i ] = (int8_t )mel_c ;
428+ }
429+ } else {
430+ memset (hop_features , MEL_CLIP_MIN_Q7 , sizeof (hop_features ));
431+ }
432+
433+ /* Release: creep back toward 0 dB target. */
434+ if (cd -> agc_gain_q23 < TFLM_AGC_GAIN_TARGET_Q23 ) {
435+ cd -> agc_gain_q23 += TFLM_AGC_RELEASE_STEP_Q23 ;
436+ if (cd -> agc_gain_q23 > TFLM_AGC_GAIN_TARGET_Q23 )
437+ cd -> agc_gain_q23 = TFLM_AGC_GAIN_TARGET_Q23 ;
432438 }
433439
434440#if CONFIG_COMP_TENSORFLOW_DEBUG_TRACE
435441 {
436- const struct mfcc_data_header * hdr =
437- (const struct mfcc_data_header * )hop_scratch ;
438442 static int dbg_hop_count ;
439443 int32_t mel_min = mel [0 ], mel_max = mel [0 ];
440444 int8_t f_min = hop_features [0 ], f_max = hop_features [0 ];
@@ -448,11 +452,12 @@ static int tflm_process(struct processing_module *mod,
448452 if (hop_features [i ] > f_max ) f_max = hop_features [i ];
449453 }
450454 snprintk (dbg_buf , sizeof (dbg_buf ),
451- "[DBG hop %d] vad=%d E=%d Ne=%d mel_min=%d mel_max=%d f_min=%d f_max=%d" ,
455+ "[DBG hop %d] vad=%d E=%d Ne=%d mel_min=%d mel_max=%d f_min=%d f_max=%d agc_q23=%d " ,
452456 dbg_hop_count , (int )hdr -> vad_flag ,
453457 (int )hdr -> energy , (int )hdr -> noise_energy ,
454458 mel_min , mel_max ,
455- f_min , f_max );
459+ f_min , f_max ,
460+ (int )cd -> agc_gain_q23 );
456461 sof_ut_log (dbg_buf );
457462 }
458463#endif
@@ -506,6 +511,14 @@ static int tflm_process(struct processing_module *mod,
506511 if (cd -> frame_counter >= TFLM_INFERENCE_STRIDE_HOPS ) {
507512 cd -> frame_counter = 0 ;
508513
514+ /* VAD gate: skip inference computation if no active speech in the 49-hop window */
515+ if (!cd -> vad_history ) {
516+ #if CONFIG_COMP_TENSORFLOW_DEBUG_TRACE
517+ sof_ut_log ("[DBG inference] skipped: VAD=0 in entire 49-hop window" );
518+ #endif
519+ continue ;
520+ }
521+
509522 cd -> tfc .audio_features = feature_buf ;
510523 cd -> tfc .audio_data_size = TFLM_FEATURE_ELEM_COUNT ;
511524
@@ -605,8 +618,9 @@ static int tflm_process(struct processing_module *mod,
605618 }
606619#endif
607620
608- // Only announce a keyword hit for real keyword classes
609- // (indices >= 2, i.e. not silence/unknown).
621+ /* Only announce a keyword hit for real keyword classes
622+ * (indices >= 2, i.e. not silence/unknown).
623+ */
610624 if (max_idx >= 2 && max_score >= 0.50f ) {
611625 char kw_buf [96 ];
612626 int max_pct = (int )(max_score * 100.0f );
@@ -651,14 +665,7 @@ static int tflm_prepare(struct processing_module *mod,
651665 printk ("[TFLM PREPARE] tflm_prepare called, loading model...\n" );
652666
653667 if (g_tflm_initialized ) {
654- /* Shared TFLM engine already up. Copy the resolved input quant
655- * params so this instance's requantize path uses the same
656- * scale/zero_point/mult/shift as the first-initialized instance.
657- */
658- cd -> tfc .input_scale = g_tflm_shared_input_scale ;
659- cd -> tfc .input_zero_point = g_tflm_shared_input_zero_point ;
660- cd -> tfc .input_mult = g_tflm_shared_input_mult ;
661- cd -> tfc .input_shift = g_tflm_shared_input_shift ;
668+ /* Shared TFLM engine already up. */
662669 cd -> tfc .categories = g_tflm_shared_categories ;
663670 printk ("[TFLM PREPARE] shared engine already initialized; attach instance\n" );
664671 goto post_init ;
@@ -677,17 +684,11 @@ static int tflm_prepare(struct processing_module *mod,
677684 return ret ;
678685 }
679686
680- g_tflm_shared_input_scale = cd -> tfc .input_scale ;
681- g_tflm_shared_input_zero_point = cd -> tfc .input_zero_point ;
682- g_tflm_shared_input_mult = cd -> tfc .input_mult ;
683- g_tflm_shared_input_shift = cd -> tfc .input_shift ;
684687 g_tflm_shared_categories = cd -> tfc .categories ;
685688 g_tflm_initialized = true;
686689 printk ("[TFLM PREPARE] TFLM model & ops initialized successfully!\n" );
687690 printk ("[TFLM PREPARE] arena_used=%zu / capacity=%zu bytes\n" ,
688691 TF_ArenaUsedBytes (), TF_ArenaCapacity ());
689- printk ("[DBG quant] input_scale_x1e6=%d input_zero_point=%d\n" ,
690- (int )(cd -> tfc .input_scale * 1000000.0f ), cd -> tfc .input_zero_point );
691692
692693post_init :
693694#if CONFIG_AMS
@@ -711,6 +712,9 @@ static int tflm_reset(struct processing_module *mod)
711712 struct tflm_comp_data * cd = module_get_private_data (mod );
712713
713714 tflm_log_summary_at_shutdown (mod );
715+ cd -> vad_history = 0 ;
716+ cd -> frame_counter = 0 ;
717+ memset (cd -> feature_buf , 0 , sizeof (cd -> feature_buf ));
714718#if CONFIG_AMS
715719 if (cd -> kpd_uuid_id != AMS_INVALID_MSG_TYPE ) {
716720 int ret = ams_helper_unregister_producer (mod -> dev ,
@@ -730,7 +734,6 @@ static const struct module_interface tflmcly_interface = {
730734 .prepare = tflm_prepare ,
731735 .process = tflm_process ,
732736 .set_configuration = tflm_set_config ,
733- // .get_configuration = tflm_get_config,
734737 .reset = tflm_reset ,
735738 .free = tflm_free
736739};
0 commit comments