Files
game-driver/pkg/audio/play_test.go
mapleafgo 6ac23c28f1
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
feat(audio): 添加单声道转立体声转换功能
- 新增 monoToStereoReader 将单声道 WAV 实时转换为立体声
- PlayWav 自动检测单声道并应用转换管线
- 添加完整的单元测试覆盖转换逻辑
- 整理 import 顺序(goimports)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-09 10:34:45 +08:00

185 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package audio
import (
"bytes"
"context"
"io"
"os"
"testing"
"time"
)
func TestPlayWav(t *testing.T) {
// 跳过测试如果没有测试文件
testFile := "testdata/test.wav"
if _, err := os.Stat(testFile); os.IsNotExist(err) {
t.Skip("测试文件不存在:", testFile)
}
f, err := os.Open(testFile)
if err != nil {
t.Fatalf("打开测试文件失败: %v", err)
}
defer f.Close()
ctx := context.Background()
err = PlayWav(ctx, f)
if err != nil {
t.Fatalf("PlayWav 失败: %v", err)
}
}
func TestPlayMP3(t *testing.T) {
testFile := "testdata/test.mp3"
if _, err := os.Stat(testFile); os.IsNotExist(err) {
t.Skip("测试文件不存在:", testFile)
}
f, err := os.Open(testFile)
if err != nil {
t.Fatalf("打开测试文件失败: %v", err)
}
defer f.Close()
ctx := context.Background()
err = PlayMP3(ctx, f)
if err != nil {
t.Fatalf("PlayMP3 失败: %v", err)
}
}
func TestPlayContextCancellation(t *testing.T) {
testFile := "testdata/test.mp3"
if _, err := os.Stat(testFile); os.IsNotExist(err) {
t.Skip("测试文件不存在:", testFile)
}
f, err := os.Open(testFile)
if err != nil {
t.Fatalf("打开测试文件失败: %v", err)
}
defer f.Close()
// 创建一个会被快速取消的 context
ctx, cancel := context.WithCancel(context.Background())
// 启动播放后立即取消
done := make(chan error, 1)
go func() {
done <- PlayMP3(ctx, f)
}()
time.Sleep(10 * time.Millisecond)
cancel()
err = <-done
if err != context.Canceled {
t.Errorf("期望 context.Canceled 错误,得到: %v", err)
}
}
// TestMonoToStereoReader 测试单声道转立体声
func TestMonoToStereoReader(t *testing.T) {
// 创建测试数据4个单声道样本8字节
monoData := []byte{
0x00, 0x10, // 样本1: 0x1000 = 4096
0x00, 0x20, // 样本2: 0x2000 = 8192
0x00, 0x30, // 样本3: 0x3000 = 12288
0x00, 0x40, // 样本4: 0x4000 = 16384
}
reader := &monoToStereoReader{src: bytes.NewReader(monoData)}
output := make([]byte, 16) // 应该产生8个样本16字节
n, err := reader.Read(output)
if err != nil {
t.Fatalf("读取失败: %v", err)
}
if n != 16 {
t.Fatalf("期望读取16字节实际读取%d字节", n)
}
// 验证立体声输出(每个单声道样本被复制到左右声道)
expected := []byte{
0x00, 0x10, 0x00, 0x10, // 样本1: 左=0x1000, 右=0x1000
0x00, 0x20, 0x00, 0x20, // 样本2: 左=0x2000, 右=0x2000
0x00, 0x30, 0x00, 0x30, // 样本3: 左=0x3000, 右=0x3000
0x00, 0x40, 0x00, 0x40, // 样本4: 左=0x4000, 右=0x4000
}
if !bytes.Equal(output, expected) {
t.Errorf("立体声转换不正确\n期望: %x\n实际: %x", expected, output)
}
}
// TestMonoToStereoReaderStreaming 测试流式读取
func TestMonoToStereoReaderStreaming(t *testing.T) {
// 创建较大的测试数据
monoData := make([]byte, 1000)
for i := range monoData {
monoData[i] = byte(i % 256)
}
reader := &monoToStereoReader{src: bytes.NewReader(monoData)}
totalRead := 0
buf := make([]byte, 32) // 小缓冲区
for {
n, err := reader.Read(buf)
totalRead += n
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("流式读取失败: %v", err)
}
if n == 0 {
t.Fatal("读取返回0字节但未EOF")
}
}
// 1000字节单声道应该转换为2000字节立体声
expectedTotal := 2000
if totalRead != expectedTotal {
t.Fatalf("期望总共读取%d字节实际读取%d字节", expectedTotal, totalRead)
}
}
// TestMonoToStereoReaderPartialRead 测试部分读取
func TestMonoToStereoReaderPartialRead(t *testing.T) {
monoData := []byte{0x00, 0x10, 0x00, 0x20, 0x00, 0x30} // 3个单声道样本
reader := &monoToStereoReader{src: bytes.NewReader(monoData)}
// 第一次读取请求6字节输出只能读取1个单声道样本=4字节输出
buf1 := make([]byte, 6)
n1, err := reader.Read(buf1)
if err != nil {
t.Fatalf("第一次读取失败: %v", err)
}
if n1 != 4 {
t.Fatalf("第一次读取期望4字节实际%d字节", n1)
}
// 第二次读取请求10字节输出读取剩余2个单声道样本=8字节输出
buf2 := make([]byte, 10)
n2, err := reader.Read(buf2)
if err != nil {
t.Fatalf("第二次读取失败: %v", err)
}
// 剩余2个单声道样本转换为8字节立体声
if n2 != 8 {
t.Fatalf("第二次读取期望8字节实际%d字节", n2)
}
// 第三次读取应该返回EOF
buf3 := make([]byte, 10)
n3, err := reader.Read(buf3)
if err != io.EOF {
t.Fatalf("第三次读取期望EOF实际: %v", err)
}
if n3 != 0 {
t.Fatalf("第三次读取EOF时期望0字节实际%d字节", n3)
}
}