Search code examples
c#naudio

Save streaming data to a WAV file using NAudio


I want to save the incoming stream data to a WAV file on my hard disk drive. How can I change the code below to be able to record the stream into a valid WAV file?

From the demo here:

private void StreamMP3(object state)
{
    this.fullyDownloaded = false;
    string url = (string)state;
    webRequest = (HttpWebRequest)WebRequest.Create(url);
    HttpWebResponse resp = null;
    try
    {
        resp = (HttpWebResponse)webRequest.GetResponse();
    }
    catch(WebException e)
    {
        if (e.Status != WebExceptionStatus.RequestCanceled)
        {
            ShowError(e.Message);
        }
        return;
    }
    byte[] buffer = new byte[16384 * 4]; // Needs to be big enough to hold a decompressed frame

    IMp3FrameDecompressor decompressor = null;
    try
    {
        using (var responseStream = resp.GetResponseStream())
        {
            var readFullyStream = new ReadFullyStream(responseStream);
            do
            {
                if (bufferedWaveProvider != null &&
                    bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes <
                      bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4)
                {
                    Debug.WriteLine("Buffer getting full, taking a break");
                    Thread.Sleep(500);
                }
                else
                {
                    Mp3Frame frame = null;
                    try
                    {
                        frame = Mp3Frame.LoadFromStream(readFullyStream);
                    }
                    catch (EndOfStreamException)
                    {
                        this.fullyDownloaded = true;

                        // Reached the end of the MP3 file / stream
                        break;
                    }
                    catch (WebException)
                    {
                        // Probably we have aborted download from the GUI thread
                        break;
                    }
                    if (decompressor == null)
                    {
                        // I don't think these details matter too much - just help ACM select the right codec.
                        // However, the buffered provider doesn't know what sample rate it is working at
                        // until we have a frame.
                        WaveFormat waveFormat = new Mp3WaveFormat(
                                                  frame.SampleRate,
                                                  frame.ChannelMode == ChannelMode.Mono ? 1 : 2,
                                                  frame.FrameLength,
                                                  frame.BitRate);
                        decompressor = new AcmMp3FrameDecompressor(waveFormat);
                        this.bufferedWaveProvider = new BufferedWaveProvider(decompressor.OutputFormat);
                        this.bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(20); // Allow us to get well ahead of ourselves
                        //this.bufferedWaveProvider.BufferedDuration = 250;
                    }
                    int decompressed = decompressor.DecompressFrame(frame, buffer, 0);
                    //Debug.WriteLine(String.Format("Decompressed a frame {0}", decompressed));
                    bufferedWaveProvider.AddSamples(buffer, 0, decompressed);
                }

            } while (playbackState != StreamingPlaybackState.Stopped);

            Debug.WriteLine("Exiting");
            // I was doing this in a finally block, but for some reason
            // we are hanging on response stream .Dispose, so we never get there.
            decompressor.Dispose();
        }
    }
    finally
    {
        if (decompressor != null)
        {
            decompressor.Dispose();
        }
    }
}

Solution

  • I wouldn't take that particular approach to saving to disk. It's a bit too hands-on, because it has to deal with playing back at the right rate. Just buffer up the response, and then wrap it in an Mp3FileReader stream and use WaveFileWriter to write the WAV file:

    MemoryStream mp3Buffered = new MemoryStream();
    using (var responseStream = resp.GetResponseStream())
    {
        byte[] buffer = new byte[65536];
        int bytesRead = responseStream.Read(buffer, 0, buffer.Length);
        while (bytesRead > 0)
        {
            mp3Buffered.Write(buffer, 0, bytesRead);
            bytesRead = responseStream.Read(buffer, 0, buffer.Length);
        }
    }
    
    mp3Buffered.Position = 0;
    using (var mp3Stream = new Mp3FileReader(mp3Buffered))
    {    
        WaveFileWriter.CreateWaveFile("file.wav", mp3Stream);
    }
    

    That does, of course, assume that your MP3 file's wave format is compatible with WAV and in particular, your WAV player. If it isn't, you'll need to inject and add a WaveFormatConversion stream as well.