Repository: naudio/NAudio (2026-05-27T20:06:09Z)

Issue:
Wav File - Malformed `fmt ` chunk for PCM data
https://github.com/naudio/NAudio/blob/5449b95ffe921c7bbdf55b17c75b2e7d63f1b64e/NAudio.Core/Wave/WaveFormats/WaveFormat.cs#L311-L313

this writes the `extraSize` into the file, even though it shouldn't exist if the data is PCM.


Discussion:

Patch:
diff --git a/NAudio.Core/Wave/WaveFormats/WaveFormat.cs b/NAudio.Core/Wave/WaveFormats/WaveFormat.cs
index 423e1b0dd..35c551d2f 100644
--- a/NAudio.Core/Wave/WaveFormats/WaveFormat.cs
+++ b/NAudio.Core/Wave/WaveFormats/WaveFormat.cs
@@ -302,14 +302,21 @@ public override int GetHashCode()
         /// <param name="writer">the output stream</param>
         public virtual void Serialize(BinaryWriter writer)
         {
-            writer.Write((int)(18 + extraSize)); // wave format length
+            // Canonical PCM uses the 16-byte PCMWAVEFORMAT layout with no cbSize field.
+            // The cbSize field only belongs to WAVEFORMATEX (non-PCM). We still guard on
+            // extraSize so a PCM-tagged subclass that carries extra data stays well-formed.
+            bool writeExtraSize = !(Encoding == WaveFormatEncoding.Pcm && extraSize == 0);
+            writer.Write((int)(writeExtraSize ? 18 + extraSize : 16)); // wave format length
             writer.Write((short)Encoding);
             writer.Write((short)Channels);
             writer.Write((int)SampleRate);
             writer.Write((int)AverageBytesPerSecond);
             writer.Write((short)BlockAlign);
             writer.Write((short)BitsPerSample);
-            writer.Write((short)extraSize);
+            if (writeExtraSize)
+            {
+                writer.Write((short)extraSize);
+            }
         }
 
         /// <summary>
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 12e116c7e..14b325f0a 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -80,6 +80,7 @@ Docs/Architecture/ReleaseStrategy.md for the release-notes process.
  * `MediaFoundationEncoder`: unselected `MediaType` instances are now disposed to prevent finalizer-thread COM ref leaks
  * `StreamMediaFoundationReader` and stream-based `MediaFoundationEncoder` encoding now use a direct managed `IMFByteStream` wrapper instead of the `IStream`→`IMFByteStream` shim, improving reliability of reading and encoding audio through .NET streams (#1288)
  * `Mp3FileReader`: fixed false sample-rate-change errors near end of file
+ * `WaveFormat.Serialize`: PCM formats now write the canonical 16-byte `fmt ` chunk (no `cbSize` field) instead of 18 bytes, matching the `PCMWAVEFORMAT` layout (#934, #1098)
  * MP3 frame parsing: more robust against false frame detections from album art and trailing metadata
  * `MidiFile`: preserved running-status across meta events (fixes "Read too far" errors when meta events interrupt running-status sequences)
  * `WaveStream.CurrentTime` setter: now lands on a block boundary, preventing garbage audio on seek in custom readers


Test patch:
diff --git a/NAudio.Core.Tests/WaveFormats/WaveFormatSerializeTests.cs b/NAudio.Core.Tests/WaveFormats/WaveFormatSerializeTests.cs
new file mode 100644
index 000000000..ebe988f66
--- /dev/null
+++ b/NAudio.Core.Tests/WaveFormats/WaveFormatSerializeTests.cs
@@ -0,0 +1,83 @@
+using System.IO;
+using NAudio.Utils;
+using NAudio.Wave;
+using NUnit.Framework;
+
+namespace NAudio.Core.Tests.WaveFormats
+{
+    [TestFixture]
+    [Category("UnitTest")]
+    public class WaveFormatSerializeTests
+    {
+        private static (int declaredLength, byte[] body) Serialize(WaveFormat format)
+        {
+            using var ms = new MemoryStream();
+            using (var writer = new BinaryWriter(ms))
+            {
+                format.Serialize(writer);
+            }
+            var bytes = ms.ToArray();
+            var declaredLength = System.BitConverter.ToInt32(bytes, 0);
+            var body = new byte[bytes.Length - 4];
+            System.Array.Copy(bytes, 4, body, 0, body.Length);
+            return (declaredLength, body);
+        }
+
+        [Test]
+        public void PcmSerializesAs16ByteChunkWithoutCbSize()
+        {
+            var format = new WaveFormat(44100, 16, 2);
+            var (declaredLength, body) = Serialize(format);
+
+            Assert.That(declaredLength, Is.EqualTo(16), "PCM fmt chunk length");
+            Assert.That(body.Length, Is.EqualTo(16), "PCM fmt body length (no cbSize field)");
+        }
+
+        [Test]
+        public void NonPcmStillSerializesCbSize()
+        {
+            var format = WaveFormat.CreateIeeeFloatWaveFormat(44100, 2);
+            var (declaredLength, body) = Serialize(format);
+
+            Assert.That(declaredLength, Is.EqualTo(18), "non-PCM fmt chunk length includes cbSize");
+            Assert.That(body.Length, Is.EqualTo(18), "non-PCM fmt body length includes cbSize");
+        }
+
+        [Test]
+        public void PcmRoundTripsThroughSerialize()
+        {
+            var format = new WaveFormat(22050, 24, 1);
+            using var ms = new MemoryStream();
+            using (var writer = new BinaryWriter(ms, System.Text.Encoding.UTF8, leaveOpen: true))
+            {
+                format.Serialize(writer);
+            }
+            ms.Position = 0;
+            using var reader = new BinaryReader(ms);
+            var roundTripped = new WaveFormat(reader);
+
+            Assert.That(roundTripped, Is.EqualTo(format), "Round-tripped format equals original");
+            Assert.That(roundTripped.ExtraSize, Is.EqualTo(0), "Extra size");
+            Assert.That(ms.Position, Is.EqualTo(ms.Length), "Reader consumed exactly the chunk");
+        }
+
+        [Test]
+        public void PcmRoundTripsThroughWaveFileWriter()
+        {
+            var format = new WaveFormat(16000, 16, 1);
+            using var ms = new MemoryStream();
+            using (var writer = new WaveFileWriter(new IgnoreDisposeStream(ms), format))
+            {
+                writer.Write(new byte[64], 0, 64);
+            }
+            ms.Position = 0;
+            using var reader = new WaveFileReader(ms);
+
+            Assert.That(reader.WaveFormat.Encoding, Is.EqualTo(WaveFormatEncoding.Pcm));
+            Assert.That(reader.WaveFormat.SampleRate, Is.EqualTo(16000));
+            Assert.That(reader.WaveFormat.BitsPerSample, Is.EqualTo(16));
+            Assert.That(reader.WaveFormat.Channels, Is.EqualTo(1));
+            Assert.That(reader.Length, Is.EqualTo(64));
+        }
+    }
+}