You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord语音录制中静音时长计算与写入问题排查

Discord语音录制:静音时长计算错误与缓冲区溢出问题

Discord在用户未发言时不会发送音频,因此需要计算用户上次语音数据接收后的时长,向缓冲区写入对应静音来保证时序正确。以下是音频数据处理的核心代码:

private async Task OnVoiceReceived(VoiceNextConnection sender, VoiceReceiveEventArgs e)
{
    try
    {
        if (!_isRecording) return;
        
        if (e.User is null) return;

        if (!_circularBuffers.TryGetValue(e.User.Id, out var circularBuffer))
        {
            circularBuffer = new CircularBuffer(e.AudioFormat.SampleRate * e.AudioFormat.ChannelCount * 2 * _bufferDurationSeconds, 
                e.AudioFormat.ChannelCount);
            _circularBuffers[e.User.Id] = circularBuffer;
        }

        TimeSpan delay;

        if (!_lastVoiceActivity.TryGetValue(e.User.Id, out var lastData))
        {
            // 若用户无历史数据,假设上次语音时间等于录制开始时间
            delay = DateTimeOffset.Now - _recordingStartedAt;
        }
        else delay = DateTimeOffset.Now - lastData;

        // 计算需要添加的静音数据长度
        var silence = new byte[e.AudioFormat.SampleRate * e.AudioFormat.ChannelCount * 2 * (int)Math.Round(delay.TotalSeconds)];
        
        await circularBuffer.WriteAsync(silence);

        _lastVoiceActivity[e.User.Id] = DateTimeOffset.Now;
        
        
        await circularBuffer.WriteAsync(e.PcmData);
    }
    catch (Exception exception)
    {
        Log.Error(exception, "Error while receiving voice data");
    }
}

录制完成后,使用ffmpeg将缓冲区内容写入.wav文件并完成混音。

当前问题

  • 静音时长计算错误,多用户混音后时序错乱:有时静音不足导致发言提前,有时静音过长导致回复延迟
  • 缓冲区溢出触发数据删除逻辑后,上述问题频发
  • 已尝试修改静音计算公式、调整_lastVoiceActivity赋值时机、设置仅当延迟超过100ms时添加静音,但均无改善
  • 测试使用的是可存储30秒音频的循环缓冲区

更新发现

禁用缓冲区溢出删除数据的逻辑后,问题几乎消失。以下是当前的CircularBuffer实现代码:

public class CircularBuffer
{
    private readonly ConcurrentQueue<byte> _buffer;
    
    private readonly object _lockObject;
    private readonly int _bufferSize;

    public int Channels { get; private set; }

    public int Count
    {
        get
        {
            lock (_lockObject)
            {
                return _buffer.Count;
            }
        }
    }

    public CircularBuffer(int size, int channels)
    {
        _buffer = new ConcurrentQueue<byte>();
        _bufferSize = size;
        Channels = channels;
        _lockObject = new object();
    }

    public async Task WriteAsync(ReadOnlyMemory<byte> data)
    {
        lock (_lockObject)
        {
            if (data.Length > _bufferSize) data = data[^_bufferSize..];

            if (_buffer.Count + data.Length > _bufferSize)
            {
                var bytesToRemove = _buffer.Count + data.Length - _bufferSize;
                
                for (var i = 0; i < bytesToRemove; i++)
                {
                    _buffer.TryDequeue(out _);
                }
            }
            
            for (var index = 0; index < data.Span.Length; index++)
            {
                var b = data.Span[index];
                _buffer.Enqueue(b);
            }
        }
        
        await Task.CompletedTask;
    }
    
    public async Task ReadAsync(Memory<byte> data)
    {
        lock (_lockObject)
        {
            if (data.Length > _buffer.Count)
            {
                data = data[.._buffer.Count];
            }
            
            for (var index = 0; index < data.Span.Length; index++)
            {
                _buffer.TryDequeue(out var b);
                data.Span[index] = b;
            }
        }

        await Task.CompletedTask;
    }

    public void Clear()
    {
        lock (_lockObject)
        {
            _buffer.Clear();
        }
    }
}

现寻求解决静音时长计算错误及缓冲区溢出相关问题的可行方案。

内容的提问来源于stack exchange,提问作者Bohdan Petrenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 21:44:54