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

