Click here to Skip to main content
15,892,643 members
Articles / Desktop Programming / Win32

C# MP3 Sound Capturing/Recording Component

Rate me:
Please Sign up or sign in to vote.
4.75/5 (47 votes)
20 Oct 2009CPOL8 min read 467.7K   33.3K   186  
A .NET component capturing WAVE or MP3 sound from a sound card. LAME used for MP3 compression.
//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER 
//  REMAINS UNCHANGED.
//
//  Email:  lukasz@istrib.org
//
//  Copyright (C) 2008-2009 Lukasz Kwiecinski. 
//

using System;
using System.Collections.Generic;

using System.Text;
using System.IO;
using Istrib.Sound.Formats;

namespace Istrib.Sound.Filters
{
    /// <summary>
    /// Adds a RIFF header (like in WAV files stored on disk) to the Sound Stream.
    /// Header is added when the streaming starts, but it has to be modified each time
    /// the streaming stops (file length is written in the header). That's why the output
    /// stream attached to this filter MUST SUPPORT SEEKING (it's usually a file stream).
    /// </summary>
    public class RiffFilter
        : SoundFilter
    {
        private bool headerWritten = false;
        private InputSplittingStream input = new InputSplittingStream();
        private BinaryWriter writer;

        public override System.IO.Stream Input
        {
            get 
            {
                return input;
            }
        }

        /// <summary>
        /// Raw PCM.
        /// </summary>
        public override Type RequiredInputFormatType
        {
            get 
            {
                return typeof(PcmSoundFormat);
            }
        }

        /// <summary>
        /// Parameters of raw PCM written into Input stream.
        /// </summary>
        public new PcmSoundFormat InputFormat
        {
            get
            {
                return (PcmSoundFormat)base.InputFormat;
            }
        }

        /// <summary>
        /// RIFF of the same parameters as input PCM.
        /// </summary>
        public override ISoundFormat OutputFormat
        {
            get 
            {
                return new RiffSoundFormat(InputFormat); 
            }
        }

        private void ProcessInputDataChunk(object sender, InputSplittingStream.ChunkWrittenEventArgs e)
        {
            WriteHeaderIfNeeded();
            Output.Write(e.Chunk, 0, e.Chunk.Length);
        }

        private void WriteHeaderIfNeeded()
        {
            if (!headerWritten)
            {
                this.writer = new BinaryWriter(Output);

                if (Output == null || !Output.CanSeek)
                {
                    throw new ArgumentException("The output stream for RiffFilter must support seeking. '" + Output + "' does not.");
                }

                /**************************************************************************
        			 
			        Here is where the file will be created. A
			        wave file is a RIFF file, which has chunks
			        of data that describe what the file contains.
			        A wave RIFF file is put together like this:
        			 
			        The 12 byte RIFF chunk is constructed like this:
			        Bytes 0 - 3 :	'R' 'I' 'F' 'F'
			        Bytes 4 - 7 :	Length of file, minus the first 8 bytes of the RIFF description.
							        (4 bytes for "WAVE" + 24 bytes for format chunk length +
							        8 bytes for data chunk description + actual sample data size.)
			        Bytes 8 - 11:	'W' 'A' 'V' 'E'
        			
			        The 24 byte FORMAT chunk is constructed like this:
			        Bytes 0 - 3 :	'f' 'm' 't' ' '
			        Bytes 4 - 7 :	The format chunk length. This is always 16.
			        Bytes 8 - 9 :	File padding. Always 1.
			        Bytes 10- 11:	Number of channels. Either 1 for mono,  or 2 for stereo.
			        Bytes 12- 15:	Sample rate.
			        Bytes 16- 19:	Number of bytes per second.
			        Bytes 20- 21:	Bytes per sample. 1 for 8 bit mono, 2 for 8 bit stereo or
							        16 bit mono, 4 for 16 bit stereo.
			        Bytes 22- 23:	Number of bits per sample.
        			
			        The DATA chunk is constructed like this:
			        Bytes 0 - 3 :	'd' 'a' 't' 'a'
			        Bytes 4 - 7 :	Length of data, in bytes.
			        Bytes 8 -...:	Actual sample data.
        			
		        ***************************************************************************/

                // Set up file with RIFF chunk info.
                char[] ChunkRiff = { 'R', 'I', 'F', 'F' };
                char[] ChunkType = { 'W', 'A', 'V', 'E' };
                char[] ChunkFmt = { 'f', 'm', 't', ' ' };
                char[] ChunkData = { 'd', 'a', 't', 'a' };

                short shPad = 1; // File padding
                int nFormatChunkLength = 0x10; // Format chunk length.
                int nLength = 0; // File length, minus first 8 bytes of RIFF description. This will be filled in later.
                short shBytesPerSample = 0; // Bytes per sample.

                // Figure out how many bytes there will be per sample.
                if (8 == InputFormat.BitsPerSample && 1 == InputFormat.Channels)
                    shBytesPerSample = 1;
                else if ((8 == InputFormat.BitsPerSample && 2 == InputFormat.Channels) || (16 == InputFormat.BitsPerSample && 1 == InputFormat.Channels))
                    shBytesPerSample = 2;
                else if (16 == InputFormat.BitsPerSample && 2 == InputFormat.Channels)
                    shBytesPerSample = 4;

                // Fill in the riff info for the wave file.
                writer.Write(ChunkRiff);
                writer.Write(nLength);
                writer.Write(ChunkType);

                // Fill in the format info for the wave file.
                writer.Write(ChunkFmt);
                writer.Write(nFormatChunkLength);
                writer.Write(shPad);
                writer.Write(InputFormat.Channels);
                writer.Write(InputFormat.SampleRate);
                writer.Write(InputFormat.AverageBytesPerSecond);
                writer.Write(shBytesPerSample);
                writer.Write(InputFormat.BitsPerSample);

                // Now fill in the data chunk.
                writer.Write(ChunkData);
                writer.Write((int)0);	// The sample length will be written in later.

                writer.Flush();

                headerWritten = true;
            }
        }

        protected internal override void OnStreamingStoppedSuspended(SoundStreamingStatus streamingStatus)
        {
            base.OnStreamingStoppedSuspended(streamingStatus);

            if (Output != null && writer != null)
            {
                Output.Flush();
                //Set the sample lenght. It may be later overriden if the streaming is again resumed.

                long curPos = Output.Position;

                writer.Seek(4, SeekOrigin.Begin); // Seek to the length descriptor of the RIFF file.
                writer.Write((int)(streamingStatus.BytesPassed + 36));	// Write the file length, minus first 8 bytes of RIFF description.
                writer.Seek(40, SeekOrigin.Begin); // Seek to the data length descriptor of the RIFF file.
                writer.Write(streamingStatus.BytesPassed); // Write the length of the sample data in bytes.

                //Return to the oryginal position (we're assuming that more data can be appended).
                //But if not, if the stream is closed then the above assures correct header values.
                Output.Position = curPos;

                Output.Flush();
            }
        }

        public RiffFilter()
        {
            input.ChunkWritten += new EventHandler<InputSplittingStream.ChunkWrittenEventArgs>(ProcessInputDataChunk);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
StaffPlan, Istrib
Poland Poland
I am a .NET developer, working currently in the UK, though still strongly associated with Poland. Primary interests: business application, server-side, enterprise-scale solutions, SOA, thin but rich internet clients (Silverlight).
Apart from my everyday work, I spend a lot of time validating design patterns for newer technologies (like Silverlight, WCF, LINQ). My warzone is www.nauka-slowek.org - a proof of concept for asynchrony, internet multimedia and command pattern in client-server apps. I also use that website myself as it is a great tool to effectively learn vocabulary (foreign languages), which aids my long being abroad.

Comments and Discussions