Repository: naudio/NAudio (2026-06-25T21:19:58Z)

Issue:
Converting WAV to MP3 truncate file length
Hi team,

<<<I see 1min ago - maybe a NAudio.Lame problem. I post the problem also there. Close it if its the false place.>>>

I have a short example for the "problem". I create two console apps (either with .NET Framework 4.72 and the other with .NET Core 6.0).
With Nuget I add NAudio 2.1.0 and NAudio.Lama 2.1.0.
When Convert in .NET Framework the result is ok. With .NET Core the file size was (in my case) only 1/4 of the original size.
Added my example file 16 seconds long is shrinked to 4s in .NET Core version. 
Seems its only the case for Mono Audio files.

[AudioFile.zip](https://github.com/naudio/NAudio/files/11259785/AudioFile.zip)

**using (var reader = new AudioFileReader("C:\\Temp\\AudioFile.wav"))
{
  using (var fileWriter = new FileStream("C:\\Temp\\Convert.mp3", FileMode.Create, FileAccess.Write))
  {
	LameConfig lc = new LameConfig();
	lc.Out
        putSampleRate = 8000;
	lc.BitRate = 16;
	using (var wtr = new LameMP3FileWriter(fileWriter, reader.WaveFormat, lc))
	{
		reader.CopyTo(wtr);
		wtr.Flush();
	}
  }
}**

Thanks in advance.
Regards
Dirk F.



Discussion:
Yes, might be best to ask someone who has LAME experience. I don't think MP3 as a format has very good support for super low sample-rates - 8kHz mono is more typically associated with telephony rather than music, which is MP3's focus. AAC or Opus would be more appropriate choices if possible.
Hi, thx for answering. Maybe the problem lies in the native lame-dlls. We will try investigating this in the future. 
Anyway - thx for your support and work on NAudio.
Regards
Dirk
Hi @markheath... been a minute 😄

Haven't probed too deep into this, just tried to follow along with the way `AudioFileReader` would process the file:

* Create a `WaveFileReader` on the input stream.
* If not PCM or IEEE-Float then:
  * Wrap reader in `WaveFormatConversionStream`
  * Wrap again in `BlockAlignReductionStream`

When I do that,  manually I get the same issues: `CopyTo` reads 4 seconds worth of samples and then stops. Since 4 seconds is the size of the circular buffer in `BlockAlignReductionStream` I figured removing that from the pipeline would help, and it does. Putting it back resurrects the problem. Replacing `CopyTo` with a loop that reads 2 seconds of samples at a time works fine and reads the entire file.

Not sure where the problem actually lies, whether it's in `CircularBuffer` or `BlockAlignReductionStream.Read`... been a long time since I looked at this stuff I'm well behind on your new code. (Yep, am lazy.)
OK, found the issue. [`BlockAlignReductionStream.Read()`][1] fills the circular buffer by reading the next batch of samples from the source stream and writing them to the circular buffer. The problem comes in when the requested byte count is larger than the available (or total) space in the circular buffer, in which case the extra bytes are lost. And since filling the circular buffer happens in a loop that doesn't stop until it has enough bytes to satisfy the request, it reads and discards the entire source stream before continuing.

On my test run (.NET 6.0, Windows x64) `CopyTo()` reads in 128KB chunks and repeats until the returned count is zero. The end result is that I get the first 4 seconds of the stream and the input file is left at end of input.

As I understand it the purpose of `BlockAlignReductionStream` is to eager-buffer reads from the source stream to reduce the overheads of decoding, especially when processing small numbers of samples. Decoding 4 seconds at a time is a reasonable setting for this, we just need to deal with situations where the total requested size exceeds that.

Here's one idea that seems to work:

```
protected int TrimToBlock(int value) => value - (value % sourceStream.BlockAlign);

public override int Read(byte[] buffer, int offset, int count)
{
    lock (lockObject)
    {
        int currOffset = offset;
        int currCount =  TrimToBlock(count);
        int rc;
        while (currCount > 0)
        {
            // Fill circular buffer if it has less than one block
            if (circularBuffer.Count < sourceStream.BlockAlign)
            {
                int available = TrimToBlock(circularBuffer.MaxLength - circularBuffer.Count);
                rc = sourceStream.Read(GetSourceBuffer(available), 0, available);
                if (rc > 0)
                    circularBuffer.Write(sourceBuffer, 0, rc);
            }

            // Read next block of bytes from circular buffer
            // (Do we need to trim currCount here?)
            rc = circularBuffer.Read(buffer, currOffset, currCount);
            if (rc == 0)
                break;
            currOffset += rc;
            currCount -= rc;
        }
        
        // return value is change in offset
        return currOffset - offset;
    }
}
```

This retains the principle of reading good-sized chunks from the source and filling the output array as best we can before returning. This version apparently handles `CopyTo` correctly - well, it puts the right number of bytes into a `MemoryStream`. I assume they're the right ones 😄

[1]: https://github.com/naudio/NAudio/blob/1c710c24cdc6f82947a9e7deee6faa45658404a5/NAudio.Core/Wave/WaveStreams/BlockAlignReductionStream.cs#L147C32-L148
Hi Corey,

thx for keeping update me!
Seems there’s an solution in the near future.

Thx for your support!!

Regards
Dirk F.

Von: Corey Murtagh ***@***.***>
Gesendet: Freitag, 12. Mai 2023 03:54
An: naudio/NAudio ***@***.***>
Cc: Dirk Frischalowski ***@***.***>; Author ***@***.***>
Betreff: Re: [naudio/NAudio] Converting WAV to MP3 truncate file length (Issue #1022)


OK, found the issue. BlockAlignReductionStream.Read()<https://github.com/naudio/NAudio/blob/1c710c24cdc6f82947a9e7deee6faa45658404a5/NAudio.Core/Wave/WaveStreams/BlockAlignReductionStream.cs#L147C32-L148> fills the circular buffer by reading the next batch of samples from the source stream and writing them to the circular buffer. The problem comes in when the requested byte count is larger than the available (or total) space in the circular buffer, in which case the extra bytes are lost. And since filling the circular buffer happens in a loop that doesn't stop until it has enough bytes to satisfy the request, it reads and discards the entire source stream before continuing.

On my test run (.NET 6.0, Windows x64) CopyTo() reads in 128KB chunks and repeats until the returned count is zero. The end result is that I get the first 4 seconds of the stream and the input file is left at end of input.

As I understand it the purpose of BlockAlignReductionStream is to eager-buffer reads from the source stream to reduce the overheads of decoding, especially when processing small numbers of samples. Decoding 4 seconds at a time is a reasonable setting for this, we just need to deal with situations where the total requested size exceeds that.

Here's one idea that seems to work:

protected int TrimToBlock(int value) => value - (value % sourceStream.BlockAlign);



public override int Read(byte[] buffer, int offset, int count)

{

    lock (lockObject)

    {

        int currOffset = offset;

        int currCount =  TrimToBlock(count);

        int rc;

        while (currCount > 0)

        {

            // Fill circular buffer if it has less than one block

            if (circularBuffer.Count < sourceStream.BlockAlign)

            {

                int available = TrimToBlock(circularBuffer.MaxLength - circularBuffer.Count);

                rc = sourceStream.Read(GetSourceBuffer(available), 0, available);

                if (rc > 0)

                    circularBuffer.Write(sourceBuffer, 0, rc);

            }



            // Read next block of bytes from circular buffer

            // (Do we need to trim currCount here?)

            rc = circularBuffer.Read(buffer, currOffset, currCount);

            if (rc == 0)

                break;

            currOffset += rc;

            currCount -= rc;

        }



        // return value is change in offset

        return currOffset - offset;

    }

}

This retains the principle of reading good-sized chunks from the source and filling the output array as best we can before returning. This version apparently handles CopyTo correctly - well, it puts the right number of bytes into a MemoryStream. I assume they're the right ones 😄

—
Reply to this email directly, view it on GitHub<https://github.com/naudio/NAudio/issues/1022#issuecomment-1544991497>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AM57OD2OO27NELW4NBLQN33XFWJ2LANCNFSM6AAAAAAXCJQH64>.
You are receiving this because you authored the thread.Message ID: ***@***.***>

Hi @all,

we tested the solution above and it works fine! THANKS. Is there any timetable or so that this fix is included in a new version?

Regards
Dirk Frischalowski
I'm experiencing the same issue, I believe,
In my case, I'm pulling down multiple WAV files and converting them all to MP3 before joining them together.
Here's my code.
        MemoryStream output = new();
        foreach (var audioFile in audioFiles)
        {
            Mp3FileReader reader = new(new MemoryStream(audioFile.AudioFile.AudioFileData));
            if (output.Position == 0 && reader.Id3v2Tag != null)
            {
                output.Write(reader.Id3v2Tag.RawData, 0, reader.Id3v2Tag.RawData.Length);
            }
            Mp3Frame frame;
            while ((frame = reader.ReadNextFrame()) != null)
            {
                output.Write(frame.RawData, 0, frame.RawData.Length);
            }
        }
        return await Task.FromResult(output.ToArray());
        
  Any suggestions on how to address this?
  My use case is having AI read pieces of text and then assembling them into paragraphs and larger documents. A lot of AI voice generators won't handle the size of text I need to be read in one shot.
Thanks for reporting, have submitted a fix for this for NAudio 3. @sean-m-cooper  I think your issue is something else - feel free to open up a separate issue if you are still having problems (although I appreciate this was quite a long time ago)



Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 91e9e1058..e2c465509 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -78,6 +78,7 @@ extensive correctness work during development — see [Docs/Sampler.md](Docs/Sam
  * `Mp3FileReader`: fixed false sample-rate-change errors near end of file, and more robust MP3 frame parsing against album art / trailing metadata
  * `MidiFile`: preserve running-status across meta events (fixes "Read too far")
  * `BlockAlignReductionStream.Position`: validates the incoming value, so a block-aligned seek after an arbitrary read no longer wrongly throws (#368)
+ * `BlockAlignReductionStream.Read`: a single read (or `Stream.CopyTo` chunk) larger than the 4-second internal buffer no longer silently discards the rest of the source and truncates the stream — e.g. converting a non-PCM WAV via `AudioFileReader` on .NET Core (#1022)
  * `MmException` messages now append a human-readable `MmResult` description (#1192); `Id3v2Tag.ReadTag` no longer throws/catches for tagless MP3 streams (#265)
  * ASIO: implemented missing `Asio64Bit` Int24LSB/Float32LSB conversions and fixed a byte-order bug in `GetSamplePosition`; `WdlResampler` backported three upstream Cockos WDL fixes
  * Hardened Media Foundation and DMO interop against COM ref leaks on error paths (`MediaFoundationReader`, `MediaFoundationEncoder`, `MediaFoundationTransform`, `MediaBuffer`, `Mf*` wrappers) (#1293)
diff --git a/src/NAudio.Core/Wave/WaveStreams/BlockAlignReductionStream.cs b/src/NAudio.Core/Wave/WaveStreams/BlockAlignReductionStream.cs
index 3eba57484..4754f8a85 100644
--- a/src/NAudio.Core/Wave/WaveStreams/BlockAlignReductionStream.cs
+++ b/src/NAudio.Core/Wave/WaveStreams/BlockAlignReductionStream.cs
@@ -129,13 +129,22 @@ public override int Read(Span<byte> buffer)
         int count = buffer.Length;
         lock (lockObject)
         {
-            // 1. attempt to fill the circular buffer with enough data to meet our request
+            // 1. attempt to fill the circular buffer with enough data to meet our request.
+            // We must never ask the source for more than will fit in the free space: the
+            // circular buffer silently discards any overflow, so a request larger than the
+            // buffer would otherwise read and drop the rest of the source stream, truncating
+            // the output to the buffer's capacity (#1022). Each source read is rounded down to
+            // a whole number of source blocks; a request bigger than the buffer is satisfied
+            // across multiple passes / Read calls instead.
             while (BufferEndPosition < position + count)
             {
-                int sourceReadCount = count;
-                if (sourceReadCount % sourceStream.BlockAlign != 0)
+                int freeSpace = circularBuffer.MaxLength - circularBuffer.Count;
+                int sourceReadCount = freeSpace - (freeSpace % sourceStream.BlockAlign);
+                if (sourceReadCount == 0)
                 {
-                    sourceReadCount = (count + sourceStream.BlockAlign) - (count % sourceStream.BlockAlign);
+                    // buffer is full to within less than one source block - we have staged
+                    // as much as we can for now
+                    break;
                 }
 
                 byte[] sourceBuf = GetSourceBuffer(sourceReadCount);


Test patch:
diff --git a/tests/NAudio.Core.Tests/WaveStreams/BlockAlignmentReductionStreamTests.cs b/tests/NAudio.Core.Tests/WaveStreams/BlockAlignmentReductionStreamTests.cs
index 54e8e95c6..64b0708c6 100644
--- a/tests/NAudio.Core.Tests/WaveStreams/BlockAlignmentReductionStreamTests.cs
+++ b/tests/NAudio.Core.Tests/WaveStreams/BlockAlignmentReductionStreamTests.cs
@@ -22,8 +22,8 @@ public void CanCreateBlockAlignmentReductionStream()
     public void CanReadNonBlockAlignedLengths()
     {
         BlockAlignedWaveStream inputStream = new BlockAlignedWaveStream(726, 80000);
-        BlockAlignReductionStream blockStream = new BlockAlignReductionStream(inputStream);
-
+        BlockAlignReductionStream blockStream = new BlockAlignReductionStream(inputStream);
+
         byte[] inputBuffer = new byte[1024];
         int read = blockStream.Read(inputBuffer, 0, 1024);
         Assert.That(read, Is.EqualTo(1024), "bytes read 1");
@@ -90,6 +90,39 @@ public void CanRepositionAfterNonBlockAlignedRead()
         CheckReadBuffer(readBuffer, 1024, 2048);
     }
 
+    [Test]
+    public void ReadLargerThanBufferReturnsAllData()
+    {
+        // Reproduces #1022: a single read (or CopyTo chunk) larger than the
+        // internal circular buffer - sized at WaveFormat.AverageBytesPerSecond * 4 -
+        // used to truncate the stream. The fill loop asked the source for the whole
+        // request at once; CircularBuffer.Write silently dropped everything past its
+        // capacity and the loop then drained and discarded the rest of the source.
+        // The whole stream must come back regardless of read size.
+        const int streamLength = 80000;
+        BlockAlignedWaveStream inputStream = new BlockAlignedWaveStream(726, streamLength);
+        BlockAlignReductionStream blockStream = new BlockAlignReductionStream(inputStream);
+
+        // 8000 Hz * 2 bytes = 16000 AverageBytesPerSecond -> 64000 byte buffer.
+        // Read more than that in one call to exercise the overflow path.
+        Assert.That(blockStream.WaveFormat.AverageBytesPerSecond * 4, Is.LessThan(streamLength),
+            "test must request more than one buffer's worth in a single read");
+
+        byte[] readBuffer = new byte[streamLength];
+        int total = 0;
+        int read;
+        // Read may legitimately return a partial buffer (capacity-limited); keep
+        // reading until the stream is exhausted, exactly as Stream.CopyTo does.
+        while (total < streamLength && (read = blockStream.Read(readBuffer, total, streamLength - total)) > 0)
+        {
+            total += read;
+        }
+
+        Assert.That(total, Is.EqualTo(streamLength), "total bytes read");
+        Assert.That(blockStream.Position, Is.EqualTo(streamLength), "final position");
+        CheckReadBuffer(readBuffer, streamLength, 0);
+    }
+
     [Test]
     public void RepositionToNonBlockAlignedPositionThrows()
     {