Permalink
Browse files

Implemented Capture.captureAudio function (including 'limit' option s…

…upport)
  • Loading branch information...
1 parent 82e82b0 commit 63017101f04c523b04cc4657ec0a2883d9f709ef sgrebnov committed Aug 29, 2011
View
1 example/www/index.html
@@ -157,6 +157,7 @@
<li onclick="exec(successCallback, failureCallback,'Capture','captureImage',{limit: 2});">Capture.captureImage</li>
<li onclick="exec(successCallback, failureCallback,'Camera','getPicture',{ sourceType: 1, destinationType: 1});">Camera.getPicture</li>
<li onclick="exec(successCallback, failureCallback,'Capture','getFormatData',{fullPath: 'CapturedImagesCache\\CameraCapture-f3b7730b-ff17-46b7-809a-59c179d08fd8.jpg'});">Capture.getMediaData</li>
+ <li onclick="exec(successCallback, failureCallback,'Capture','captureAudio',{limit: 1});">Capture.captureAudio</li>
</ul>
<div id="divProperties" style="color:Orange;">Placeholder for app messages</div>
View
15 framework/PGView.xaml.cs
@@ -41,16 +41,25 @@ public PGView()
//Type t = Type.GetType("WP7GapClassLib.PhoneGap.Commands.Camera");
}
+ /// <summary>
+ /// Indicates whether web control has been loaded and no additional initialization is needed.
+ /// Prevents data clearing during page transitions.
+ /// </summary>
+ private bool IsBrowserInitialized = false;
+
void GapBrowser_Loaded(object sender, RoutedEventArgs e)
{
if (DesignerProperties.IsInDesignTool)
{
return;
}
+
+ // prevents data clearing during page transitions.
+ if (this.IsBrowserInitialized) return;
+
+
try
{
- Thread.Sleep(1000);
-
StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri("GapSourceDictionary.xml", UriKind.Relative));
if (streamInfo != null)
@@ -117,6 +126,8 @@ select new
this.GapBrowser.NavigateToString(str);
+ this.IsBrowserInitialized = true;
+
/*
string htmlString = "<html><head><script>var myMsg='blah';function test(msg){alert(msg);}</script></head>";
View
148 framework/PhoneGap/Commands/Capture.cs
@@ -16,6 +16,10 @@
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone;
using System.Windows.Media.Imaging;
+using WP7GapClassLib.PhoneGap.UI;
+using System.Windows.Navigation;
+using Microsoft.Phone.Controls;
+using AudioResult = WP7GapClassLib.PhoneGap.UI.AudioCaptureTask.AudioResult;
namespace WP7GapClassLib.PhoneGap.Commands
{
@@ -45,6 +49,24 @@ public static CaptureImageOptions Default
}
/// <summary>
+ /// Represents captureAudio action options.
+ /// </summary>
+ [DataContract]
+ public class CaptureAudioOptions
+ {
+ /// <summary>
+ /// The maximum number of images the device user can capture in a single capture operation. The value must be greater than or equal to 1 (defaults to 1).
+ /// </summary>
+ [DataMember(IsRequired = false, Name = "limit")]
+ public int Limit { get; set; }
+
+ public static CaptureAudioOptions Default
+ {
+ get { return new CaptureAudioOptions() { Limit = 1 }; }
+ }
+ }
+
+ /// <summary>
/// Represents getFormatData action options.
/// </summary>
[DataContract]
@@ -89,7 +111,17 @@ public MediaFile(string filePath, Picture image)
this.FileName = System.IO.Path.GetFileName(this.FilePath);
this.Type = MimeTypeMapper.GetMimeType(FileName);
this.Size = image.GetImage().Length;
- this.LastModifiedDate = image.Date.ToString();
+ this.LastModifiedDate = IsolatedStorageFile.GetUserStoreForApplication().GetLastWriteTime(filePath).DateTime.ToString();
+
+ }
+
+ public MediaFile(string filePath, Stream stream)
+ {
+ this.FilePath = filePath;
+ this.FileName = System.IO.Path.GetFileName(this.FilePath);
+ this.Type = MimeTypeMapper.GetMimeType(FileName);
+ this.Size = stream.Length;
+ this.LastModifiedDate = IsolatedStorageFile.GetUserStoreForApplication().GetLastWriteTime(filePath).DateTime.ToString();
}
}
@@ -138,15 +170,25 @@ public MediaFileData(WriteableBitmap image)
protected CaptureImageOptions captureImageOptions;
/// <summary>
+ /// Capture Audio options
+ /// </summary>
+ protected CaptureAudioOptions captureAudioOptions;
+
+ /// <summary>
/// Used to open camera application
/// </summary>
private CameraCaptureTask cameraTask;
/// <summary>
- /// Stores informaton about captured files
+ /// Used for audio recording
+ /// </summary>
+ private AudioCaptureTask audioCaptureTask;
+
+ /// <summary>
+ /// Stores information about captured files
/// </summary>
List<MediaFile> files = new List<MediaFile>();
-
+
/// <summary>
/// Launches default camera application to capture image
/// </summary>
@@ -179,6 +221,37 @@ public void captureImage(string options)
}
/// <summary>
+ /// Launches our own audio recording control to capture audio
+ /// </summary>
+ /// <param name="options">may contains additional parameters</param>
+ public void captureAudio(string options)
+ {
+ try
+ {
+ try
+ {
+ this.captureAudioOptions = String.IsNullOrEmpty(options) ?
+ CaptureAudioOptions.Default : JSON.JsonHelper.Deserialize<CaptureAudioOptions>(options);
+
+ }
+ catch (Exception ex)
+ {
+ this.DispatchCommandResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION, ex.Message));
+ return;
+ }
+
+ audioCaptureTask = new AudioCaptureTask();
+ audioCaptureTask.Completed += audioRecordingTask_Completed;
+ audioCaptureTask.Show();
+
+ }
+ catch (Exception e)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, e.Message));
+ }
+ }
+
+ /// <summary>
/// Retrieves the format information of the media file.
/// </summary>
/// <param name="options"></param>
@@ -242,7 +315,7 @@ public void getFormatData(string options)
/// Handles result of capture to save image information
/// </summary>
/// <param name="sender"></param>
- /// <param name="e">stores inforamation about currrent captured image</param>
+ /// <param name="e">stores information about current captured image</param>
private void cameraTask_Completed(object sender, PhotoResult e)
{
@@ -320,6 +393,73 @@ private void cameraTask_Completed(object sender, PhotoResult e)
}
/// <summary>
+ /// Handles result of audio recording tasks
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e">stores information about current captured audio</param>
+ private void audioRecordingTask_Completed(object sender, AudioResult e)
+ {
+
+ if (e.Error != null)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
+ return;
+ }
+
+ switch (e.TaskResult)
+ {
+ case TaskResult.OK:
+ try
+ {
+ // Get image data
+ MediaFile data = new MediaFile(e.AudioFileName, e.AudioFile);
+
+ this.files.Add(data);
+
+ if (files.Count < this.captureAudioOptions.Limit)
+ {
+ audioCaptureTask.Show();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ }
+ catch (Exception ex)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Error capturing audio."));
+ }
+ break;
+
+ case TaskResult.Cancel:
+ if (files.Count > 0)
+ {
+ // User canceled operation, but some audio clips were made
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Canceled."));
+ }
+ break;
+
+ default:
+ if (files.Count > 0)
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.OK, files, "navigator.device.capture._castMediaFile"));
+ files.Clear();
+ }
+ else
+ {
+ DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Did not complete!"));
+ }
+ break;
+ }
+ }
+
+ /// <summary>
/// Extract file from Isolated Storage as WriteableBitmap object
/// </summary>
/// <param name="filePath"></param>
View
1 framework/PhoneGap/Commands/MimeTypeMapper.cs
@@ -37,6 +37,7 @@ public static class MimeTypeMapper
{"mpg", "video/mpeg"},
{"mpga", "audio/mpeg"},
{"pbm", "image/x-portable-bitmap"},
+ {"pcm", "audio/x-pcm"},
{"pct", "image/pict"},
{"pgm", "image/x-portable-graymap"},
{"pic", "image/pict"},
View
89 framework/PhoneGap/UI/AudioCaptureTask.cs
@@ -0,0 +1,89 @@
+using System;
+using System.IO;
+using System.Windows;
+using Microsoft.Phone.Controls;
+using Microsoft.Phone.Tasks;
+
+namespace WP7GapClassLib.PhoneGap.UI
+{
+ /// <summary>
+ /// Allows an application to launch the Audio Recording application.
+ /// Use this to allow users to record audio from your application.
+ /// </summary>
+ public class AudioCaptureTask
+ {
+ /// <summary>
+ /// Represents recorded audio returned from a call to the Show method of
+ /// a WP7GapClassLib.PhoneGap.Controls.AudioCaptureTask object
+ /// </summary>
+ public class AudioResult : TaskEventArgs
+ {
+ /// <summary>
+ /// Initializes a new instance of the AudioResult class.
+ /// </summary>
+ public AudioResult()
+ { }
+
+ /// <summary>
+ /// Initializes a new instance of the AudioResult class
+ /// with the specified Microsoft.Phone.Tasks.TaskResult.
+ /// </summary>
+ /// <param name="taskResult">Associated Microsoft.Phone.Tasks.TaskResult</param>
+ public AudioResult(TaskResult taskResult)
+ : base(taskResult)
+ { }
+
+ /// <summary>
+ /// Gets the file name of the recorded audio.
+ /// </summary>
+ public Stream AudioFile { get; internal set; }
+
+ /// <summary>
+ /// Gets the stream containing the data for the recorded audio.
+ /// </summary>
+ public string AudioFileName { get; internal set; }
+ }
+
+ /// <summary>
+ /// Occurs when a audio recording task is completed.
+ /// </summary>
+ public event EventHandler<AudioResult> Completed;
+
+ /// <summary>
+ /// Shows Audio Recording application
+ /// </summary>
+ public void Show()
+ {
+
+ var root = Application.Current.RootVisual as PhoneApplicationFrame;
+
+ root.Navigated += new System.Windows.Navigation.NavigatedEventHandler(NavigationService_Navigated);
+
+ // dummy parameter is used to always open a fresh version
+ root.Navigate(new System.Uri("/WP7GapClassLib;component/PhoneGap/Controls/AudioRecorder.xaml?dummy=" + Guid.NewGuid().ToString() , UriKind.Relative));
+ }
+
+ /// <summary>
+ /// Performs additional configuration of the recording application.
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ private void NavigationService_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
+ {
+ if (!(e.Content is AudioRecorder)) return;
+
+ (Application.Current.RootVisual as PhoneApplicationFrame).Navigated -= NavigationService_Navigated;
+
+ AudioRecorder audioRecorder = (AudioRecorder)e.Content;
+
+ if (audioRecorder != null)
+ {
+ audioRecorder.Completed += this.Completed;
+ }
+ else if (this.Completed != null)
+ {
+ this.Completed(this, new AudioResult(TaskResult.Cancel));
+ }
+ }
+ }
+}
View
48 framework/PhoneGap/UI/AudioRecorder.xaml
@@ -0,0 +1,48 @@
+<phone:PhoneApplicationPage
+ x:Class="WP7GapClassLib.PhoneGap.UI.AudioRecorder"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
+ xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ FontFamily="{StaticResource PhoneFontFamilyNormal}"
+ FontSize="{StaticResource PhoneFontSizeNormal}"
+ Foreground="{StaticResource PhoneForegroundBrush}"
+ SupportedOrientations="Portrait" Orientation="Portrait"
+ mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
+ shell:SystemTray.IsVisible="True">
+
+ <!--LayoutRoot is the root grid where all page content is placed-->
+ <Grid x:Name="LayoutRoot" Background="Transparent">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="Auto"/>
+ <RowDefinition Height="*"/>
+ </Grid.RowDefinitions>
+
+ <!--TitlePanel contains the name of the application and page title-->
+ <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
+ <TextBlock x:Name="PageTitle" Text="Audio recorder" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
+ </StackPanel>
+
+ <!--ContentPanel - place additional content here-->
+ <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
+ <Button Name="btnStartStop" Content="Start" Height="72" HorizontalAlignment="Left" Margin="126,96,0,0" VerticalAlignment="Top" Width="160" Click="btnStartStop_Click" />
+ <Button Name="btnTake" Content="Take" IsEnabled="False" Height="72" HorizontalAlignment="Left" Margin="125,182,0,0" VerticalAlignment="Top" Width="160" Click="btnTake_Click" />
+ <TextBlock Height="30" HorizontalAlignment="Left" Margin="138,60,0,0" Name="txtDuration" Text="Duration: 00:00" VerticalAlignment="Top" />
+ </Grid>
+ </Grid>
+
+ <!--Sample code showing usage of ApplicationBar-->
+ <!--<phone:PhoneApplicationPage.ApplicationBar>
+ <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
+ <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
+ <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
+ <shell:ApplicationBar.MenuItems>
+ <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
+ <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
+ </shell:ApplicationBar.MenuItems>
+ </shell:ApplicationBar>
+ </phone:PhoneApplicationPage.ApplicationBar>-->
+
+</phone:PhoneApplicationPage>
View
344 framework/PhoneGap/UI/AudioRecorder.xaml.cs
@@ -0,0 +1,344 @@
+using System;
+using System.IO;
+using System.IO.IsolatedStorage;
+using System.Windows;
+using System.Windows.Threading;
+using Microsoft.Phone.Controls;
+using Microsoft.Phone.Tasks;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using AudioResult = WP7GapClassLib.PhoneGap.UI.AudioCaptureTask.AudioResult;
+
+namespace WP7GapClassLib.PhoneGap.UI
+{
+ /// <summary>
+ /// Implements Audio Recording application
+ /// </summary>
+ public partial class AudioRecorder : PhoneApplicationPage
+ {
+
+ #region Constants
+
+ private const string RecordingStartCaption = "Start";
+ private const string RecordingStopCaption = "Stop";
+
+ private const string LocalFolderName = "AudioCache";
+ private const string FileNameFormat = "Audio-{0}.wav";
+
+ #endregion
+
+ #region Callbacks
+
+ /// <summary>
+ /// Occurs when a audio recording task is completed.
+ /// </summary>
+ public event EventHandler<AudioResult> Completed;
+
+ #endregion
+
+ #region Fields
+
+ /// <summary>
+ /// Audio source
+ /// </summary>
+ private Microphone microphone;
+
+ /// <summary>
+ /// Temporary buffer to store audio chunk
+ /// </summary>
+ private byte[] buffer;
+
+ /// <summary>
+ /// Recording duration
+ /// </summary>
+ private TimeSpan duration;
+
+ /// <summary>
+ /// Output buffer
+ /// </summary>
+ private MemoryStream memoryStream;
+
+ /// <summary>
+ /// Recording result, dispatched back when recording page is closed
+ /// </summary>
+ private AudioResult result = new AudioResult(TaskResult.Cancel);
+
+ /// <summary>
+ /// Whether we are recording audio now
+ /// </summary>
+ private bool IsRecording
+ {
+ get
+ {
+ return this.microphone.State == MicrophoneState.Started;
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Creates new instance of the AudioRecorder class.
+ /// </summary>
+ public AudioRecorder()
+ {
+
+ this.microphone = Microphone.Default;
+ this.microphone.BufferDuration = TimeSpan.FromMilliseconds(500);
+
+ this.buffer = new byte[microphone.GetSampleSizeInBytes(this.microphone.BufferDuration)];
+
+ this.microphone.BufferReady += new EventHandler<EventArgs>(MicrophoneBufferReady);
+
+ this.InitializeXnaGameLoop();
+
+ // microphone requires special XNA initialization to work
+ InitializeComponent();
+ }
+
+ /// <summary>
+ /// Starts recording, data is stored in memory
+ /// </summary>
+ private void StartRecording()
+ {
+ this.btnTake.IsEnabled = false;
+ this.btnStartStop.Content = RecordingStopCaption;
+
+ this.memoryStream = new MemoryStream();
+ this.WriteWavHeader(this.memoryStream, this.microphone.SampleRate);
+
+ this.duration = new TimeSpan(0);
+
+ this.microphone.Start();
+ }
+
+ /// <summary>
+ /// Stops recording
+ /// </summary>
+ private void StopRecording()
+ {
+ this.microphone.Stop();
+ this.UpdateWavHeader(this.memoryStream);
+
+ btnStartStop.Content = RecordingStartCaption;
+
+ // check there is some data
+ this.btnTake.IsEnabled = true;
+ }
+
+ /// <summary>
+ /// Handles Start/Stop events
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ private void btnStartStop_Click(object sender, RoutedEventArgs e)
+ {
+
+ if (this.IsRecording)
+ {
+ this.StopRecording();
+ }
+ else
+ {
+ this.StartRecording();
+ }
+ }
+
+ /// <summary>
+ /// Handles Take button click
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ private void btnTake_Click(object sender, RoutedEventArgs e)
+ {
+ this.result = this.SaveAudioClipToLocalStorage();
+
+ if (this.NavigationService.CanGoBack)
+ {
+ this.NavigationService.GoBack();
+ }
+ }
+
+ /// <summary>
+ /// Handles page closing event, stops recording if needed and dispatches results.
+ /// </summary>
+ /// <param name="e"></param>
+ protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
+ {
+ if (this.IsRecording)
+ {
+ StopRecording();
+ }
+
+ if (this.Completed != null)
+ {
+ this.Completed(this, result);
+ }
+
+ base.OnNavigatedFrom(e);
+
+ }
+
+ /// <summary>
+ /// Copies data from microphone to memory storages and updates recording state
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ private void MicrophoneBufferReady(object sender, EventArgs e)
+ {
+ this.microphone.GetData(this.buffer);
+ this.memoryStream.Write(this.buffer, 0, this.buffer.Length);
+
+ this.Dispatcher.BeginInvoke(() =>
+ {
+ this.duration += this.microphone.BufferDuration;
+
+ this.txtDuration.Text = "Duration: " +
+ this.duration.Minutes.ToString().PadLeft(2, '0') + ":" +
+ this.duration.Seconds.ToString().PadLeft(2, '0');
+ });
+
+ }
+
+ /// <summary>
+ /// Writes audio data from memory to isolated storage
+ /// </summary>
+ /// <returns></returns>
+ private AudioResult SaveAudioClipToLocalStorage()
+ {
+ if (this.memoryStream == null || this.memoryStream.Length <= 0)
+ {
+ return new AudioResult(TaskResult.Cancel);
+ }
+
+ // save audio data to local isolated storage
+
+ string filename = String.Format(FileNameFormat, Guid.NewGuid().ToString());
+
+ try
+ {
+ var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
+
+ if (!isoFile.DirectoryExists(LocalFolderName))
+ {
+ isoFile.CreateDirectory(LocalFolderName);
+ }
+
+ string filePath = System.IO.Path.Combine(LocalFolderName, filename);
+
+ this.memoryStream.Seek(0, SeekOrigin.Begin);
+
+ var fileStream = isoFile.CreateFile(filePath);
+
+ this.memoryStream.CopyTo(fileStream);
+
+ AudioResult result = new AudioResult(TaskResult.OK);
+ result.AudioFileName = filePath;
+ result.AudioFile = fileStream;
+
+ // set position to beginning when returning it
+ result.AudioFile.Seek(0, SeekOrigin.Begin);
+
+ return result;
+
+ }
+ catch (Exception e)
+ {
+ //TODO: log or do something else
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Special initialization required for the microphone: XNA game loop
+ /// </summary>
+ private void InitializeXnaGameLoop()
+ {
+ // Timer to simulate the XNA game loop (Microphone is from XNA)
+ DispatcherTimer dt = new DispatcherTimer();
+ dt.Interval = TimeSpan.FromMilliseconds(33);
+ dt.Tick += delegate { try { FrameworkDispatcher.Update(); } catch { } };
+ dt.Start();
+ }
+
+ #region Wav format
+ // Original source http://damianblog.com/2011/02/07/storing-wp7-recorded-audio-as-wav-format-streams/
+
+ /// <summary>
+ /// Adds wav file format header to the stream
+ /// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+ /// </summary>
+ /// <param name="stream">Wav stream</param>
+ /// <param name="sampleRate">Sample Rate</param>
+ private 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);
+ }
+
+ /// <summary>
+ /// Updates wav file format header
+ /// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+ /// </summary>
+ /// <param name="stream">Wav stream</param>
+ private 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);
+ }
+
+ #endregion
+
+
+ }
+}
View
8 framework/WP7GapClassLib.csproj
@@ -56,6 +56,10 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="PhoneGap\UI\AudioCaptureTask.cs" />
+ <Compile Include="PhoneGap\UI\AudioRecorder.xaml.cs">
+ <DependentUpon>AudioRecorder.xaml</DependentUpon>
+ </Compile>
<Compile Include="PhoneGap\JSON\JsonHelper.cs" />
<Compile Include="PhoneGap\Commands\MimeTypeMapper.cs" />
<Compile Include="PhoneGap\PhoneGapCommandCall.cs" />
@@ -92,6 +96,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
+ <Page Include="PhoneGap\UI\AudioRecorder.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
</ItemGroup>
<ItemGroup>
<Content Include="js\.disclaimer.js" />

0 comments on commit 6301710

Please sign in to comment.