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

如何在C#中检测WAV文件是否仅完成部分上传?

这确实是个很典型的坑——WAV文件的元数据(包括时长信息)存在文件头部,上传刚开始时,发送端可能就已经把完整的RIFF头写入共享路径的文件了,哪怕后续音频数据传输中断,文件大小会被预分配成完整尺寸,MCI这类工具只会读取头部的时长字段,自然会返回“完整时长”,但实际后面的音频数据都是空字节(表现为静音)。

给你几个可行的检测方案,从严谨到便捷:


方案1:解析WAV结构,检测实际有效音频数据

核心思路是:跳过WAV头部的声明信息,直接扫描音频数据块的实际有效内容(非零字节部分),再结合音频参数计算真实时长。

第一步:解析WAV头部获取关键参数

先从WAV文件里读出采样率、声道数、位深这些核心参数,用来计算每秒音频对应的字节数:

private static (int SampleRate, int Channels, int BitsPerSample) GetWavAudioParams(string wavFilePath)
{
    using (var stream = new FileStream(wavFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    {
        // 跳过RIFF头,定位到fmt块的参数区域
        stream.Seek(20, SeekOrigin.Begin);
        
        // 读取声道数
        byte[] channelBytes = new byte[2];
        stream.Read(channelBytes, 0, 2);
        short channels = BitConverter.ToInt16(channelBytes, 0);
        
        // 读取采样率
        byte[] rateBytes = new byte[4];
        stream.Read(rateBytes, 0, 4);
        int sampleRate = BitConverter.ToInt32(rateBytes, 0);
        
        // 跳过字节率、块对齐,读取位深
        stream.Seek(6, SeekOrigin.Current);
        byte[] bitBytes = new byte[2];
        stream.Read(bitBytes, 0, 2);
        short bitsPerSample = BitConverter.ToInt16(bitBytes, 0);

        return (sampleRate, channels, bitsPerSample);
    }
}

第二步:扫描音频数据块的有效长度

从文件末尾往前找第一个非零字节,确定实际有音频数据的长度:

private static long GetValidAudioDataLength(string wavFilePath)
{
    using (var stream = new FileStream(wavFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    {
        // 先定位到data块的起始位置
        long dataChunkStart = FindDataChunkStart(stream);
        if (dataChunkStart == -1)
            return 0;

        long totalDataBytes = stream.Length - dataChunkStart;
        long currentPos = totalDataBytes;
        byte[] buffer = new byte[4096]; // 用4KB块读取,平衡效率和内存
        int bytesRead;

        // 从末尾往前逐块扫描非零字节
        while (currentPos > 0)
        {
            long readPos = dataChunkStart + Math.Max(0, currentPos - buffer.Length);
            stream.Seek(readPos, SeekOrigin.Begin);
            bytesRead = stream.Read(buffer, 0, (int)Math.Min(buffer.Length, currentPos));

            // 从当前块的末尾往前找第一个非零字节
            for (int i = bytesRead - 1; i >= 0; i--)
            {
                if (buffer[i] != 0)
                {
                    return readPos - dataChunkStart + i + 1;
                }
            }
            currentPos -= bytesRead;
        }
        return 0;
    }
}

// 辅助方法:遍历WAV子块,找到data块的起始位置
private static long FindDataChunkStart(FileStream stream)
{
    stream.Seek(0, SeekOrigin.Begin);
    byte[] riffHeader = new byte[12];
    stream.Read(riffHeader, 0, 12);
    
    // 简单校验是否为标准RIFF WAV文件
    if (Encoding.ASCII.GetString(riffHeader, 0, 4) != "RIFF" || 
        Encoding.ASCII.GetString(riffHeader, 8, 4) != "WAVE")
        return -1;

    // 遍历所有子块,找到data块
    byte[] chunkHeader = new byte[8];
    while (stream.Position < stream.Length)
    {
        stream.Read(chunkHeader, 0, 8);
        string chunkId = Encoding.ASCII.GetString(chunkHeader, 0, 4);
        int chunkSize = BitConverter.ToInt32(chunkHeader, 4);

        if (chunkId == "data")
        {
            return stream.Position;
        }
        // 跳过当前块的内容,继续找下一个块
        stream.Seek(chunkSize, SeekOrigin.Current);
    }
    return -1;
}

第三步:计算真实时长

结合上面两个方法的结果,算出实际有效的录音时长:

public static int GetActualRecordingLength(string wavFilePath)
{
    var (sampleRate, channels, bitsPerSample) = GetWavAudioParams(wavFilePath);
    long validDataLength = GetValidAudioDataLength(wavFilePath);
    if (validDataLength == 0)
        return 0;

    // 计算每秒音频对应的字节数
    int bytesPerSecond = sampleRate * channels * (bitsPerSample / 8);
    // 转换成毫秒级时长
    return (int)(validDataLength * 1000L / bytesPerSecond);
}

方案2:从上传流程根源避免问题(可控场景)

如果上传逻辑是你这边可以控制的,建议做以下优化,从源头避免误判:

  • 上传时先写入临时文件(比如命名为xxx.wav.tmp)
  • 只有当100%上传完成后,再将临时文件重命名为正式的.wav文件
  • 检测时只处理正式命名的文件,临时文件直接忽略

这种方式比事后检测更可靠,尤其适合网络不稳定的场景。


注意事项

  • 读取共享路径文件时,一定要加上FileShare.ReadWrite参数,避免上传进程占用文件导致读取失败
  • 上述代码仅针对PCM格式的WAV文件(MCI默认处理的waveaudio类型),如果是压缩格式WAV(比如ADPCM),需要调整解析逻辑
  • 块读取的大小可以根据实际文件规模调整,4KB是兼顾效率和内存的均衡选择

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:38:59