Streaming from the Microphone to IsolatedStorage in Windows Phone 7

No.of Views1087
Bookmarked0 times
Downloads 
Votes0
By  j2inet   On  21 May 2011 21:05:50
Tag : Windows Phone , Miscellaneous
In this article I will explain, how to handle buffer the entire recording in memory before writing it to a file. A rather astute reader asked me what would happen if the user let the recording go long enough to fill up memory. The answer to that question is the application would crash due to an exception being thrown when it fails to allocate more memory and all of the recording would be lost.
emailbookmarkadd commentsprint

Images in this article missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at info@codegain.com

 

Introduction

In this article I will explain, how to handle buffer the entire recording in memory before writing it to a file. A rather astute reader asked me what would happen if the user let the recording go long enough to fill up memory. The answer to that question is the application would crash due to an exception being trhown when it fails to allocate more memory and all of the recordingwould be lost. I had already been thinking of a sime reusable solution for doing this but I also offered to the user the following code sample to handle streaming directly to IsolatedStorage.

My two goals in writing it were to keep it simple and keep it portable/reusable. As far as usage goes I can't think of any ways to make it any easier.

//To start a recording
   StreamingRecorder myRecorder  = new StreamingRecorder();
   myRecorder.Start("myFileName");

  //To stop a recording();
  myRecorder.Stop();

After the code has run you will have a WAVE file with a proper header ready to be consumed by a SoundEffect, MediaElement, or whatever it is that you want to do with it.

In implementing this I must say that I have a hiher appreciation for how MediaElement's interface is designed. The starting and stopping process are not immediate. In otherwords when you call Start() or Stop() it is not until a few moments later that the request is fully processed. Because of the asynchronous nature of these processes I've implemented the event RecordingStateChanged and the property RecordingState so that I would know when a state change was complete. If you are familiar with the media element class then your recognize the similarity of this pattern.
I'll go into further details on how this works along with implemeting some other functionality (such as a Pause method) in a later post. But the code is in a working state now so I'm sharing it.

C# Code

public class StreamingRecorder :INotifyPropertyChanged,  IDisposable
{


    object SyncLock = new object();

    private Queue<MemoryStream> _availablBufferQueue;        
    private Queue<MemoryStream> _writeBufferQueue;

    private int _bufferCount;
    private byte[] _audioBuffer;

    //private int _currentRecordingBufferIndex;
        

    private TimeSpan _bufferDuration;
    private int _bufferSize;
    private Stream _outputStream;
    private Microphone _currentMicrophone;
    private bool _ownsStream = false;
    private long _startPosition;

    

    public  StreamingRecorder(TimeSpan? bufferDuration = null, int bufferCount=2)
    {
        _bufferDuration = bufferDuration.HasValue ? bufferDuration.Value : TimeSpan.FromSeconds(0);
        _bufferCount = bufferCount;
        _currentMicrophone= Microphone.Default;   
    }

    private MemoryStream CurrentBuffer
    {
        get; set;
    }

    public void Start(string fileName)
    {
        var isoStore = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication();
        var targetFile = isoStore.OpenFile(fileName, FileMode.Create);
        Start(targetFile, true);
    }

    public void Start(Stream outputStream, bool ownsStream=false)
    {
        _outputStream = outputStream;
        _ownsStream = ownsStream;
        _startPosition = outputStream.Position;

        Size = 0;

        //Create our recording buffers
        _availablBufferQueue = new Queue<MemoryStream>();
        _writeBufferQueue = new Queue<MemoryStream>();
        _audioBuffer = new byte[_currentMicrophone.GetSampleSizeInBytes(_currentMicrophone.BufferDuration)];
        _bufferSize = _currentMicrophone.GetSampleSizeInBytes(_bufferDuration + _currentMicrophone.BufferDuration);
        for (var i = 0; i < _bufferCount; ++i)
        {
            _availablBufferQueue.Enqueue(new MemoryStream(_bufferSize));
        }

        CurrentBuffer = _availablBufferQueue.Dequeue();
        //Stuff a bogus wave header in the output stream as a space holder.
        //we will come back and make it valid later. For now the size is invalid.
        //I could have just as easily stuffed any set of values here as long as 
        //the size of those values equaled 0x2C
        WaveHeaderWriter.WriteHeader(CurrentBuffer, -1, 1, _currentMicrophone.SampleRate);
        Size += (int)CurrentBuffer.Position;

        //Subscribe to the Microphone's buffer ready event and start listening.
        _currentMicrophone.BufferReady += new EventHandler<EventArgs>(_currentMicrophone_BufferReady);            
        _currentMicrophone.Start();
    }


    void _currentMicrophone_BufferReady(object sender, EventArgs e)
    {
        _currentMicrophone.GetData(_audioBuffer);
        //If the recorder is paused (not implemented) then don't add this audio chunk to
        // the output. If HasFlushed is set then the recording is actually ready to shut
        //down and we shouldn't accumulate anything more. 
        if ((CurrentState != RecordingState.Paused))
        {
            //Append the audio chunk to our current buffer
            CurrentBuffer.Write(_audioBuffer, 0, _audioBuffer.Length);
            //Increment the size of the recording.
            Size += _audioBuffer.Length;
            //If the buffer is full or if we are shutting down then we need to submit
            //the buffer to be written to the output stream.
            if ((CurrentBuffer.Length > _bufferSize)||(CurrentState==RecordingState.Stopping))
            {

                SubmitToWriteBuffer(CurrentBuffer);
                //If we were shutting down then set a flag so that it is known that the last audio
                //chunk has been written. 
                if (CurrentState == RecordingState.Stopping)
                {
                    _currentMicrophone.Stop();
                    _currentMicrophone.BufferReady -= _currentMicrophone_BufferReady;
                }
                CurrentBuffer = _availablBufferQueue.Count > 0 ? _availablBufferQueue.Dequeue() : new MemoryStream();
            }
        }
    }

                

    // CurrentState - generated from ObservableField snippet - Joel Ivory Johnson

    private RecordingState _currentState;
    public RecordingState CurrentState
    {
        get { return _currentState; }
        set
        {
            if (_currentState != value)
            {
                _currentState = value;
                OnPropertyChanged("CurrentState");
                OnRecordingStateChanged(value);
            }
        }
    }
    //-----


    void WriteData(object a )
    {

        lock(SyncLock)
        {                
            while (_writeBufferQueue.Count > 0)
            {
                var item = _writeBufferQueue.Dequeue();
                var buffer = item.GetBuffer();
                _outputStream.Write(buffer, 0,(int) item.Length);
                item.SetLength(0);

                _availablBufferQueue.Enqueue(item);

                if (CurrentState == RecordingState.Stopping)
                {
                    //Correct the information in the wave header. After it is
                    //written set the file pointer back to the end of the file.
                    long prePosition = _outputStream.Position;
                    _outputStream.Seek(_startPosition, SeekOrigin.Begin);
                    WaveHeaderWriter.WriteHeader(_outputStream,Size-44,1,_currentMicrophone.SampleRate);
                    _outputStream.Seek(prePosition, SeekOrigin.Begin);
                    _outputStream.Flush();
                    if (_ownsStream)
                        _outputStream.Close();
                    CurrentState = RecordingState.Stopped;
                }
            }
        }
    }

    void SubmitToWriteBuffer(MemoryStream target)
    {
        //Do the writing on another thread so that processing on this thread can continue. 
        _writeBufferQueue.Enqueue(target);
        ThreadPool.QueueUserWorkItem(new WaitCallback(WriteData));
    }

    public void Pause()
    {
        if ((CurrentState != RecordingState.Paused) && (CurrentState != RecordingState.Recording))
        {
            throw new Exception("you can't pause if you are not recording");
        }
        CurrentState = RecordingState.Paused;
    }

    public void Stop()
    {
        CurrentState = RecordingState.Stopping;
    }


    // Size - generated from ObservableField snippet - Joel Ivory Johnson

    private int  _size;
    public int Size
    {
        get { return _size; }
        set
        {
            if (_size != value)
            {
                _size = value;
                OnPropertyChanged("Size");
            }
        }
    }
    //-----

    public long RemainingSpace
    {
        get
        {                
            return System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication().AvailableFreeSpace;
        }
    }

    public TimeSpan RecordingDuration
    {
        get
        {
            return _currentMicrophone.GetSampleDuration((int)Size);
        }
    }

    public TimeSpan RemainingRecordingTime
    {
        get
        {
            return _currentMicrophone.GetSampleDuration((int)RemainingSpace);
        }
    }

    //-------

    public event EventHandler<RecordingStateChangedEventArgs> RecordingStateChanged;
    protected void OnRecordingStateChanged(RecordingState newState)
    {
        if(RecordingStateChanged!=null)
        {
            RecordingStateChanged(this, new RecordingStateChangedEventArgs(){NewState = newState});
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public void Dispose()
    {
        Stop();
    }
}

Ah, problem has been solved.enjoy with windows phone recording more.

 
Sign Up to vote for this article
 
About Author
 
j2inet
Occupation -Not Provided
Company-Not Provided
Member Type-Senior
Location-Not Provided
Joined date-12 Jun 2009
Home Page-Not Provided
Blog Page-Not Provided
 
 
Other popularSectionarticles
    In this article we will retrieve data from SharePoint list and display it Windows 7 phone application.
    Published Date : 03/Dec/2010
    In this article , i will show to how solve WCF Data Service Client library bug for Windows Phone 7.
    Published Date : 07/Jul/2010
    Introducing Windows Phone 7 Series
    Published Date : 11/Apr/2010
    You've got a class that contains data that you want to be able to save and load in IsolatedStorage on Windows Phone 7. How would you go about doing that? There is more than one way, but I wanted to share a generic solution for didactic purposes; I'm finding that many beginners like to start off with a single satisfying solution and brance off into specialized solutions later. Let's say that you are starting off with a stereotypical Employee class.
    Published Date : 05/Mar/2011
    In this article, i'm going to show to you,How to get System Information on Windows Phone 7
    Published Date : 06/Nov/2010
Comments
There is no comments for this articles.
Leave a Reply
Title:
Display Name:
Email:
(not display in page for the security purphase)
Website:
Message:
Please refresh your screen using Ctrl+F5
If you can't read this number refresh your screen
Please input the anti-spam code that you can read in the image.
^ Scroll to Top