Files
Cyrene_v2/internal/runtime/multimodal/voice/service.go
T
AskaEth b0fbb8bc0d
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(comments): 注释中文化 · internal/runtime/multimodal(21 文件,纯替换)
- 只翻注释/文档字符串,代码零变化:go/scanner 词法流比对 21 文件 token+字面量全等,增删行数 1:1(467/467)
- 刻意保留非散文英文:JSON Schema description、工具 Description() 返回值、adapter prompt、API 路径与命令示例、环境变量名
- 逐行确认代码前缀未变,未触碰其他模块
2026-10-04 12:28:16 +08:00

182 lines
4.7 KiB
Go

package voice
import (
"context"
"fmt"
"sync"
)
// Service 在后台工作池上运行 STT/TTS 请求,使调用方
// goroutine(智能体循环)绝不会被提供方延迟阻塞。每个
// 请求入队到 jobs channel,并在每个 job 自己的结果
// channel 上应答;调用方 context 被取消或服务已关闭时,
// 调用方立刻解除阻塞,且不泄漏任何 worker goroutine。它可安全并发使用。
type Service struct {
chain *Chain
jobs chan job
bgCtx context.Context
cancel context.CancelFunc
nWorkers int
wg sync.WaitGroup
mu sync.RWMutex
closed bool
}
// ServiceOption 定制 Service。
type ServiceOption func(*Service)
// WithWorkers 设置工作池大小。小于等于 0 的值被忽略,
// 保留默认的单 worker。
func WithWorkers(n int) ServiceOption {
return func(s *Service) {
if n > 0 {
s.nWorkers = n
}
}
}
// jobKind 选择 worker 执行的操作。
type jobKind int
const (
jobSTT jobKind = iota
jobTTS
)
// job 是调用方入队的一个请求。
type job struct {
kind jobKind
audio Audio
text string
res chan jobResult
}
// jobResult 承载写回调用方的结果。
type jobResult struct {
text string
audio Audio
provider string
err error
}
// NewService 构造一个由 chain 支撑的 Service,并启动工作池。
func NewService(chain *Chain, opts ...ServiceOption) *Service {
bg, cancel := context.WithCancel(context.Background())
s := &Service{
chain: chain,
jobs: make(chan job),
bgCtx: bg,
cancel: cancel,
nWorkers: 1,
}
for _, o := range opts {
o(s)
}
for i := 0; i < s.nWorkers; i++ {
s.wg.Add(1)
go s.worker()
}
return s
}
// worker 消费 job 直到服务关闭。
func (s *Service) worker() {
defer s.wg.Done()
for {
select {
case <-s.bgCtx.Done():
return
case j := <-s.jobs:
s.run(j)
}
}
}
// run 执行一个 job 并投递结果。提供方在服务的
// 后台 context 下运行,因此 Close 会取消在途工作,且不泄漏
// worker goroutine;结果写入带缓冲的 channel,因此已
// 返回的调用方绝不会阻塞该 worker。
func (s *Service) run(j job) {
res := jobResult{}
switch j.kind {
case jobSTT:
res.text, res.provider, res.err = s.chain.STTWithProvider(s.bgCtx, j.audio)
case jobTTS:
res.audio, res.provider, res.err = s.chain.TTSWithProvider(s.bgCtx, j.text)
}
j.res <- res
}
// STT 入队一个转写请求并等待结果。ctx 会取消
// 等待以及底层提供方调用。
func (s *Service) STT(ctx context.Context, audio Audio) (string, error) {
text, _, err := s.STTWithProvider(ctx, audio)
return text, err
}
// STTWithProvider 即 STT,但额外报告产出该转写的
// 提供方级别名。
func (s *Service) STTWithProvider(ctx context.Context, audio Audio) (string, string, error) {
res, err := s.submit(ctx, job{kind: jobSTT, audio: audio, res: make(chan jobResult, 1)})
if err != nil {
return "", "", err
}
return res.text, res.provider, res.err
}
// TTS 入队一个合成请求并等待结果。ctx 会取消
// 等待以及底层提供方调用。
func (s *Service) TTS(ctx context.Context, text string) (Audio, error) {
audio, _, err := s.TTSWithProvider(ctx, text)
return audio, err
}
// TTSWithProvider 即 TTS,但额外报告产出该音频的
// 提供方级别名。
func (s *Service) TTSWithProvider(ctx context.Context, text string) (Audio, string, error) {
res, err := s.submit(ctx, job{kind: jobTTS, text: text, res: make(chan jobResult, 1)})
if err != nil {
return Audio{}, "", err
}
return res.audio, res.provider, res.err
}
// CloneVoice 创建自定义音色,交由底层链处理。它是一次性
// 管理操作(不是工作池 job),因此直接运行。
func (s *Service) CloneVoice(ctx context.Context, referenceURL, prefix string) (string, error) {
return s.chain.CloneVoice(ctx, referenceURL, prefix)
}
// submit 把 j 入队并阻塞,直到拿到结果或发生取消。
func (s *Service) submit(ctx context.Context, j job) (jobResult, error) {
select {
case s.jobs <- j:
case <-ctx.Done():
return jobResult{}, fmt.Errorf("voice: %w", ctx.Err())
case <-s.bgCtx.Done():
return jobResult{}, ErrClosed
}
select {
case r := <-j.res:
return r, nil
case <-ctx.Done():
return jobResult{}, fmt.Errorf("voice: %w", ctx.Err())
case <-s.bgCtx.Done():
return jobResult{}, ErrClosed
}
}
// Close 取消工作池并等待在途 job 结束。调用一次是
// 安全的;后续调用为空操作。在途提供方调用由各提供方
// 自己的 context 超时兜底,因此 Close 会迅速返回。
func (s *Service) Close() {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return
}
s.closed = true
s.cancel()
s.wg.Wait()
}