This commit is contained in:
zpc 2025-03-28 04:34:33 +08:00
parent 97f852d5e5
commit 631019cf6a
5 changed files with 261 additions and 11 deletions

View File

@ -120,6 +120,11 @@ public class AudioFilesConfig
/// 数字按键音频文件模板0-9
/// </summary>
public string DigitToneFileTemplate { get; set; } = "{0}.mp3";
/// <summary>
/// 按键音的最小播放时间(毫秒),即使按键释放也会至少播放这么长时间
/// </summary>
public int MinKeyTonePlayTimeMs { get; set; } = 200;
/// <summary>
/// 获取完整的音频文件路径
@ -214,6 +219,21 @@ public class RecordingConfig
/// 是否上传录音文件到服务器
/// </summary>
public bool UploadRecordingToServer { get; set; } = false;
/// <summary>
/// 是否在录音时播放背景音乐
/// </summary>
public bool EnableBackgroundMusic { get; set; } = true;
/// <summary>
/// 录音背景音乐文件名
/// </summary>
public string BackgroundMusicFile { get; set; } = "bj.mp3";
/// <summary>
/// 背景音乐音量 (0.0-1.0)
/// </summary>
public float BackgroundMusicVolume { get; set; } = 0.1f;
}
/// <summary>

View File

@ -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();
}
}
/// <summary>
/// 开始播放背景音乐
/// </summary>
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();
}
}
/// <summary>
/// 停止背景音乐
/// </summary>
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}");
}
}
}

View File

@ -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,

Binary file not shown.

View File

@ -386,7 +386,7 @@ namespace ShengShengBuXi.Services
// 检查会话是否存在
if (!_webSockets.TryGetValue(sessionId, out var webSocket))
{
_logger.LogWarning($"会话不存在: {sessionId}");
_logger.LogWarning($"ProcessAudioAsync->会话不存在: {sessionId}");
return;
}