Click here to Skip to main content
15,867,568 members
Articles / Mobile Apps / Windows Phone 7

Writing a Proper Wave File

Rate me:
Please Sign up or sign in to vote.
5.00/5 (14 votes)
21 Nov 2010CPOL3 min read 54.5K   24   10
Playing back data that was recorded from the Microphone, in Windows Phone 7

Currently one of the recurring questions I see in the Windows Phone 7 forums deals with playing back data that was recorded from the microphone. Often time, developers will write the sound bytes that they receive from a microphone to a file and then try to export them for playback or play them back using the media classes on the phone only to find that the file can't be processed. During my lunch break today, I had a chance to throw something together that I think will point those developers in the right direction.

Why Won't the File Play

The file won't play because none of the components or software which it has, has been given to know anything about the file. If you record from the microphone and dump the raw bytes to a file, the things you are not writing include the sample rate, number of bits per sample, the file format, and so on. You need to prepend the file with all of these things for it to be usable by the media classes. Having done a quick Bing search, I found a description of the needed header here. Using that, I put together a quick desktop application that produces a playable wave file. I targeted the desktop because the computer I'm using doesn't have the phone developer tools. But the code will pretty much be the same for the desktop as on the phone. The only difference will be in the creation of your file. While I am creating a file stream directly, you would create a stream in isolated storage.

Simulating Audio Data

I need some data to write to my file. As is my preference, I've created a function that will populate an array of bytes with the output of the Sine function. As its parameters, it takes the sample rate, the length of time that I want the sound to play, the wave's frequency, and it's magnitude (with 0 being the lowest magnitude and 1 being the greatest) and returns the data in a byte array. You would populate your array with the bytes from the recording instead. The code I used to do this follows:

C#
public static byte[] CreateSinWave( 
        int sampleRate, 
        double frequency, 
        TimeSpan length, 
        double magnitude
    )
{
    int sampleCount = (int)(((double)sampleRate) * length.TotalSeconds);
    short[] tempBuffer = new short[sampleCount];
    byte[] retVal = new byte[sampleCount*2];
    double step = Math.PI*2.0d/frequency;
    double current = 0;
            
    for(int i=0;i<tempBuffer.Length;++i)
    {
        tempBuffer[i] = (short)(Math.Sin(current) * magnitude * ((double)short.MaxValue));
        current += step;
    }

    Buffer.BlockCopy(tempBuffer,0,retVal,0,retVal.Length);
    return retVal;
}

Populating the Wave Header

There are better ways to do this, much better ways. But I'm just trying to create something satisficing in a short period of time.

Trival Fact: Satisficing is a phrase coined by Herbert Simon to mean sufficiently satisfying. A satisficing solution may not be the best solution, but it get's the job done!

Looking on the chart that describes a wave header, I wrote either literal bytes or calculated values, where the calculated values are based on sample rate, number of channels, and a few other factors. There's not a lot to say about it, but the code follows:

C#
static byte[] RIFF_HEADER = new byte[] { 0x52, 0x49, 0x46, 0x46 };
static byte[] FORMAT_WAVE = new byte[] { 0x57, 0x41, 0x56, 0x45 };
static byte[] FORMAT_TAG  = new byte[] { 0x66, 0x6d, 0x74, 0x20 };
static byte[] AUDIO_FORMAT = new byte[] {0x01, 0x00};
static byte[] SUBCHUNK_ID  = new byte[] { 0x64, 0x61, 0x74, 0x61 };
private const int BYTES_PER_SAMPLE = 2;

public static void WriteHeader(
     System.IO.Stream targetStream, 
     int byteStreamSize, 
     int channelCount, 
     int sampleRate)
{
    int byteRate = sampleRate*channelCount*BYTES_PER_SAMPLE;
    int blockAlign = channelCount*BYTES_PER_SAMPLE;

    targetStream.Write(RIFF_HEADER,0,RIFF_HEADER.Length);
    targetStream.Write(PackageInt(byteStreamSize+42, 4), 0, 4);

    targetStream.Write(FORMAT_WAVE, 0, FORMAT_WAVE.Length);
    targetStream.Write(FORMAT_TAG, 0, FORMAT_TAG.Length);
    targetStream.Write(PackageInt(16,4), 0, 4);//Subchunk1Size    

    targetStream.Write(AUDIO_FORMAT, 0, AUDIO_FORMAT.Length);//AudioFormat   
    targetStream.Write(PackageInt(channelCount, 2), 0, 2);
    targetStream.Write(PackageInt(sampleRate, 4), 0, 4);
    targetStream.Write(PackageInt(byteRate, 4), 0, 4);
    targetStream.Write(PackageInt(blockAlign, 2), 0, 2);
    targetStream.Write(PackageInt(BYTES_PER_SAMPLE*8), 0, 2);
    //targetStream.Write(PackageInt(0,2), 0, 2);//Extra param size
    targetStream.Write(SUBCHUNK_ID, 0, SUBCHUNK_ID.Length);
    targetStream.Write(PackageInt(byteStreamSize, 4), 0, 4);
}

static byte[] PackageInt(int source, int length=2)
{
    if((length!=2)&&(length!=4))
        throw new ArgumentException("length must be either 2 or 4", "length");
    var retVal = new byte[length];
    retVal[0] = (byte)(source & 0xFF);
    retVal[1] = (byte)((source >> 8) & 0xFF);
    if (length == 4)
    {
        retVal[2] = (byte) ((source >> 0x10) & 0xFF);
        retVal[3] = (byte) ((source >> 0x18) & 0xFF);
    }
    return retVal;
}

That's pretty much all you need to know. To use the code, I wrote a simple console mode program.

C#
static void Main(string[] args)
{
    var soundData = WaveHeaderWriter.CreateSinWave(44000, 120, TimeSpan.FromSeconds(60), 1d);
    using(FileStream fs = new FileStream("MySound2.wav", FileMode.Create))
    {
        WaveHeaderWriter.WriteHeader(fs, soundData.Length, 1, 44100);
        fs.Write(soundData,0,soundData.Length);
        fs.Close();
    }
}

I opened the resulting output in Audacity and the results are what I expected.

And of course, as a final test, I double clicked on the file. It opened in Windows Media Player and played the Sine wave.

So there you have it, the program works! When I get a chance, I will try to make a version of this in Windows Phone 7. Those of you that have WPDT without the full version of Visual Studio will not be able to compile this program directly. But the binary is included in the source code if you want to run it.

License

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


Written By
Software Developer
United States United States
I attended Southern Polytechnic State University and earned a Bachelors of Science in Computer Science and later returned to earn a Masters of Science in Software Engineering. I've largely developed solutions that are based on a mix of Microsoft technologies with open source technologies mixed in. I've got an interest in astronomy and you'll see that interest overflow into some of my code project articles from time to time.



Twitter:@j2inet

Instagram: j2inet


Comments and Discussions

 
GeneralMy vote of 5 Pin
Kanasz Robert21-Sep-12 0:45
professionalKanasz Robert21-Sep-12 0:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.