基本逻辑完成
This commit is contained in:
80
pkg/audio/play.go
Normal file
80
pkg/audio/play.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package audio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gopxl/beep/v2"
|
||||
"github.com/gopxl/beep/v2/mp3"
|
||||
"github.com/gopxl/beep/v2/speaker"
|
||||
"github.com/gopxl/beep/v2/wav"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DefaultSampleRate = beep.SampleRate(44100)
|
||||
|
||||
func init() {
|
||||
err := speaker.Init(DefaultSampleRate, DefaultSampleRate.N(time.Second/10))
|
||||
if err != nil {
|
||||
panic("扬声器初始化异常: " + err.Error())
|
||||
}
|
||||
log.Println("扬声器初始化完成")
|
||||
}
|
||||
|
||||
func PlayWav(c context.Context, r io.Reader) {
|
||||
streamer, format, err := wav.Decode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer streamer.Close()
|
||||
|
||||
s := beep.Resample(4, format.SampleRate, DefaultSampleRate, streamer)
|
||||
|
||||
ctrl := &beep.Ctrl{Streamer: s}
|
||||
done := make(chan struct{})
|
||||
speaker.Play(beep.Seq(ctrl, beep.Callback(func() {
|
||||
close(done)
|
||||
})))
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-c.Done():
|
||||
{
|
||||
speaker.Lock()
|
||||
ctrl.Streamer = nil
|
||||
speaker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PlayMP3(c context.Context, r io.ReadCloser) {
|
||||
streamer, format, err := mp3.Decode(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer streamer.Close()
|
||||
|
||||
s := beep.Resample(4, format.SampleRate, DefaultSampleRate, streamer)
|
||||
|
||||
ctrl := &beep.Ctrl{Streamer: s}
|
||||
done := make(chan struct{})
|
||||
speaker.Play(beep.Seq(ctrl, beep.Callback(func() {
|
||||
close(done)
|
||||
})))
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-c.Done():
|
||||
{
|
||||
speaker.Lock()
|
||||
ctrl.Streamer = nil
|
||||
speaker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
pkg/errorsx/error.go
Normal file
9
pkg/errorsx/error.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package errorsx
|
||||
|
||||
import "errors"
|
||||
|
||||
var DriverTimeoutErr = errors.New("处理超时")
|
||||
|
||||
var DriverCancelErr = errors.New("系统取消")
|
||||
|
||||
var ThirdPartyErr = errors.New("第三方请求异常")
|
||||
7
pkg/ports.go
Normal file
7
pkg/ports.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "game-driver/pkg/relay"
|
||||
|
||||
func main() {
|
||||
relay.PrintPorts()
|
||||
}
|
||||
28
pkg/relay/portlist.go
Normal file
28
pkg/relay/portlist.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"go.bug.st/serial/enumerator"
|
||||
)
|
||||
|
||||
func PrintPorts() {
|
||||
ports, err := enumerator.GetDetailedPortsList()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
return
|
||||
}
|
||||
for _, port := range ports {
|
||||
fmt.Printf("Port: %s\n", port.Name)
|
||||
if port.Product != "" {
|
||||
fmt.Printf(" Product Name: %s\n", port.Product)
|
||||
}
|
||||
if port.IsUSB {
|
||||
fmt.Printf(" USB ID : %s:%s\n", port.VID, port.PID)
|
||||
fmt.Printf(" USB serial : %s\n", port.SerialNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
44
pkg/relay/relay.go
Normal file
44
pkg/relay/relay.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"go.bug.st/serial"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
port serial.Port
|
||||
}
|
||||
|
||||
func (r *Device) Close() error {
|
||||
return r.port.Close()
|
||||
}
|
||||
|
||||
func (r *Device) On(num int) error {
|
||||
_, err := io.WriteString(r.port, fmt.Sprintf("AT+OUT%v+1=ON\r\n", num))
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Device) Off(num int) error {
|
||||
_, err := io.WriteString(r.port, fmt.Sprintf("AT+OUT%v+1=OFF\r\n", num))
|
||||
return err
|
||||
}
|
||||
|
||||
func New(portName string, reader func(msg string)) (*Device, error) {
|
||||
port, err := serial.Open(portName, &serial.Mode{
|
||||
BaudRate: 9600,
|
||||
DataBits: 8,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
r := bufio.NewReader(port)
|
||||
line, _, _ := r.ReadLine()
|
||||
reader(string(line))
|
||||
}
|
||||
}()
|
||||
return &Device{port: port}, nil
|
||||
}
|
||||
101
pkg/tts/aliyun.go
Normal file
101
pkg/tts/aliyun.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package tts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"game-driver/config"
|
||||
"game-driver/leaf"
|
||||
"game-driver/pkg/audio"
|
||||
"game-driver/pkg/errorsx"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
nls "github.com/aliyun/alibabacloud-nls-go-sdk"
|
||||
)
|
||||
|
||||
type AliTTS struct {
|
||||
config.AliyunConfig
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
type result struct {
|
||||
Data io.ReadWriter
|
||||
Error error
|
||||
}
|
||||
|
||||
// onTaskFailed 识别过程中的错误处理回调参数
|
||||
func (tts *AliTTS) onTaskFailed(text string, param interface{}) {
|
||||
p, _ := param.(*result)
|
||||
p.Error = fmt.Errorf("语音合成异常: %v", text)
|
||||
}
|
||||
|
||||
// onSynthesisResult 语音合成数据回调参数
|
||||
func (tts *AliTTS) onSynthesisResult(data []byte, param interface{}) {
|
||||
p, _ := param.(*result)
|
||||
p.Data.Write(data)
|
||||
}
|
||||
|
||||
func (tts *AliTTS) Sound(text string) {
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
buf, err := tts.Get(text)
|
||||
if err == nil && buf != nil {
|
||||
audio.PlayWav(tts.ctx, buf)
|
||||
} else {
|
||||
log.Panicln("AliTTS 请求异常: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (tts *AliTTS) Get(text string) (io.Reader, error) {
|
||||
param := nls.DefaultSpeechSynthesisParam()
|
||||
param.Volume = 100
|
||||
connectConfig := nls.NewConnectionConfigWithToken(nls.DEFAULT_URL, tts.AppKey, tts.Token)
|
||||
|
||||
logger := nls.NewNlsLogger(leaf.DefaultWriter, "", log.LstdFlags|log.Ltime)
|
||||
logger.SetLogSil(false)
|
||||
logger.SetDebug(true)
|
||||
|
||||
ttsData := &result{
|
||||
Data: &bytes.Buffer{},
|
||||
}
|
||||
|
||||
synthesis, err := nls.NewSpeechSynthesis(
|
||||
connectConfig, logger, false,
|
||||
tts.onTaskFailed, tts.onSynthesisResult, nil,
|
||||
nil, nil, ttsData,
|
||||
)
|
||||
if err != nil {
|
||||
return ttsData.Data, err
|
||||
}
|
||||
defer synthesis.Shutdown()
|
||||
|
||||
ch, err := synthesis.Start(text, param, nil)
|
||||
if err != nil {
|
||||
return ttsData.Data, err
|
||||
}
|
||||
|
||||
// 等待语音合成结束
|
||||
select {
|
||||
case done := <-ch:
|
||||
{
|
||||
if !done {
|
||||
return ttsData.Data, errorsx.ThirdPartyErr
|
||||
}
|
||||
return ttsData.Data, nil
|
||||
}
|
||||
case <-time.After(time.Duration(tts.Timeout) * time.Second):
|
||||
return ttsData.Data, errorsx.DriverTimeoutErr
|
||||
case <-tts.ctx.Done():
|
||||
return ttsData.Data, errorsx.DriverCancelErr
|
||||
}
|
||||
}
|
||||
|
||||
func New(ctx context.Context, config config.AliyunConfig) *AliTTS {
|
||||
return &AliTTS{
|
||||
ctx: ctx,
|
||||
AliyunConfig: config,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user