在windows phone的开发中,有时候我们需要在程序中嵌入一段语音,至少这要比打字速度快上很多。之前在android和ios的市场上上已经发现了这种集成录音功能的应用,貌似那两个系统都提供了接口,我想在windows phone肯定也能做到这一点。遗憾的是,在国内网站上面搜索时找到的资料很少,当我按英文检索时立刻就发现了一篇很有用的资料,Making a Voice Recorder on Windows Phone 于是下面就是对这篇文章的粗略翻译(略去了我认为不重要的东西)。
在开始写应用之前,我想好了这个程序要实现哪些功能,列表如下:
public class XNAFrameworkDispatcherService : IApplicationService { private DispatcherTimer frameworkDispatcherTimer; public XNAFrameworkDispatcherService() { this.frameworkDispatcherTimer = new DispatcherTimer(); this.frameworkDispatcherTimer.Interval = TimeSpan.FromTicks(333333); this.frameworkDispatcherTimer.Tick += frameworkDispatcherTimer_Tick; FrameworkDispatcher.Update(); } void frameworkDispatcherTimer_Tick(object sender, EventArgs e) { FrameworkDispatcher.Update(); } void IApplicationService.StartService(ApplicationServiceContext context) { this.frameworkDispatcherTimer.Start(); } void IApplicationService.StopService() { this.frameworkDispatcherTimer.Stop(); } }
<Application.ApplicationLifetimeObjects> <!--Required object that handles lifetime events for the application--> <shell:PhoneApplicationService Launching="Application_Launching" Closing="Application_Closing" Activated="Application_Activated" Deactivated="Application_Deactivated"/> <local:XNAFrameworkDispatcherService /> </Application.ApplicationLifetimeObjects>
//code for recording from the microphone and saving to a file
public void StartRecording()
{
if (_currentMicrophone == null)
{
_currentMicrophone = Microphone.Default;
_currentMicrophone.BufferReady +=
new EventHandler<EventArgs>(_currentMicrophone_BufferReady);
_audioBuffer = new byte[_currentMicrophone.GetSampleSizeInBytes(
_currentMicrophone.BufferDuration)];
_sampleRate = _currentMicrophone.SampleRate;
}
_stopRequested = false;
_currentRecordingStream = new MemoryStream(1048576);
_currentMicrophone.Start();
}
public void RequestStopRecording()
{
_stopRequested = true;
}
void _currentMicrophone_BufferReady(object sender, EventArgs e)
{
_currentMicrophone.GetData(_audioBuffer);
_currentRecordingStream.Write(_audioBuffer,0,_audioBuffer.Length);
if (!_stopRequested)
return;
_currentMicrophone.Stop();
var isoStore =
System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication();
using (var targetFile = isoStore.CreateFile(FileName))
{
WaveHeaderWriter.WriteHeader(targetFile,
(int)_currentRecordingStream.Length, 1, _sampleRate);
var dataBuffer = _currentRecordingStream.GetBuffer();
targetFile.Write(dataBuffer,0,(int)_currentRecordingStream.Length);
targetFile.Flush();
targetFile.Close();
}
}
public void PlayRecording(RecordingDetails source) { if(_currentSound!=null) { _currentSound.Stop(); _currentSound = null; } var isoStore = System.IO.IsolatedStorage.IsolatedStorageFile. GetUserStoreForApplication(); if(isoStore.FileExists(source.FilePath)) { byte[] fileContents; using (var fileStream = isoStore.OpenFile(source.FilePath, FileMode.Open)) { fileContents = new byte[(int) fileStream.Length]; fileStream.Read(fileContents, 0, fileContents.Length); fileStream.Close();//not really needed, but it makes me feel better. } int sampleRate =((fileContents[24] << 0) | (fileContents[25] << 8) | (fileContents[26] << 16) | (fileContents[27] << 24)); AudioChannels channels = (fileContents[22] == 1) ? AudioChannels.Mono : AudioChannels.Stereo; var se = new SoundEffect(fileContents, 44, fileContents.Length - 44, sampleRate, channels, 0, 0); _currentSound = se.CreateInstance(); _currentSound.Play(); } }
public void PlayRecording(RecordingDetails source) { SoundEffect se; if(_currentSound!=null) { _currentSound.Stop(); _currentSound = null; } var isoStore = System.IO.IsolatedStorage. IsolatedStorageFile.GetUserStoreForApplication(); if(isoStore.FileExists(source.FilePath)) { byte[] fileContents; using (var fileStream = isoStore.OpenFile(source.FilePath, FileMode.Open)) { se = SoundEffect.FromStream(fileStream); fileStream.Close();//not really needed, but it makes me feel better. } _currentSound = se.CreateInstance(); _currentSound.Play(); } }
public class RecordingDetails { public string Title { get; set; } public string Details { get; set; } public DateTime TimeStamp { get; set; } public string FilePath { get; set; } public string SourcePath { get; set; } }
[DataContract] public class RecordingDetails: INotifyPropertyChanged { // Title - generated from ObservableField snippet - Joel Ivory Johnson private string _title; [DataMember] public string Title { get { return _title; } set { if (_title != value) { _title = value; OnPropertyChanged("Title"); } } } //----- // Details - generated from ObservableField snippet - Joel Ivory Johnson private string _details; [DataMember] public string Details { get { return _details; } set { if (_details != value) { _details = value; OnPropertyChanged("Details"); } } } //----- // FilePath - generated from ObservableField snippet - Joel Ivory Johnson private string _filePath; [DataMember] public string FilePath { get { return _filePath; } set { if (_filePath != value) { _filePath = value; OnPropertyChanged("FilePath"); } } } //----- // TimeStamp - generated from ObservableField snippet - Joel Ivory Johnson private DateTime _timeStamp; [DataMember] public DateTime TimeStamp { get { return _timeStamp; } set { if (_timeStamp != value) { _timeStamp = value; OnPropertyChanged("TimeStamp"); } } } //----- // SourceFileName - generated from ObservableField snippet - Joel Ivory Johnson private string _sourceFileName; [IgnoreDataMember] public string SourceFileName { get { return _sourceFileName; } set { if (_sourceFileName != value) { _sourceFileName = value; OnPropertyChanged("SourceFileName"); } } } //----- // IsNew - generated from ObservableField snippet - Joel Ivory Johnson private bool _isNew = false; [IgnoreDataMember] public bool IsNew { get { return _isNew; } set { if (_isNew != value) { _isNew = value; OnPropertyChanged("IsNew"); } } } //----- // IsDirty - generated from ObservableField snippet - Joel Ivory Johnson private bool _isDirty = false; [IgnoreDataMember] public bool IsDirty { get { return _isDirty; } set { if (_isDirty != value) { _isDirty = value; OnPropertyChanged("IsDirty"); } } } //----- public void Copy(RecordingDetails source) { this.Details = source.Details; this.FilePath = source.FilePath; this.SourceFileName = source.SourceFileName; this.TimeStamp = source.TimeStamp; this.Title = source.Title; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
//Saving Data var myDataSaver = new DataSaver<RecordingDetails>() {}; myDataSaver.SaveMyData(LastSelectedRecording, LastSelectedRecording.SourceFileName); //Loading Data var myDataSaver = new DataSaver<RecordingDetails>(); var item = myDataSaver.LoadMyData(LastSelectedRecording.SourceFileName);
这样操作后,你就拥有了进行录音,保存录音,载入录音的全部信息,当程序第一次启动时,我让其载入所有的RecordingDetails并且把他们加载到我的视图模式中的一个ObservableCollection中。在那里它们可以以一种列表的形式展现给用户。
public void LoadData() { var isoStore = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication(); var recordingList = isoStore.GetFileNames("data/*.xml"); var myDataSaver = new DataSaver<RecordingDetails>(); Items.Clear(); foreach (var desc in recordingList.Select(item => { var result =myDataSaver.LoadMyData(String.Format("data/{0}", item)); result.SourceFileName = String.Format("data/{0}", item); return result; })) { Items.Add(desc); } this.IsDataLoaded = true; }
你的程序可以在任何时间被中断,例如一个电话,一个突然跳出程序进行的搜索等等。当这一切发生时,你的应用会被墓碑化;操作系统将会保存用户所在的页面并且给程序保存其他数据的机会。当程序被再次载入时,开发者必须确保采取适当步骤重新加载状态。大多数情况下,我并不需要担心墓碑化因为程序大部分状态数据被迅速保存到隔离存储中。这里也没有多少需要保存的状态数据,因为录音随着程序设置的改变会被立即执行。
下面内容不再翻译,无非是作者提出自己想增加应用功能的说明。最后给出源码下载地址和应用截图:
下载地址:http://www.codeproject.com/KB/windows-phone-7/WpVoiceMemo/WpVoiceMemo.zip