40760aa884
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调 - desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导) - android:Compose 主 App + IME(键盘内录音直传) - ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写) - design/design-pipeline:设计系统 + token 导出 iOS/Android 主题 - doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
package asr
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestGummyLive 9A gummy 实测(需真实凭证,无则跳过):
|
|
//
|
|
// rbw get dashscope-api-key | { read -r K; DASHSCOPE_API_KEY="$K" GUMMY_TEST_WAV=/path/16k.wav go test ./internal/asr -run TestGummyLive -v; }
|
|
func TestGummyLive(t *testing.T) {
|
|
key := os.Getenv("DASHSCOPE_API_KEY")
|
|
wav := os.Getenv("GUMMY_TEST_WAV")
|
|
if key == "" || wav == "" {
|
|
t.Skip("DASHSCOPE_API_KEY / GUMMY_TEST_WAV 未设置,跳过 gummy 实测")
|
|
}
|
|
pcm, err := readWavDataT(t, wav)
|
|
if err != nil {
|
|
t.Fatalf("读 wav: %v", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
sess, err := NewGummy(key).StartSession(ctx, SessionConfig{SampleRate: 16000, SessionID: "live-test"})
|
|
if err != nil {
|
|
t.Fatalf("StartSession: %v", err)
|
|
}
|
|
|
|
var partials, finals int
|
|
var lastFinal string
|
|
var maxEndMs int64
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for r := range sess.Results() {
|
|
if r.Err != nil {
|
|
t.Errorf("result err: %v", r.Err)
|
|
return
|
|
}
|
|
if r.IsFinal {
|
|
finals++
|
|
lastFinal = r.Text
|
|
} else {
|
|
partials++
|
|
}
|
|
if r.EndTimeMs > maxEndMs {
|
|
maxEndMs = r.EndTimeMs
|
|
}
|
|
}
|
|
}()
|
|
|
|
for off := 0; off < len(pcm); off += 3200 {
|
|
end := min(off+3200, len(pcm))
|
|
if err := sess.SendAudio(pcm[off:end]); err != nil {
|
|
t.Fatalf("SendAudio: %v", err)
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
_ = sess.Close()
|
|
<-done
|
|
|
|
if partials == 0 || finals == 0 {
|
|
t.Fatalf("期望 partial>0 且 final>0,实得 partial=%d final=%d", partials, finals)
|
|
}
|
|
if strings.TrimSpace(lastFinal) == "" {
|
|
t.Fatal("final 文本为空")
|
|
}
|
|
audioMs := int64(len(pcm)) * 1000 / 32000
|
|
if maxEndMs <= 0 || maxEndMs > audioMs+3000 {
|
|
t.Fatalf("provider 时间戳异常: end=%dms audio=%dms", maxEndMs, audioMs)
|
|
}
|
|
t.Logf("partials=%d finals=%d end=%dms final=%q", partials, finals, maxEndMs, lastFinal)
|
|
}
|
|
|
|
func readWavDataT(t *testing.T, path string) ([]byte, error) {
|
|
t.Helper()
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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, os.ErrInvalid
|
|
}
|