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; }