diff --git a/ShengShengBuXi.ConsoleApp/Models/PhoneBoothConfig.cs b/ShengShengBuXi.ConsoleApp/Models/PhoneBoothConfig.cs
index bceeab5..051240e 100644
--- a/ShengShengBuXi.ConsoleApp/Models/PhoneBoothConfig.cs
+++ b/ShengShengBuXi.ConsoleApp/Models/PhoneBoothConfig.cs
@@ -120,6 +120,11 @@ public class AudioFilesConfig
/// 数字按键音频文件模板(0-9)
///
public string DigitToneFileTemplate { get; set; } = "{0}.mp3";
+
+ ///
+ /// 按键音的最小播放时间(毫秒),即使按键释放也会至少播放这么长时间
+ ///
+ public int MinKeyTonePlayTimeMs { get; set; } = 200;
///
/// 获取完整的音频文件路径
@@ -214,6 +219,21 @@ public class RecordingConfig
/// 是否上传录音文件到服务器
///
public bool UploadRecordingToServer { get; set; } = false;
+
+ ///
+ /// 是否在录音时播放背景音乐
+ ///
+ public bool EnableBackgroundMusic { get; set; } = true;
+
+ ///
+ /// 录音背景音乐文件名
+ ///
+ public string BackgroundMusicFile { get; set; } = "bj.mp3";
+
+ ///
+ /// 背景音乐音量 (0.0-1.0)
+ ///
+ public float BackgroundMusicVolume { get; set; } = 0.1f;
}
///
diff --git a/ShengShengBuXi.ConsoleApp/Services/PhoneBoothService.cs b/ShengShengBuXi.ConsoleApp/Services/PhoneBoothService.cs
index 13f2f43..d0c9380 100644
--- a/ShengShengBuXi.ConsoleApp/Services/PhoneBoothService.cs
+++ b/ShengShengBuXi.ConsoleApp/Services/PhoneBoothService.cs
@@ -55,6 +55,12 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
private Timer? _silenceTimer = null;
private volatile bool _isHangUpKeyPressed = false;
+ // 背景音乐相关
+ private WaveOutEvent? _backgroundMusicDevice = null;
+ private AudioFileReader? _backgroundMusicReader = null;
+ private CancellationTokenSource? _backgroundMusicCts = null;
+ private volatile bool _isBackgroundMusicPlaying = false;
+
[StructLayout(LayoutKind.Sequential)]
private struct KBDLLHOOKSTRUCT
{
@@ -175,6 +181,12 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
await Task.Delay(100); // 给一点时间让等待音停止
}
+ // 停止背景音乐
+ if (_isBackgroundMusicPlaying)
+ {
+ StopBackgroundMusic();
+ }
+
// 停止录音
if (_isRecording)
{
@@ -241,6 +253,7 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
// 释放取消令牌源
_programCts.Dispose();
_waitingToneCts?.Dispose();
+ _backgroundMusicCts?.Dispose();
GC.SuppressFinalize(this);
}
@@ -291,6 +304,26 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
_waitingToneReader.Dispose();
_waitingToneReader = null;
}
+
+ // 释放背景音乐设备
+ _backgroundMusicCts?.Cancel();
+ _backgroundMusicCts?.Dispose();
+ _backgroundMusicCts = null;
+
+ if (_backgroundMusicDevice != null)
+ {
+ _backgroundMusicDevice.Stop();
+ _backgroundMusicDevice.Dispose();
+ _backgroundMusicDevice = null;
+ }
+
+ if (_backgroundMusicReader != null)
+ {
+ _backgroundMusicReader.Dispose();
+ _backgroundMusicReader = null;
+ }
+
+ _isBackgroundMusicPlaying = false;
foreach (var (device, reader) in _keyToneDevices.Values)
{
@@ -542,13 +575,20 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
{
recordingCompletionSource.TrySetResult(true);
});
+
+ // 启动背景音乐播放(不等待它完成)
+ if (_config.Recording.EnableBackgroundMusic)
+ {
+ _ = StartPlayingBackgroundMusic(linkedCts.Token);
+ }
// 等待录音完成
await recordingCompletionSource.Task;
}
- catch (Exception)
+ catch (Exception ex)
{
// 捕获所有异常,确保不会中断主流程
+ Console.WriteLine($"录音任务异常: {ex.Message}");
}
});
@@ -564,6 +604,9 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
// 停止录音
StopAudioRecording();
+
+ // 确保背景音乐停止
+ StopBackgroundMusic();
Console.WriteLine("录音结束,重置状态...");
@@ -825,6 +868,9 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
{
// 确保录音设备被释放
StopAudioRecording();
+
+ // 确保背景音乐被停止
+ StopBackgroundMusic();
// 清除录音状态
_isRecording = false;
@@ -863,6 +909,16 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
_pressedKeys.Clear();
_currentPressedKeys.Clear();
_keyToneDevices.Clear();
+
+ // 尝试停止背景音乐
+ try
+ {
+ StopBackgroundMusic();
+ }
+ catch
+ {
+ // 忽略任何错误
+ }
// 尝试重新开始播放等待音
try
@@ -1084,12 +1140,12 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
{
try
{
- if (_keyToneDevices.TryRemove(digit, out var deviceInfo))
- {
- deviceInfo.Device.Stop();
- deviceInfo.Device.Dispose();
- deviceInfo.Reader.Dispose();
- }
+ // 当按键释放时,我们不做特殊处理
+ // 按键音的播放和结束完全由PlayKeyTone中的逻辑控制
+ // 这样确保了即使按键时间很短,声音也能够完整播放
+
+ // 我们只需要确认按键已经从当前按下状态列表中移除即可
+ // 实际的音频资源释放由PlayKeyTone中的PlaybackStopped事件处理
}
catch (Exception ex)
{
@@ -1104,18 +1160,68 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
{
try
{
+ // 如果该按键声音正在播放中,不再重新开始播放
if (_keyToneDevices.ContainsKey(digit))
{
- return; // 已经在播放中
+ // 如果已经在播放中,可以选择重新开始播放,或者保持当前播放
+ // 这里我们选择重新开始播放
+ if (_keyToneDevices.TryRemove(digit, out var oldDeviceInfo))
+ {
+ // 安全地停止并释放旧设备
+ try
+ {
+ oldDeviceInfo.Device.Stop();
+ oldDeviceInfo.Device.Dispose();
+ oldDeviceInfo.Reader.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"停止旧按键音 {digit} 失败: {ex.Message}");
+ }
+ }
}
var device = new WaveOutEvent();
var reader = new AudioFileReader(_config.AudioFiles.GetDigitToneFilePath(digit));
device.Init(reader);
+ // 记录开始播放时间
+ var startTime = DateTime.Now;
+ int minPlayTime = _config.AudioFiles.MinKeyTonePlayTimeMs;
+
if (_keyToneDevices.TryAdd(digit, (device, reader)))
{
device.Play();
+
+ // 确保至少播放最小时长
+ device.PlaybackStopped += async (s, e) =>
+ {
+ // 计算已经播放的时间
+ var playedTime = (int)(DateTime.Now - startTime).TotalMilliseconds;
+
+ // 如果播放时间不足最小时间,延迟处理
+ if (playedTime < minPlayTime)
+ {
+ try
+ {
+ // 如果播放已经停止但时间不够,先不删除设备
+ // 等待剩余的时间后再处理
+ var remainingTime = minPlayTime - playedTime;
+ await Task.Delay(remainingTime);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"按键音 {digit} 最小播放时间延迟失败: {ex.Message}");
+ }
+ }
+
+ // 播放完成后(包括可能的延迟),移除并释放资源
+ if (_keyToneDevices.TryRemove(digit, out var deviceInfo) && deviceInfo.Device == device)
+ {
+ deviceInfo.Device.Dispose();
+ deviceInfo.Reader.Dispose();
+ }
+ };
}
else
{
@@ -1229,4 +1335,124 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
reader?.Dispose();
}
}
+
+ ///
+ /// 开始播放背景音乐
+ ///
+ private async Task StartPlayingBackgroundMusic(CancellationToken token)
+ {
+ try
+ {
+ // 如果配置不启用背景音乐,则直接返回
+ if (!_config.Recording.EnableBackgroundMusic)
+ {
+ Console.WriteLine("背景音乐功能已禁用");
+ return;
+ }
+
+ // 获取背景音乐文件路径
+ string musicFilePath = _config.AudioFiles.GetFullPath(_config.Recording.BackgroundMusicFile);
+
+ // 检查文件是否存在
+ if (!File.Exists(musicFilePath))
+ {
+ Console.WriteLine($"背景音乐文件不存在: {musicFilePath}");
+ return;
+ }
+
+ // 取消可能正在播放的背景音乐
+ _backgroundMusicCts?.Cancel();
+ _backgroundMusicCts?.Dispose();
+ _backgroundMusicCts = CancellationTokenSource.CreateLinkedTokenSource(token);
+
+ // 初始化背景音乐设备
+ _backgroundMusicDevice = new WaveOutEvent();
+ _backgroundMusicReader = new AudioFileReader(musicFilePath);
+
+ // 设置音量(默认为10%)
+ _backgroundMusicReader.Volume = _config.Recording.BackgroundMusicVolume;
+
+ _backgroundMusicDevice.Init(_backgroundMusicReader);
+
+ // 标记背景音乐开始播放
+ _isBackgroundMusicPlaying = true;
+ Console.WriteLine($"开始播放背景音乐,音量: {_backgroundMusicReader.Volume * 100}%");
+
+ // 循环播放背景音乐
+ while (!_backgroundMusicCts.Token.IsCancellationRequested)
+ {
+ try
+ {
+ // 检查是否到达文件末尾,是则重置
+ if (_backgroundMusicReader.Position >= _backgroundMusicReader.Length)
+ {
+ _backgroundMusicReader.Position = 0;
+ }
+
+ // 如果没有在播放,则开始播放
+ if (_backgroundMusicDevice.PlaybackState != PlaybackState.Playing)
+ {
+ _backgroundMusicDevice.Play();
+ }
+
+ // 短暂等待,避免CPU占用过高
+ await Task.Delay(100, _backgroundMusicCts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ // 正常取消,退出循环
+ break;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"背景音乐播放出错: {ex.Message}");
+ // 短暂等待后继续尝试
+ await Task.Delay(1000, _backgroundMusicCts.Token);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"启动背景音乐失败: {ex.Message}");
+ }
+ finally
+ {
+ // 停止并释放音乐设备
+ StopBackgroundMusic();
+ }
+ }
+
+ ///
+ /// 停止背景音乐
+ ///
+ private void StopBackgroundMusic()
+ {
+ try
+ {
+ // 取消播放任务
+ _backgroundMusicCts?.Cancel();
+
+ // 停止并释放设备
+ if (_backgroundMusicDevice != null)
+ {
+ _backgroundMusicDevice.Stop();
+ _backgroundMusicDevice.Dispose();
+ _backgroundMusicDevice = null;
+ }
+
+ if (_backgroundMusicReader != null)
+ {
+ _backgroundMusicReader.Dispose();
+ _backgroundMusicReader = null;
+ }
+
+ // 标记背景音乐已停止
+ _isBackgroundMusicPlaying = false;
+ Console.WriteLine("背景音乐已停止");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"停止背景音乐失败: {ex.Message}");
+ }
+ }
}
\ No newline at end of file
diff --git a/ShengShengBuXi.ConsoleApp/config.json b/ShengShengBuXi.ConsoleApp/config.json
index bc3c29e..a322640 100644
--- a/ShengShengBuXi.ConsoleApp/config.json
+++ b/ShengShengBuXi.ConsoleApp/config.json
@@ -6,7 +6,8 @@
"PhonePickupFile": "\u7535\u8BDD\u63A5\u8D77.mp3",
"PromptUserRecordFile": "\u63D0\u793A\u7528\u6237\u5F55\u97F3.mp3",
"BeepPromptFile": "\u6EF4\u63D0\u793A\u97F3.wav",
- "DigitToneFileTemplate": "{0}.mp3"
+ "DigitToneFileTemplate": "{0}.mp3",
+ "MinKeyTonePlayTimeMs": 200
},
"Dial": {
"MinDigitsToDialOut": 8,
@@ -22,7 +23,10 @@
"SilenceThreshold": 0.02,
"SilenceTimeoutSeconds": 30,
"AllowUserHangup": true,
- "UploadRecordingToServer": false
+ "UploadRecordingToServer": false,
+ "EnableBackgroundMusic": true,
+ "BackgroundMusicFile": "bj.mp3",
+ "BackgroundMusicVolume": 0.1
},
"CallFlow": {
"WaitForPickupMinSeconds": 3,
diff --git a/ShengShengBuXi.ConsoleApp/mp3/bj.mp3 b/ShengShengBuXi.ConsoleApp/mp3/bj.mp3
new file mode 100644
index 0000000..35eac27
Binary files /dev/null and b/ShengShengBuXi.ConsoleApp/mp3/bj.mp3 differ
diff --git a/ShengShengBuXi/Services/SpeechToTextService.cs b/ShengShengBuXi/Services/SpeechToTextService.cs
index 47fea03..b16f2e2 100644
--- a/ShengShengBuXi/Services/SpeechToTextService.cs
+++ b/ShengShengBuXi/Services/SpeechToTextService.cs
@@ -386,7 +386,7 @@ namespace ShengShengBuXi.Services
// 检查会话是否存在
if (!_webSockets.TryGetValue(sessionId, out var webSocket))
{
- _logger.LogWarning($"会话不存在: {sessionId}");
+ _logger.LogWarning($"ProcessAudioAsync->会话不存在: {sessionId}");
return;
}