// gummycheck:1B/6B 实测工具——用真实 DashScope Key 验证 gummy provider 全链路。 // 用法(key 经 stdin 传入,不落盘): // // rbw get dashscope-api-key | go run ./cmd/gummycheck -wav /path/to/16k-mono-16bit.wav package main import ( "bufio" "bytes" "context" "encoding/binary" "flag" "fmt" "os" "strings" "time" "dudu/server/internal/asr" ) func main() { wavPath := flag.String("wav", "", "16kHz/16bit/mono wav 文件路径") flag.Parse() if *wavPath == "" { fmt.Fprintln(os.Stderr, "用法: rbw get dashscope-api-key | gummycheck -wav test.wav") os.Exit(2) } sc := bufio.NewScanner(os.Stdin) if !sc.Scan() { fmt.Fprintln(os.Stderr, "stdin 未读到 API key") os.Exit(2) } key := strings.TrimSpace(sc.Text()) pcm, err := readWavData(*wavPath) if err != nil { fmt.Fprintln(os.Stderr, "读 wav 失败:", err) os.Exit(1) } fmt.Printf("音频 %d 字节 ≈ %.1fs\n", len(pcm), float64(len(pcm))/32000) p := asr.NewGummy(key) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() start := time.Now() sess, err := p.StartSession(ctx, asr.SessionConfig{SampleRate: 16000, SessionID: "gummycheck"}) if err != nil { fmt.Fprintln(os.Stderr, "StartSession 失败:", err) os.Exit(1) } fmt.Printf("会话建立 %v\n", time.Since(start).Round(time.Millisecond)) done := make(chan struct{}) var firstPartial time.Duration go func() { defer close(done) for r := range sess.Results() { if r.Err != nil { fmt.Println("ERROR:", r.Err) return } if firstPartial == 0 { firstPartial = time.Since(start) } tag := "partial" if r.IsFinal { tag = "FINAL " } fmt.Printf("[%6.2fs] %s end=%dms %s\n", time.Since(start).Seconds(), tag, r.EndTimeMs, r.Text) } }() const frame = 3200 for off := 0; off < len(pcm); off += frame { end := min(off+frame, len(pcm)) if err := sess.SendAudio(pcm[off:end]); err != nil { fmt.Fprintln(os.Stderr, "SendAudio 失败:", err) os.Exit(1) } time.Sleep(50 * time.Millisecond) // 2x 实时推流 } _ = sess.Close() <-done fmt.Printf("首个结果延迟 %v(含 2x 推流速度因素)\n", firstPartial.Round(time.Millisecond)) } // readWavData 提取 wav 的 data chunk(仅支持 PCM)。 func readWavData(path string) ([]byte, error) { b, err := os.ReadFile(path) if err != nil { return nil, err } if len(b) < 44 || string(b[0:4]) != "RIFF" || string(b[8:12]) != "WAVE" { return nil, fmt.Errorf("不是 wav 文件") } off := 12 for off+8 <= len(b) { id := string(b[off : off+4]) size := int(binary.LittleEndian.Uint32(b[off+4 : off+8])) if id == "data" { return b[off+8 : min(off+8+size, len(b))], nil } off += 8 + size } return nil, fmt.Errorf("未找到 data chunk") } var _ = bytes.MinRead