Tuesday, April 1, 2014

Part 20: Recording an Audio Wav File [AbsoluteBeginnersSeriesForWindowsPhone8_files]


In this lesson we'll write the code required to record the custom sound. We'll employ the
Coding4Fun Toolkit to help make this easier, but we'll still need to understand things like
MemoryStreams and the phone's IsolatedStorage.

Here's our game plan in this lesson:

1.  We'l  modify the ToggleButton wiring up event handler methods to the Checked and Unchecked events. 
2.  We'l  use the MicrophoneRecord class in the Coding4Fun Toolkit's Audio namespace to begin and stop the recording process. 
3.  When we stop recording, we'l  need to temporarily save the sound stored in the phone's memory to a location on disk so it can be played back or saved permanently. 
4.  We'l  add a MediaElement control so we can enable playback of the sound. 
5.  Manage the state of the Play sound button, turning it off and on depending on the recording action of the user.

Let me just say that this is perhaps one of the most challenging lessons in this entire
series because it deals with some slightly more advanced material. You should
embrace this ... you only learn when you struggle, and challenging yourself with difficult
concepts will help you grow faster. Be sure to not only watch this video, but also read
the MSDN articles that I reference for more information. So put on your thinking cap and
let's get started.

1. Modify the ToggleButton Control wiring up Event Handler Methods
Edit the XAML code on the RecordAudio.xaml page for the ToggleButton as follows:


In lines 39 and 40 we wire up method handlers for the two states of the ToggleButton.

2. Create a private instance of the Coding4Fun.Toolkit.Audio.MicrophoneRecord class
In the RecordAudio.xaml.cs file, add the following line of code:

We create a new private instance of the MicrophoneRecorder class, and use the hover-
over-the-blue-dash technique to add a using statement for the
Coding4Fun.Toolkit.Audio namespace.

Now, we can start and stop the MicrophoneRecorder by adding code to the
ToggleButton's Checked and Unchecked event handler methods:


3. Saving the sound data collected by the MicrophoneRecorder into a file
As we're recording, the MicrophoneRecorder object is collecting the sound information
in a buffer. A buffer is just a pocket of memory devoted to storing data. Buffers are
typically used when there is a difference between the rate at which data is received and
the rate at which it can be processed, or in the case that these rates are variable. So, it
may take us 10 seconds to record a 10 second sound and during that time data is being added to the buffer. That said, the computer could process that data in a fraction of a second. The buffer is just a queue ... we can write the data at one rate of speed while reading it at another rate of speed. In programming, buffers are usually used between physical hardware and software, or when moving data from memory to disk and back, or data from memory to a network connection and back. I'm not a computer science guy, so I just think of a buffer as a bucket that you slowly collect things in until you're ready to work with the entire bucket at one time. So, I'm collecting shells on the beach and placing them into my bucket. Once I get a full bucket, I start processing them,
deciding which to keep and which to throw away. I collect over a long period of time,
then once I've filled my bucket, I process very quickly. That's how I think of a buffer.

When we call the Stop() method, the MicrophoneRecord stops adding sound
information, but is holding that data in memory, in a buffer and NOW we need to
process the data in the buffer. In this case, when I use the term "process" what I mean
is that I want to grab the sound data out of the buffer and place it as a WAV file into a
temporary file.

Once it's in a file sitting on my Phone's storage, I'll be able to hand that file over to a
MediaElement and direct it to play that temporary WAV file. If the user wants to keep
the file, I can re-name it and store it permanently.

So, let's talk about storing files on a Windows Phone. The storage space on the phone
is partitioned into isolated areas. Each app installed on the phone gets one of these
isolated areas. I use the term "isolated" because one app can't look at the storage area
of another app. This is for security ... one app can't rummage through your photos or
notes or other secret data and upload it to a secret malicious server or corrupt it in some evil way.

This isolated permanent storage area that's dedicated to your app is called
IsolatedStorage. It's just a tidy way of keeping each app's data safe since each app is
only able to write and read from its own storage area.

So, back to the problem at hand ... the MicrophoneRecorder object has a bunch on data
sitting in a buffer in memory, a MemoryStream object. My job is to save that data from
the MemoryStream to a filea temporary filein IsolatedStorage. To accomplish this,
I'll create a helper method that will take a MemoryStream as an input parameter and
then in the body of the helper method, I'll create a new file in IsolatedStorage and then
dump all the MemoryStream buffer data into that file.

That's the plan, let's build it:


As the screenshot indicates, the input parameter is of type MemoryStream and will need
a using statement to reference System.IO.

Next, we'll employ a different type of using statement:


In this context, the using statement will create a context for an instance of
System.IO.IsolatedStorage.IsolatedStorageFile. Any code inside of the code block
defined by the using statement will have access to the isoStore variable. Once the flow
of execution leaves the closing curly brace, the isoStore variable will be disposed from
memory properly.

You use this using syntax when you want to work with classes that implement
IDisposable ... typically these are managed types in the .NET Base Class Library (or in
our case, the Windows Phone API) that access UNMANAGED resources. So the
primary use of the IDisposable interface is to release unmanaged resources. The
garbage collector automatically releases the memory allocated to a MANAGED object
when that object is no longer used. That said, it is not possible to predict when garbage
collection will occur. Furthermore, the garbage collector has no knowledge of
UNMANAGED resources such as window handles, or open files and streams. The
danger is that two or more processes (apps) attempt to access the same resources at
the same time and cause an unrecoverable error condition. Types implementing
IDisposable handle these scenarios correctly. For a more complete explanation of these topics:

using Statement (C# Reference)

IDisposable Interface

The IsolatedStorageFile class provides methods that help you manage the files and
folder for use by your app. We use the GetUserStoreForApplication() to retrieve the
IsolatedStorage area for just our app.

Next, we'll grab the buffer and attempt to create a file in our IsolatedStorage area:


In line 69, I add a line of code that will retrieve values from the buffer (passed in to the
helper method) and convert it to the format of a wav (audio) file. As you can see, the
MemoryBuffer doesn't implement this method. Instead, we want to use an extension
method from the Coding4Fun.Toolkit.Audio.Helpers namespace. So, in order to apply
this extension method, we'll add another using statement at the top of our code file:


An extension method in .NET allows you to attach a method to any type. So, I could add
some utilities to the int or string or, in this case, the System.IO.MemoryStream. The
extension method allows you to work with the members of that type just like any public
method could. For more information on extension methods, and how to create your
own, check out:


Note: this is an advanced topic ... as long as you understand what they do and what
purpose they serve, that's enough for now. Later you can learn how to create your own.

Ok, so now we have the buffer in wav (audio) format, we just need to actually put it into
a new file on the device's IsolatedStorage area:


(1) We create a temporary name for the wav file. We may discard this in the future
(should the user hit the Record button again).
(2) The CreateFile() method creates a file on the file system using the name we pass in
(line 73, above), and gives us back a reference to that we can use to write to.
(3) Now that we have an empty file to write to, we'll write the data stored in the bytes
variable containing our sound. The Write() method's second and third parameters allows
us to specify a portion of the sound data we want to write ... in this case, we'll write the
entire file from the beginning (0) to the end (bytes.Length).

4. Add a MediaElement to play the new temporary file
In the RecordAudio.xaml file, beneath the ContentPanel grid control, we'll add a
MediaElement control, give it a name, and set AutoPlay to false:


Back in the RecordAudio.xaml.cs, we'll programmatically set the source of our new
MediaElement to our new temporarily sound file:


Now we're ready to play the sound file ... we just need to wire-up the event handler for
the Play button.

5. Handle the Play button's Click Event to Test the Sound
In the RecordAudio.xaml file, in the definition for the Button element with the content
"Play", we'll add a Click event handler called "PlayAudioClick":


Navigate to the new event handler and add the following line of code to call the Play()
method of the MediaElement control:


At this point we have it all wired up, but it's not pretty ... it's pretty fragile ... so we'll need
to comb back through this and program defensively. We'll do that later in this lesson.

For now, let's test our app. We've written a lot of code and, due to the nature of the
functionality we've created, it wasn't possible to test it in small parts. We had to
implement it all then test it to see where the problems pop up.

6. Declaring a Microphone capability for the app
Run the app, click the microphone icon in the application bar, then click the Record
ToggleButton:


When you click Record one of two things probably happened ... either the app
disappeared silently (no error message), or you'll get an exception, depending on which
version of the Coding4Fun Toolkit you're using. The reason this doesn't work is because we do not have permission to use the phone device's microphone. To request
permission to use the microphone, we need to declare a capability in our
WMAppManifest.xml file.

In Windows Phone, you have to request permissions to use certain device capabilities.
One purpose of this is to notify the user just how we intend to use their device. They
may grow suspicious if our app wants to use their geolocation or their camera when
that's not the purpose of our app.

The way to gain access to the capabilities of the phone's hardware is to open up the
WMAppManifest.xml file and go to the second tab, "Capabilities":


1.  Open up the WMAppManifest.xml file 
2.  Choose the second tab, Capabilities 
3.  Add a checkmark next to the ID_CAP_MICROPHONE capability

If we re-run the app and go through the same set of steps, it should work. Note: to
actually record sound in the Phone Emulator, you will need a working microphone
hooked up to the computer. Make sure the mic works prior to starting this so that you
can verify that it is not the source of any problems you might encounter:


I think the biggest issue at this point is that (a) the code is pretty fragile and easily
broken ... if we were to click the play button and immediately click the record button
again, we could probably break the app, and (b) we don't have any visual feedback to
let the user know what worked and what didn't, and (c) we are not actually saving the
sound permanently, nor are we allowing the user to select a name for the new sound.
We'll fix all of these in due time.

For now, let's focus on improving the quality of the code by adding defensive
programming statements.

7. Add defensive programming statements to guard against potential exceptions
First, in the SaveTempAudio helper method, we'll make sure that there's actually data in
our buffer before we attempt to create a file and fill it up with the buffer's contents:


Next, I want to guard against the possibility that the MediaElement is currently playing
when the user taps the ToggleButton to record a sound. To do that, I'll refactor my code
creating the IsolateStorageFileStream as a private member of the class:


Now, I'll add code to determine whether the _audioStream is empty. If it's not that
means I have data that needs to be saved, then I need to empty it out. While I'm there,
I'll make sure the MediaElement is no longer playing a sound, and its Source property is
set to null. This will release any hold the MediaElement may have on the _audioStream
from a previous try. It's possible that the MediaPlayer will keep a reference to the
_audioStream from a previous recording, and I want it to release that reference before I
try to create a new file using the same variable name _audioStream:




1.  Here I check to see whether _audioStream is nul . If it's not, then I need to make sure that we're not in the middle of using the _audioStream in playback mode. In lines 74 and 75 I release the hold that the MediaElement has on the _audioStream, then ... 
2.  I call dispose on the _audioStream. That should properly release any resources_audioStream is using. It's now ready to be re-populated with a newly created file handle. 
3.  Here I re-work the code ... I don't need to create a new instance of _audioStream ... it's been cleaned out and all resources have been released. So, I can merely set_audioStream to a new file reference.

Now I want to think about how I create and reference the temporary file name. I want to
make sure to not leave old temporary files around since they could clutter my apps
IsolatedStorage area much like extra files take up too much space on our hard drives.
The exception here is that your IsolatedStorage is much smaller than a hard drive, and
audio files can be large. So, first I'll move it's declaration to a private member variable
(line 24, below):


Next, I'll replace the:

var tempFileName = "tempWav.wav";

... with the following:


1.  If a temporary file already exists on the user's device from a previous recording attempt, we want to remove that file calling the .DeleteFile() method. 
2.  We want to give the file a unique name and so we use the DateTime.Now.ToFileTime() to give it a unique name down to the second. That should be sufficient for our purposes.

Next, I want to disable the Play button when recording. That simple state management
should make our lives as developers easier and reduce the possibility that the user is
able to corrupt something. I'll give the Button the name "PlayAudio":


Next, I'll perform some state management in the ToggleButton's event handlers:


1.  When I start recording, I'l  disable the PlayAudio button, and ... 
2.  When I stop recording, I'l  enable the PlayAudio button.
  
Recap
To recap, the big take aways from this lesson include utilizing the Coding4Fun Toolkit's
Audio features to greatly reduce the complexity of using the Phone's microphone to
record a sound. If you want to peek and see what it does, you can search for the full
name of the class in Bing to find a code listing on Codeplex:


We learned about the other using statement in C# to properly dispose of managed
classes that work with unmanaged resources. We learned about the function of buffers
as a means of collecting data at one rate that could be processed at a different rate. We used the buffer built into the Coding4Fun MicrophoneRecorder class and retrieved the buffer to save it into a file on the device's storage. We learned about solatedStorage and how it protects each application's storage area keeping it private from the other apps on the system. We briefly learned about extension methods as we used one that was implemented in the Coding4Fun Audio Helpers namespace and, finally, we practiced some defensive programming techniques to ensure that our app can handle unique situations or edge cases.

No comments:

Post a Comment