Damian Mehers' Blog Xamarin from Geneva, Switzerland.

7Feb/1116

Storing WP7 recorded audio as WAV format streams

The Microsoft audio recording example shows how to record audio, but it just gets a PCM encoded stream of samples – if you want to send this audio stream off somewhere else, to be played later, it needs a proper header.

Below I show you how to use a couple of methods I’ve written (WriteWavHeader and UpdateWavHeader), and I include their implementation.

Assuming you start with the Microsoft sample, when you initiate the audio recording you initialize a memory stream into which the samples will be written:

private void StartRecording()
{
    _microphone.BufferDuration = TimeSpan.FromMilliseconds(500);

    // Allocate memory to hold the audio data
    _recordingBuffer = new byte[_microphone.GetSampleSizeInBytes(_microphone.BufferDuration)];

    _recordingStream.SetLength(0);

    WriteWavHeader(_recordingStream, _microphone.SampleRate);

    _microphone.Start();
}

Note the call to WriteWavHeader to write the wav header to the start of the audio stream.

When the recording finishes, you update the header (because we need to fill in fields based on the data length):

private void StopRecording()
{
    _microphone.Stop();
    UpdateWavHeader(_recordingStream);
}

Here are the implementations of the WriteWavHeader and UpdateWavHeader methods.  The comments come from this web page describing the wav header file format.

public void WriteWavHeader(Stream stream, int sampleRate)
{
    const int bitsPerSample = 16;
    const int bytesPerSample = bitsPerSample / 8;
    var encoding = System.Text.Encoding.UTF8;

    // ChunkID Contains the letters "RIFF" in ASCII form (0x52494646 big-endian form).
    stream.Write(encoding.GetBytes("RIFF"), 0, 4);

    // NOTE this will be filled in later
    stream.Write(BitConverter.GetBytes(0), 0, 4);

    // Format Contains the letters "WAVE"(0x57415645 big-endian form).
    stream.Write(encoding.GetBytes("WAVE"), 0, 4);

    // Subchunk1ID Contains the letters "fmt " (0x666d7420 big-endian form).
    stream.Write(encoding.GetBytes("fmt "), 0, 4);

    // Subchunk1Size 16 for PCM.  This is the size of therest of the Subchunk which follows this number.
    stream.Write(BitConverter.GetBytes(16), 0, 4);

    // AudioFormat PCM = 1 (i.e. Linear quantization) Values other than 1 indicate some form of compression.
    stream.Write(BitConverter.GetBytes((short)1), 0, 2);

    // NumChannels Mono = 1, Stereo = 2, etc.
    stream.Write(BitConverter.GetBytes((short)1), 0, 2);

    // SampleRate 8000, 44100, etc.
    stream.Write(BitConverter.GetBytes(sampleRate), 0, 4);

    // ByteRate =  SampleRate * NumChannels * BitsPerSample/8
    stream.Write(BitConverter.GetBytes(sampleRate * bytesPerSample), 0, 4);

    // BlockAlign NumChannels * BitsPerSample/8 The number of bytes for one sample including all channels.
    stream.Write(BitConverter.GetBytes((short)(bytesPerSample)), 0, 2);

    // BitsPerSample    8 bits = 8, 16 bits = 16, etc.
    stream.Write(BitConverter.GetBytes((short)(bitsPerSample)), 0, 2);

    // Subchunk2ID Contains the letters "data" (0x64617461 big-endian form).
    stream.Write(encoding.GetBytes("data"), 0, 4);
    
    // NOTE to be filled in later
    stream.Write(BitConverter.GetBytes(0), 0, 4);
}

public void UpdateWavHeader(Stream stream)
{
    if (!stream.CanSeek) throw new Exception("Can't seek stream to update wav header");

    var oldPos = stream.Position;

    // ChunkSize  36 + SubChunk2Size
    stream.Seek(4, SeekOrigin.Begin);
    stream.Write(BitConverter.GetBytes((int)stream.Length - 8), 0, 4);

    // Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8 This is the number of bytes in the data.
    stream.Seek(40, SeekOrigin.Begin);
    stream.Write(BitConverter.GetBytes((int)stream.Length - 44), 0, 4);

    stream.Seek(oldPos, SeekOrigin.Begin);
}

Filed under: WP7 16 Comments
21Jan/1114

Windows Phone 7 Watermark on PasswordBox

For my textboxes I’ve been using a modified version Johan Danforth’s Watermark behaviour (binding the watermark text to a ViewModel property instead of hardcoding the text), but when I needed to do something similar for a PasswordBox I thought I was stuck.

I’ve come up with an incredibly basic, simple solution that seems to work well. Instead of just having a PasswordBox, I also have a TextBox behind it which I make invisible when the PasswordBox has content, or has focus.  Because the watermark TextBox is behind the PasswordBox, it never gets focus: it is purely visual.

Code Snippet
<Grid>
    <TextBox x:Name="PasswordWatermark" TextWrapping="Wrap" Text="{Binding Labels.password, Mode=OneTime}" Foreground="{StaticResource PhoneTextBoxReadOnlyBrush}" IsHitTestVisible="False"/>
    <PasswordBox x:Name="Password" LostFocus="PasswordLostFocus" Opacity="0" GotFocus="PasswordGotFocus" Password="{Binding Password, Mode=TwoWay}"/>
</Grid>

In the code-behind I hide/show the watermark TextBox and the PasswordBox as appropriate.  Note that I show and hide them by changing their opacity, not by setting their visibility. 

Because the PasswordBox is in front of the the watermark Textbox, when the user clicks in that area he always clicks on the PasswordBox, even if it is invisible.

Code Snippet
  1. private void PasswordLostFocus(object sender, RoutedEventArgs e)
  2. {
  3.     CheckPasswordWatermark();
  4. }
  5.  
  6. public void CheckPasswordWatermark()
  7. {
  8.     var passwordEmpty = string.IsNullOrEmpty(Password.Password);
  9.     PasswordWatermark.Opacity = passwordEmpty ? 100 : 0;
  10.     Password.Opacity = passwordEmpty ? 0 : 100;
  11. }
  12.  
  13. private void PasswordGotFocus(object sender, RoutedEventArgs e)
  14. {
  15.     PasswordWatermark.Opacity = 0;
  16.     Password.Opacity = 100;
  17. }

When populating the PasswordBox, you can call CheckPasswordWatermark() to show/hide the watermark as appropriate.

Filed under: WP7 14 Comments
21Jan/111

Restoring Pivot Scroll on WP7 Tombstone resume

Putting the user back to the correct pivot item when they return to a tombestoned application whose last visited page contained a Pivot is pretty easy, but ensuring that they are put back to the right scroll position if they’ve scrolled down a list isn’t quite as easy.

In the code below I have a method which returns the first ScrollViewer under an item.  This works well for me, but if your PivotItem’s contents contain ScrollViewers then you’ll need something more sophistacated.

In the OnNavigatedFrom I follow the standard pattern Microsoft recommends, and store away the scroll offset in the page state.

In the OnNavigatedTo I recuperate the scroll position, but cannot yet scroll because the PivotItem’s contents have not been loaded.  Instead I store the scroll position, and then when the pivot has been loaded I then scroll.  In my XAML I hook up each PivotItem’s Loaded event to invoke PivotItemLoaded

Code Snippet
  1. static ScrollViewer FindScrollViewer(DependencyObject parent)
  2. {
  3.     var childCount = VisualTreeHelper.GetChildrenCount(parent);
  4.     for (var i = 0; i < childCount; i++)
  5.     {
  6.         var elt = VisualTreeHelper.GetChild(parent, i);
  7.         if (elt is ScrollViewer) return (ScrollViewer)elt;
  8.         var result = FindScrollViewer(elt);
  9.         if (result != null) return result;
  10.     }
  11.     return null;
  12. }
  13.  
  14. protected override void
  15.     OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
  16. {
  17.     base.OnNavigatedFrom(e);
  18.  
  19.     State["MainPivot.SelectedIndex"] = MainPivot.SelectedIndex;
  20.     var pivot = (PivotItem)MainPivot.SelectedItem;
  21.     var scrollViewer = FindScrollViewer(pivot);
  22.     State["scrollViewer.VerticalOffset"] = scrollViewer.VerticalOffset;
  23.     _newPageInstance = false;
  24.     State["PreservingPageState"] = true;
  25. }
  26.  
  27. // Communicates scroll position between OnNavigatedTo and PivotLoaded
  28. private double _pivotScroll;
  29.  
  30. protected override void
  31.     OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  32. {
  33.     base.OnNavigatedTo(e);
  34.  
  35.     if (!_newPageInstance || !State.ContainsKey("PreservingPageState"))
  36.     {
  37.         return;
  38.     }
  39.  
  40.     MainPivot.SelectedIndex = (int)State["MainPivot.SelectedIndex"];
  41.  
  42.     // Can't scroll now because Pivot's contents won't have been loaded
  43.     _pivotScroll = (double)State["scrollViewer.VerticalOffset"];
  44. }
  45.  
  46. // Hooked up to each pivot item's Loaded event handler.
  47. private void PivotLoaded(object sender, RoutedEventArgs e)
  48. {
  49.     if (_pivotScroll == 0 || sender != MainPivot.SelectedItem) return;
  50.  
  51.     var pivot = (PivotItem)sender;
  52.     var scrollViewer = FindScrollViewer(pivot);
  53.     scrollViewer.ScrollToVerticalOffset(_pivotScroll);
  54.     _pivotScroll = 0;
  55. }

Filed under: WP7 1 Comment