From 1f725abec29ad465f3c73c28c360818139761212 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 03 Jul 2026 05:28:17 +0800
Subject: [PATCH] Record R221 2.2.2 release candidate

---
 C1.source/privatevoice.src/cmd/benchmark-recognition/main.go |  115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 115 insertions(+), 0 deletions(-)

diff --git a/C1.source/privatevoice.src/cmd/benchmark-recognition/main.go b/C1.source/privatevoice.src/cmd/benchmark-recognition/main.go
new file mode 100644
index 0000000..28d7a17
--- /dev/null
+++ b/C1.source/privatevoice.src/cmd/benchmark-recognition/main.go
@@ -0,0 +1,115 @@
+package main
+
+import (
+	"encoding/binary"
+	"flag"
+	"fmt"
+	"os"
+	"time"
+	"voicesnap/internal/engine"
+)
+
+func main() {
+	wavPath := flag.String("wav", "", "16kHz mono PCM16 WAV file")
+	runs := flag.Int("runs", 3, "number of recognition runs")
+	flag.Parse()
+
+	if *wavPath == "" {
+		fmt.Fprintln(os.Stderr, "missing -wav")
+		os.Exit(2)
+	}
+	if *runs < 1 {
+		*runs = 1
+	}
+
+	samples, sampleRate, err := readPCM16WAV(*wavPath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "read wav: %v\n", err)
+		os.Exit(1)
+	}
+	if sampleRate != 16000 {
+		fmt.Fprintf(os.Stderr, "unsupported sample rate %d: provide 16kHz mono PCM16 WAV\n", sampleRate)
+		os.Exit(1)
+	}
+
+	eng, err := engine.New()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "init engine: %v\n", err)
+		os.Exit(1)
+	}
+	defer eng.Close()
+
+	audioMS := int64(len(samples)) * 1000 / 16000
+	fmt.Printf("engine=%q samples=%d audio_ms=%d runs=%d\n", eng.HardwareInfo(), len(samples), audioMS, *runs)
+	var total int64
+	for i := 1; i <= *runs; i++ {
+		start := time.Now()
+		text, err := eng.Recognize(samples)
+		elapsedMS := time.Since(start).Milliseconds()
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "run=%d error=%v\n", i, err)
+			os.Exit(1)
+		}
+		total += elapsedMS
+		fmt.Printf("run=%d recognize_ms=%d rtf=%.3f text_runes=%d\n", i, elapsedMS, float64(elapsedMS)/float64(audioMS), len([]rune(text)))
+	}
+	fmt.Printf("avg_recognize_ms=%d\n", total/int64(*runs))
+}
+
+func readPCM16WAV(path string) ([]float32, int, error) {
+	data, err := os.ReadFile(path)
+	if err != nil {
+		return nil, 0, err
+	}
+	if len(data) < 12 || string(data[0:4]) != "RIFF" || string(data[8:12]) != "WAVE" {
+		return nil, 0, fmt.Errorf("not a RIFF/WAVE file")
+	}
+
+	var audioFormat, channels, bitsPerSample uint16
+	var sampleRate uint32
+	var pcm []byte
+	for offset := 12; offset+8 <= len(data); {
+		chunkID := string(data[offset : offset+4])
+		chunkSize := int(binary.LittleEndian.Uint32(data[offset+4 : offset+8]))
+		chunkStart := offset + 8
+		chunkEnd := chunkStart + chunkSize
+		if chunkEnd > len(data) {
+			return nil, 0, fmt.Errorf("truncated %s chunk", chunkID)
+		}
+
+		switch chunkID {
+		case "fmt ":
+			if chunkSize < 16 {
+				return nil, 0, fmt.Errorf("fmt chunk too small")
+			}
+			audioFormat = binary.LittleEndian.Uint16(data[chunkStart : chunkStart+2])
+			channels = binary.LittleEndian.Uint16(data[chunkStart+2 : chunkStart+4])
+			sampleRate = binary.LittleEndian.Uint32(data[chunkStart+4 : chunkStart+8])
+			bitsPerSample = binary.LittleEndian.Uint16(data[chunkStart+14 : chunkStart+16])
+		case "data":
+			pcm = data[chunkStart:chunkEnd]
+		}
+
+		offset = chunkEnd
+		if offset%2 == 1 {
+			offset++
+		}
+	}
+
+	if audioFormat != 1 || channels != 1 || bitsPerSample != 16 {
+		return nil, 0, fmt.Errorf("unsupported WAV format: audio_format=%d channels=%d bits_per_sample=%d; want PCM16 mono", audioFormat, channels, bitsPerSample)
+	}
+	if len(pcm) == 0 {
+		return nil, 0, fmt.Errorf("missing data chunk")
+	}
+	if len(pcm)%2 != 0 {
+		return nil, 0, fmt.Errorf("odd PCM data length")
+	}
+
+	samples := make([]float32, len(pcm)/2)
+	for i := range samples {
+		v := int16(binary.LittleEndian.Uint16(pcm[i*2 : i*2+2]))
+		samples[i] = float32(v) / 32768.0
+	}
+	return samples, int(sampleRate), nil
+}

--
Gitblit v1.9.3