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

CS:GO服务器CELT解码音频转WAV后播放过快带杂音问题排查

问题描述

我需要从CS:GO游戏服务器提取语音数据,引擎会提供压缩音频流片段,我通过CELT库的IVoiceCodec::Decompress接口解压缩,实现逻辑参考CS:GO引擎源码。

根据源码推测,解码后应为单声道16bit/22050Hz PCM音频,解压代码如下:

static std::vector<char> buffer;

char decompressedData[MAX_UNCOMPRESSED_VOICE_BUFFER_SIZE];  
const int decompressedBytes = g_pCodec->Decompress(data.data(),
                                                   data.size(),
                                                   decompressedData,
                                                   MAX_UNCOMPRESSED_VOICE_BUFFER_SIZE);

if(decompressedBytes > 0)
    buffer.insert(buffer.end(),
                  decompressedData,
                  decompressedData + decompressedBytes);
        
if(buffer.size() > 50000)
{
    static int fileIndex = 0;
    const std::string fileName("wav/test_" + std::to_string(clientIndex) + "_" + std::to_string(fileIndex) + ".wav");
    const bool result = WriteWaveFile(fileName.c_str(),
                    buffer.data(),
                    buffer.size(),
                    16, // Bits per sample
                    1, // Channels
                    22050 // Samples per sec
                    );
                    
    buffer.clear();
        
    ++fileIndex;
}

解码器运行无错误,输出数据能辨认语音,但保存为WAV文件(添加42字节WAV头)后,音频音高正常,但播放速率过快且存在杂音。我尝试修改采样率和采样位数,问题依旧。

以下是我编写的WAV文件写入代码:

void WriteDWord(FileHandle_t fp, unsigned long val) 
{
    g_pFileSystem->Write(&val, 4, fp);
}

void WriteWord(FileHandle_t fp, unsigned short val) 
{
    g_pFileSystem->Write(&val, 2, fp);
}

bool WriteWaveFile(
    const char *pFilename, 
    const char *pData, 
    int nBytes, 
    int wBitsPerSample, 
    int nChannels, 
    int nSamplesPerSec)
{
    FileHandle_t fp = g_pFileSystem->Open(pFilename, "wb");
    if(!fp)
        return false;

    // Write the RIFF chunk.
    g_pFileSystem->Write("RIFF", 4, fp);
    WriteDWord(fp, 0);
    g_pFileSystem->Write("WAVE", 4, fp);


    // Write the FORMAT chunk.
    g_pFileSystem->Write("fmt ", 4, fp);

    WriteDWord(fp, 0x10);
    WriteWord(fp, WAVE_FORMAT_PCM);
    WriteWord(fp, (unsigned short)nChannels);   
    WriteDWord(fp, (unsigned long)nSamplesPerSec);
    WriteDWord(fp, (unsigned long)((wBitsPerSample / 8) * nChannels * nSamplesPerSec));
    WriteWord(fp, (unsigned short)((wBitsPerSample / 8) * nChannels));
    WriteWord(fp, (unsigned long)wBitsPerSample);

    // Write the DATA chunk.
    g_pFileSystem->Write("data", 4, fp);
    WriteDWord(fp, (unsigned long)nBytes);
    g_pFileSystem->Write(pData, nBytes, fp);

    // Go back and write the length of the riff file.
    unsigned long dwVal = g_pFileSystem->Tell(fp) - 8;
    g_pFileSystem->Seek(fp, 4, FILESYSTEM_SEEK_HEAD);
    WriteDWord(fp, dwVal);

    g_pFileSystem->Close(fp);
    return true;
}

请问WAV文件可能存在什么问题?


问题排查与解决方案

1. 字节序(端序)不匹配

WAV格式要求所有多字节字段(如Word、DWord)必须使用小端字节序,但你的WriteDWord和WriteWord直接将内存中的值写入文件,这会依赖主机字节序。如果服务器是大端架构,或CELT解码输出的PCM数据是大端字节序,就会导致播放异常。

修复方式:写入前将数值转换为小端字节序:

#include <cstdint>

uint32_t ConvertToLittleEndian32(uint32_t val) {
    #if defined(_MSC_VER)
        return _byteswap_ulong(val);
    #else
        return __builtin_bswap32(val);
    #endif
}

uint16_t ConvertToLittleEndian16(uint16_t val) {
    #if defined(_MSC_VER)
        return _byteswap_ushort(val);
    #else
        return __builtin_bswap16(val);
    #endif
}

void WriteDWord(FileHandle_t fp, unsigned long val) 
{
    uint32_t le_val = ConvertToLittleEndian32(static_cast<uint32_t>(val));
    g_pFileSystem->Write(&le_val, 4, fp);
}

void WriteWord(FileHandle_t fp, unsigned short val) 
{
    uint16_t le_val = ConvertToLittleEndian16(val);
    g_pFileSystem->Write(&le_val, 2, fp);
}

同时检查PCM数据字节序:若CELT输出为大端,需对每个16bit采样点进行字节交换。

2. WAV头参数类型错误

在WriteWaveFile中,写入采样位数时存在类型不匹配:

WriteWord(fp, (unsigned long)wBitsPerSample);

WriteWord接收unsigned short类型,但你强制转换为unsigned long,会导致多余字节写入,破坏WAV头结构。应改为:

WriteWord(fp, static_cast<unsigned short>(wBitsPerSample));

3. 缓冲区溢出或数据拼接异常

检查MAX_UNCOMPRESSED_VOICE_BUFFER_SIZE是否与CS:GO引擎定义一致(引擎中该值应为22050 * 2 * 1,即单声道16bit每秒的字节数)。若定义过小,Decompress会截断数据,导致拼接后的PCM不完整,产生杂音。

另外,确保每次Decompress返回的decompressedBytes有效,未超出缓冲区大小,避免垃圾数据写入buffer。

4. WAV文件长度计算错误

验证g_pFileSystem->Tell(fp)的返回值是否准确,确保RIFF长度计算正确。若长度错误,播放器会错误解析音频时长,导致播放速率异常。


内容的提问来源于stack exchange,提问作者Dmitry Logov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 18:45:58