VB.NET实现WAV文件跳转播放或生成短文件的方法咨询
WAV文件跳转播放问题修复方案
问题根源分析
- 初始字符串处理错误:WAV是二进制文件,不能用
StreamReader以UTF8编码读取为字符串——二进制数据包含无法被UTF8正确编码/解码的字节,转成字符串后再转回字节流会彻底损坏文件结构,导致InvalidCastException。 - 修改后无声音问题:
- 文件头计算错误:RIFF块大小(
FileSize)和数据块大小(DataSize)的计算不符合WAV规范,导致My.Computer.Audio无法识别文件格式。 - 跳过字节数的计算逻辑错误:直接基于文件长度计算跳过量,忽略了WAV的音频参数(采样率、声道数、位深),可能导致数据对齐错误。
My.Computer.Audio.Play仅支持PCM格式的WAV,若生成的文件格式不符合要求(如非PCM、参数不兼容),会出现无声现象。
- 文件头计算错误:RIFF块大小(
正确实现方案
方案1:直接跳转播放(无需生成临时文件)
如果只是需要从指定位置播放,无需生成新文件,可通过读取文件到内存流,直接定位到数据块的指定位置后播放:
Sub PlayWavFromPosition(filePath As String, skipSeconds As Integer) Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read) Using br As New BinaryReader(fs) ' 读取RIFF头 br.ReadBytes(4) ' 跳过RIFF标识 br.ReadInt32() ' 跳过文件大小 br.ReadBytes(4) ' 跳过WAVE标识 ' 读取fmt块 br.ReadBytes(4) ' 跳过fmt标识 Dim fmtSize As Integer = br.ReadInt32() Dim audioFormat As Short = br.ReadInt16() Dim channels As Short = br.ReadInt16() Dim sampleRate As Integer = br.ReadInt32() br.ReadInt32() ' 跳过ByteRate Dim blockAlign As Short = br.ReadInt16() Dim bitsPerSample As Short = br.ReadInt16() ' 跳过fmt块的额外数据(如果有) If fmtSize > 16 Then br.ReadBytes(fmtSize - 16) End If ' 定位到data块,处理可能存在的其他子块(如LIST) Dim dataId As String = Encoding.ASCII.GetString(br.ReadBytes(4)) While dataId <> "data" Dim chunkSize As Integer = br.ReadInt32() br.ReadBytes(chunkSize) dataId = Encoding.ASCII.GetString(br.ReadBytes(4)) End If Dim dataSize As Integer = br.ReadInt32() ' 计算要跳过的字节数,并对齐到块大小 Dim bytesPerSecond As Integer = sampleRate * channels * (bitsPerSample \ 8) Dim skipBytes As Integer = skipSeconds * bytesPerSecond skipBytes -= skipBytes Mod blockAlign skipBytes = Math.Min(skipBytes, dataSize) ' 跳转到指定位置 br.BaseStream.Position += skipBytes ' 读取剩余数据并重新构建WAV文件头 Dim remainingData As Byte() = br.ReadBytes(dataSize - skipBytes) Using ms As New MemoryStream() ms.Write(Encoding.ASCII.GetBytes("RIFF"), 0, 4) Dim riffSize As Integer = 36 + If(fmtSize > 16, fmtSize - 16, 0) + remainingData.Length ms.Write(BitConverter.GetBytes(riffSize), 0, 4) ms.Write(Encoding.ASCII.GetBytes("WAVE"), 0, 4) ms.Write(Encoding.ASCII.GetBytes("fmt "), 0, 4) ms.Write(BitConverter.GetBytes(fmtSize), 0, 4) ms.Write(BitConverter.GetBytes(audioFormat), 0, 2) ms.Write(BitConverter.GetBytes(channels), 0, 2) ms.Write(BitConverter.GetBytes(sampleRate), 0, 4) Dim byteRate As Integer = sampleRate * channels * (bitsPerSample \ 8) ms.Write(BitConverter.GetBytes(byteRate), 0, 4) ms.Write(BitConverter.GetBytes(blockAlign), 0, 2) ms.Write(BitConverter.GetBytes(bitsPerSample), 0, 2) If fmtSize > 16 Then ' 若需支持非标准fmt块,需提前保存并写入额外数据 End If ms.Write(Encoding.ASCII.GetBytes("data"), 0, 4) ms.Write(BitConverter.GetBytes(remainingData.Length), 0, 4) ms.Write(remainingData, 0, remainingData.Length) ms.Position = 0 ' 使用SoundPlayer播放 Using player As New System.Media.SoundPlayer(ms) player.Play() End Using End Using End Using End Using End Sub
方案2:生成正确的短WAV文件
如果需要生成临时文件,需严格按照WAV规范计算文件头参数:
Sub CreateShortWavFile(originalPath As String, outputPath As String, skipSeconds As Integer) Dim waveBytes As Byte() = File.ReadAllBytes(originalPath) Using ms As New MemoryStream(waveBytes) Using br As New BinaryReader(ms) ' 读取RIFF块 Dim riffId As Byte() = br.ReadBytes(4) Dim riffSize As Integer = br.ReadInt32() Dim waveId As Byte() = br.ReadBytes(4) ' 读取fmt块 Dim fmtId As Byte() = br.ReadBytes(4) Dim fmtSize As Integer = br.ReadInt32() Dim audioFormat As Short = br.ReadInt16() Dim channels As Short = br.ReadInt16() Dim sampleRate As Integer = br.ReadInt32() Dim byteRate As Integer = br.ReadInt32() Dim blockAlign As Short = br.ReadInt16() Dim bitsPerSample As Short = br.ReadInt16() ' 保存fmt额外数据(如果有) Dim fmtExtraData As Byte() = Nothing If fmtSize > 16 Then fmtExtraData = br.ReadBytes(fmtSize - 16) End If ' 定位到data块,处理其他子块 Dim dataId As Byte() = br.ReadBytes(4) While Encoding.ASCII.GetString(dataId) <> "data" Dim chunkSize As Integer = br.ReadInt32() br.ReadBytes(chunkSize) dataId = br.ReadBytes(4) End If Dim dataSize As Integer = br.ReadInt32() ' 计算跳过字节数并对齐 Dim bytesPerSecond As Integer = sampleRate * channels * (bitsPerSample \ 8) Dim skipBytes As Integer = skipSeconds * bytesPerSecond skipBytes -= skipBytes Mod blockAlign skipBytes = Math.Min(skipBytes, dataSize) ' 跳过指定字节并读取剩余数据 br.ReadBytes(skipBytes) Dim remainingData As Byte() = br.ReadBytes(dataSize - skipBytes) ' 计算新的文件头参数 Dim newDataSize As Integer = remainingData.Length Dim newRiffSize As Integer = 4 + fmtSize + 4 + newDataSize + 8 ' WAVE标识 + fmt块 + data块总大小 ' 构建新的WAV文件 Using outputMs As New MemoryStream() Using bw As New BinaryWriter(outputMs) ' 写入RIFF头 bw.Write(riffId) bw.Write(newRiffSize) bw.Write(waveId) ' 写入fmt块 bw.Write(fmtId) bw.Write(fmtSize) bw.Write(audioFormat) bw.Write(channels) bw.Write(sampleRate) bw.Write(byteRate) bw.Write(blockAlign) bw.Write(bitsPerSample) If fmtExtraData IsNot Nothing Then bw.Write(fmtExtraData) End If ' 写入data块 bw.Write(dataId) bw.Write(newDataSize) bw.Write(remainingData) End Using ' 保存到文件 File.WriteAllBytes(outputPath, outputMs.ToArray()) End Using End Using End Using ' 播放生成的文件 My.Computer.Audio.Play(outputPath, AudioPlayMode.Background) End Sub
关键注意事项
- 始终用二进制方式处理WAV文件:绝对避免将二进制文件转换为字符串,会导致数据损坏。
- 严格遵循WAV规范:文件头的每个字段大小和含义必须准确,尤其是块大小的计算,否则播放器无法识别。
My.Computer.Audio的限制:仅支持PCM格式、采样率8/11/22/44kHz、位深8/16位的WAV文件,若需支持更多格式,建议使用专业音频库。
内容的提问来源于stack exchange,提问作者Blake Frampton
相关产品推荐
相关产品推荐

