http://blog.youkuaiyun.com/jj12345jj198999/article/details/8512401
windows phone开发学习--录音
在windows phone的开发中,有时候我们需要在程序中嵌入一段语音,至少这要比打字速度快上很多。之前在android和ios的市场上上已经发现了这种集成录音功能的应用,貌似那两个系统都提供了接口,我想在windows phone肯定也能做到这一点。遗憾的是,在国内网站上面搜索时找到的资料很少,当我按英文检索时立刻就发现了一篇很有用的资料,Making a Voice Recorder on Windows Phone 于是下面就是对这篇文章的粗略翻译(略去了我认为不重要的东西)。
确定特征功能
在开始写应用之前,我想好了这个程序要实现哪些功能,列表如下:
- 导出录音
- 用WAV格式保存录音
- 为录音添加备注
- 加快或放慢录音速度
- 改变语音
- 混合,分割,编辑录音
- 以MP3格式导出
- 分类标记
- 时间/日期提醒
- 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(); }
- }
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(); }
}
一旦这个类在你的工程中被声明,它需要被作为应用生命期的对象添加进来。这里有很多方法实现这一点,但是我喜欢把他添加到App.xaml之中。
- <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>
<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>
完成了这一步,我不再需要再考虑FrameworkDispatcher.Update.它会在程序启动时自动执行并在程序关闭时自动终止。
使用Microphone类录音




- <span style="font-family: Arial, Helvetica, sans-serif;">//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();
- }
- }</span>
//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)
{
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 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; }
- }
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));
- }
- }
- }
[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));
}
}
}
[DataMember]属性贯穿了整个代码,所以我可以使用数据合同序列化读写该类。由于我使用了DataContractSerializer,我并不需要过于操心当文件被存储和载入时具体的编码细节。同时使用隔离存储不是很难,我使用了之前我自己写的一个实用类的变种( 具体看这里)来把序列化和反序列化的简化为一小段代码。当用户创建一个新的录音时,该类的一个新的实例也会被创建。除了标题,备注和时间戳外,这个类也包含描述该录音的路径,一个指出数据从哪加载的原始文件名称的非序列化成员SourceFileName。如果没有这些信息,当用户决定更新数据时,便无法知道当保存内容时哪个文件需要被重写。
- //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);
//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;
- }
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