Saturday 26 September 2009

Trimming a WAV file using NAudio

I’m hoping to write a few brief code snippets to demonstrate various uses of NAudio, to eventually form the basis of an FAQ. This example shows how you can take a WAV file and trim a section out of it. You specify the TimeSpan to remove from the beginning and end, as well as an output WAV file. Please note this will only be reliable with PCM format WAV files.

public static class WavFileUtils
{
    public static void TrimWavFile(string inPath, string outPath, TimeSpan cutFromStart, TimeSpan cutFromEnd)
    {
        using (WaveFileReader reader = new WaveFileReader(inPath))
        {
            using (WaveFileWriter writer = new WaveFileWriter(outPath, reader.WaveFormat))
            {
                int bytesPerMillisecond = reader.WaveFormat.AverageBytesPerSecond / 1000;

                int startPos = (int)cutFromStart.TotalMilliseconds * bytesPerMillisecond;
                startPos = startPos - startPos % reader.WaveFormat.BlockAlign;

                int endBytes = (int)cutFromEnd.TotalMilliseconds * bytesPerMillisecond;
                endBytes = endBytes - endBytes % reader.WaveFormat.BlockAlign;
                int endPos = (int)reader.Length - endBytes; 

                TrimWavFile(reader, writer, startPos, endPos);
            }
        }
    }

    private static void TrimWavFile(WaveFileReader reader, WaveFileWriter writer, int startPos, int endPos)
    {
        reader.Position = startPos;
        byte[] buffer = new byte[1024];
        while (reader.Position < endPos)
        {
            int bytesRequired = (int)(endPos - reader.Position);
            if (bytesRequired > 0)
            {
                int bytesToRead = Math.Min(bytesRequired, buffer.Length);
                int bytesRead = reader.Read(buffer, 0, bytesToRead);
                if (bytesRead > 0)
                {
                    writer.WriteData(buffer, 0, bytesRead);
                }
            }
        }
    }
}

10 comments:

  1. Hello,

    Was pleased with this example.
    Tried it out with NAudio 1.3 (which was totally new to me) and worked perfectly on regular wav disk files.

    My scenario involed in-memory Waves so I was pleased to see that (apparently starting with 1.3 release) WaveFileReader/Writer did support Streams.

    Problem is that in the current WaveFileWriter class design, wave header and length is only updated when the object is disposed... which is ok when writing to a file, a little bit less when the stream is from another kind, because we might want to copy the result stream somewhere before disposing it.

    Correct me if I'm wrong, but I was thinking of copying the block of code that updates the header from the Dispose method into the Flush method. Do you think that could do the job without breaking anything else?

    UPDATE: Actually I did try this quick fix and it dit it, at least to me. Keep on the good work on this very helpful library.

    Regards
    Frederic

    ReplyDelete
  2. Can we use NAudio for web application..?

    ReplyDelete
  3. the web server can use NAudio to manipulate audio files, but NAudio cannot be used on the client side in the browser

    ReplyDelete
  4. I have tried to implement this code in Visual Basic, but am getting an error
    "Must read complete blocks" at
    Dim bytesRead As Integer = reader.Read(buffer, 0, bytesToRead)
    when invoking as follows:

    Sub TrimWavFile(ByVal inPath As String, ByVal outPath As String, _
    ByVal cutFromStart As TimeSpan, ByVal cutFromEnd As TimeSpan)
    Dim reader As WaveFileReader = New WaveFileReader(inPath)
    Dim writer As WaveFileWriter = New WaveFileWriter(outPath)
    Dim bytesPerMillisecond As Integer = reader.WaveFormat.AverageBytesPerSecond / 1000
    Dim startPos As Integer = cutFromStart.TotalMilliseconds * bytesPerMillisecond
    startPos = startPos - startPos Mod reader.WaveFormat.BlockAlign
    Dim endBytes As Integer = cutFromEnd.TotalMilliseconds * bytesPerMillisecond
    endBytes = endBytes - endBytes Mod reader.WaveFormat.BlockAlign
    Dim endPos As Integer = reader.Length - endBytes
    TrimWavFile(reader, writer, startPos, endPos)
    End Sub

    Sub TrimWavFile(ByVal reader As WaveFileReader, ByVal writer As WaveFileWriter, ByVal startPos As Integer, ByVal endPos As Integer)
    reader.Position = startPos
    Dim buffer(1024) As Byte
    While reader.Position < endPos
    Dim bytesRequired As Integer = endPos - reader.Position
    If bytesRequired > 0 Then
    Dim bytesToRead As Integer = Math.Min(bytesRequired, buffer.Length)
    Dim bytesRead As Integer = reader.Read(buffer, 0, bytesToRead)
    If (bytesRead > 0) Then
    writer.WriteData(buffer, 0, bytesRead)
    End If
    End If
    End While
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    TrimWavFile("F:\VBProjectsDevelopment\3DTrackDisplay1\Sounds\tonovario.wav", _
    "F:\VBProjectsDevelopment\3DTrackDisplay1\Sounds\tonovarioShort.wav", _
    New TimeSpan(10000000), New TimeSpan(10000000))

    End Sub

    My C# coding level is about 3 out of 10 so maybe I've missed something in the migration.

    ReplyDelete
  5. what is the value of bytesToRead?

    ReplyDelete
  6. Thanks for the snippet. This worked a treat for me!

    I did spot one issue though - there's a rounding error when calculating bytesPerMillisecond. If you change it from an int to a double that should sort it.

    Also, for what it's worth the 'Must read complete blocks' error is because the data must be read in multiples of reader.WaveFormat.BlockAlign. I had this issue reading a GSM file with a block align of 65, so I set the buffer size to BlockAlign * 100.

    ReplyDelete
  7. Getting the "Must read complete blocks" error. Is this due to selecting a bad TimeSpan; or an oddly formed WAV file. My is 13kbps bitrate. I have 63 bytes left.

    ReplyDelete
  8. Getting the "Must read complete blocks" error. Is this due to selecting a bad TimeSpan; or an oddly formed WAV file. My is 13kbps bitrate. I have 63 bytes left.

    ReplyDelete
  9. instead of a buffer size of 1024, make sure it is sized to a multiple of your WaveFormat.BlockAlign.

    ReplyDelete
  10. instead of a buffer size of 1024, make sure it is sized to a multiple of your WaveFormat.BlockAlign.

    ReplyDelete