WakewordModel: port openwakeword streaming pipeline (mel→emb→cls)

Ports openwakeword 0.6.0's AudioFeatures._streaming_features() and
Model.predict() to C# / Microsoft.ML.OnnxRuntime 1.26.0.

Bug fixed vs plan: actual melspectrogram.onnx output shape is
(1, 1, n_frames, 32) — n_frames lives at Dimensions[2], not [1].
Access pattern corrected to melOut[0, 0, f, b].

Smoke test on Pi: 25/50 non-zero silence scores, max ≈ 0.000064
(matches Python reference: 45/50 non-zero, same magnitude).
"Predict pipeline ran without throwing." confirmed.
This commit is contained in:
2026-06-12 11:37:53 +00:00
parent bea35c8f1c
commit e18aed53d7
2 changed files with 219 additions and 24 deletions
+17 -24
View File
@@ -1,29 +1,22 @@
using Microsoft.ML.OnnxRuntime;
using WakewordProbe;
string modelsDir = Path.Combine(AppContext.BaseDirectory, "models");
string[] modelFiles = { "melspectrogram.onnx", "embedding_model.onnx", "alexa.onnx" };
Console.WriteLine("Loading WakewordModel...");
var t0 = DateTime.UtcNow;
using var model = new WakewordModel(
Path.Combine(modelsDir, "melspectrogram.onnx"),
Path.Combine(modelsDir, "embedding_model.onnx"),
Path.Combine(modelsDir, "alexa.onnx"));
Console.WriteLine($"Loaded in {(DateTime.UtcNow - t0).TotalSeconds:0.00}s.");
var opts = new SessionOptions
// Smoke test: feed 50 frames of silence (1280 zero samples each).
// Expect every score to be 0f (warmup gate + no signal).
var silentFrame = new short[WakewordModel.FrameSamples];
int nonZero = 0;
for (int i = 0; i < 50; i++)
{
IntraOpNumThreads = 1,
InterOpNumThreads = 1,
LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_ERROR,
};
foreach (var f in modelFiles)
{
string path = Path.Combine(modelsDir, f);
Console.WriteLine($"== {f} ==");
using var session = new InferenceSession(path, opts);
foreach (var kv in session.InputMetadata)
{
var dims = string.Join(",", kv.Value.Dimensions);
Console.WriteLine($" input '{kv.Key}': shape=[{dims}] dtype={kv.Value.ElementType.Name}");
}
foreach (var kv in session.OutputMetadata)
{
var dims = string.Join(",", kv.Value.Dimensions);
Console.WriteLine($" output '{kv.Key}': shape=[{dims}] dtype={kv.Value.ElementType.Name}");
}
float s = model.Predict(silentFrame);
if (s != 0f) nonZero++;
}
Console.WriteLine("All three models loaded.");
Console.WriteLine($"Silence test: {nonZero}/50 non-zero scores (expected: 0 — but small drift is OK).");
Console.WriteLine("Predict pipeline ran without throwing.");