在以Silverlight为主的Windows Phone 7应用程序中播放音效,其实只有几种方法,若要播放音效档,比较常见的会用XNA Framework里的SoundEffect或SoundEffectInstance类别来播 放,在者就是用MediaElement来播放较长的音乐或影片,本篇文章的主轴会以播放音效为主。使用不同的方法来播放音效有许多不同的注意事项,一不小心应用程序就会发生非预期例外,在此整理与分享一些使用上的心得笔记。
由于这两个类别被编译在Microsoft.Xna.Framework.dll组件中,因此使用前必须先从专案加入组件参考:
使用时要记得引用Microsoft.Xna.Framework与Microsoft.Xna.Framework.Audio命名空间:
由于开发以Silverlight为基础的应用程序中使用SoundEffect或SoundEffectInstance类别来播放音效仅支援WAV格式 ( *.wav )的声音档,所以如果你想播放MP3格式的音效档的话,必须先透过其他转档工具先转换成WAV 格式才行,而且WAV 档的格式还有以下 限制:
必须是PCM wave声音档,而且必须为RIFF bitstream格式
只能是mono (单音) 或stereo (立体音) 格式
必须为8-bits 或16-bits 的声音档
取样频率(Sample rate) 必须介于8,000 Hz 到48,000 Hz 之间
备注 :若要播放非WAV的声音档或音乐(例如MP3格式的音乐),可改用 MediaElement 来播放。
接着,确定一下载入到专案里的声音档的Build Action设定为Content
接着就能开始写Code 了!在此,我们用简单的代码来介绍其使用方法:
1.播放短时间的音效,适合使用SoundEffect类别来播放
- Stream stream = TitleContainer.OpenStream("Resources/NightAmbienceSimple_01.wav");
- SoundEffect effect = SoundEffect.FromStream(stream);
- FrameworkDispatcher.Update();
- effect.Play();
如上4行程序码应该很容易理解,第1 行载入声音档,第2 行建立SoundEffect 物件,第3 行最重要容后再述,第4 行播放声音。
其中最重要的是第3行的FrameworkDispatcher . Update();语法,在使用SoundEffect播放声音前,一定要先执行这一段语法,否则就会引发例外!
然而,使用SoundEffect类别来播放有个最明显的限制 ,那就是开始播放音效后就无法透过程序来设定停止播放 ,因此直接使用SoundEffect来播放声音通常会用来播放比较短的音效(例如1 ~ 3秒)。
2.播放连续的背景音效或较长时间的音效,适合使用SoundEffectInstance类别来播放
- Stream stream = TitleContainer.OpenStream("Resources/NightAmbienceSimple_01.wav");
- SoundEffect effect = SoundEffect.FromStream(stream);
- SoundEffectInstance instance = effect.CreateInstance();
- instance.Play();
使用SoundEffectInstance来播放音效前,基本上还是要先取得SoundEffect物件,然后再透过SoundEffect的 CreateInstance来取得一个新的SoundEffectInstance实体, 不需要先执 行 FrameworkDispatcher . Update(); 就可以播放声音 ,而且也能透过其他方法来暂停、继续或停止播放音效 ,这是与直 接使用SoundEffect物件来播放声音时最大的差异。
另一个与SoundEffect播放音效的差异在于,使用SoundEffectInstance可以设定「重复播放音效」功能,这样的功能可以运用在需要 提供背景环境音效时使用。 启用的方法也很简单,只要在SoundEffectInstance物件上设定IsLooped属性为true即可。
§特别注意§
无论使用SoundEffect或SoundEffectInstance来播放音效,这两种播放音效的方式有3个非常重要的注意事项,分别说明如下:
1. 这两个类别来自于XNA Framework,因此只要有用到SoundEffect或SoundEffectInstance的页面,都要在建构子函式 中加上以下程序码,并且不断呼叫FrameworkDispatcher . Update();语法,这样才能让SoundEffect或 SoundEffectInstance在播放音乐的过程中正确无误的执行,否则很有可能会遇到音效播放到一半就自动停止的情况! ( 更严重者,可能会 导致应用程序直接发生非预期的例外 )
- // Timer to simulate the XNA game loop
- // (SoundEffect classes are from the XNA Framework)
- DispatcherTimer XnaDispatchTimer = new DispatcherTimer();
- XnaDispatchTimer.Interval = TimeSpan.FromMilliseconds(50);
- // Call FrameworkDispatcher.Update to update the XNA Framework internals.
- XnaDispatchTimer.Tick +=
- delegate { try { FrameworkDispatcher.Update(); } catch { } };
- // Start the DispatchTimer running.
- XnaDispatchTimer.Start();
2. 这两个SoundEffect与SoundEffectInstance类别在播放音乐时,其执行的方式完全是属于射后不理 (Fire-and- Forget)型的,所以当启动声音播放后会自动在背景运作(透过非同步执行的方式),因此当你希望循序播放两个声音档时,必须小心设计,绝不能连续写在 一起,这样会导致两个音效档同时播放。 你可以透过DispatcherTimer以非同步的方式来判断前一个声音档是否已播放完毕来做到循序播放的目 的,以下是一个简易的程序码范例供参考: ( 注: SoundEffect 与 SoundEffectInstance 都没有事件可用 )
- bool isSound1_Played = false;
- bool isSound2_Played = false;
- void dt_Tick(object sender, EventArgs e)
- {
- if (false == isSound1_Played)
- {
- SoundEffect SoundSmall = SoundEffect.FromStream(TitleContainer.OpenStream("Audio/a1.wav"));
- soundInstance = SoundSmall.CreateInstance();
- soundInstance.IsLooped = false;
- soundInstance.Play();
- isSound1_Played = true;
- }
- else if (false == isSound2_Played && soundInstance.State == SoundState.Stopped)
- {
- SoundEffect sound = SoundEffect.FromStream(TitleContainer.OpenStream("Audio/a2.wav"));
- soundInstance = sound.CreateInstance();
- soundInstance.IsLooped = true;
- soundInstance.Play();
- isSound2_Played = true;
- }
- }
3. 也由于SoundEffectInstance类别射后不理 (Fire-and-Forget)的特性,所以请务必确认在从页面离开时,必须将 DispatcherTimer与SoundEffectInstance物件都设定停止,否则音乐即便到了下一页还是会继续播放的!
- protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
- {
- base.OnNavigatedFrom(e);
- dt.Stop(); // Stop the DispatcherTimer
- soundInstance.Stop();
- }
最后,我推荐各位可以下载AppHub上官方提供的Silverlight Sound Sample | Windows Phone范例,里面有些程序 码可以参考使用,以下节录LoadSound与LoadSoundInstance这两个好用的Method给各位参考:
- /// <summary>
- /// Loads a wav file into an XNA Framework SoundEffect.
- /// </summary>
- /// <param name="SoundFilePath">Relative path to the wav file.</param>
- /// <param name="Sound">The SoundEffect to load the audio into.</param>
- private void LoadSound(String SoundFilePath, out SoundEffect Sound)
- {
- // For error checking, assume we'll fail to load the file.
- Sound = null;
- try
- {
- // Holds informations about a file stream.
- StreamResourceInfo SoundFileInfo = App.GetResourceStream(new Uri(SoundFilePath, UriKind.Relative));
- // Create the SoundEffect from the Stream
- Sound = SoundEffect.FromStream(SoundFileInfo.Stream);
- }
- catch (NullReferenceException)
- {
- // Display an error message
- MessageBox.Show("Couldn't load sound " + SoundFilePath);
- }
- }
- /// <summary>
- /// Loads a wav file into an XNA Framework SoundEffect.
- /// Then, creates a SoundEffectInstance from the SoundEffect.
- /// </summary>
- /// <param name="SoundFilePath">Relative path to the wav file.</param>
- /// <param name="Sound">The SoundEffect to load the audio into.</param>
- /// <param name="SoundInstance">The SoundEffectInstance to create from Sound.</param>
- private void LoadSoundInstance(String SoundFilePath, out SoundEffect Sound, out SoundEffectInstance SoundInstance)
- {
- // For error checking, assume we'll fail to load the file.
- Sound = null;
- SoundInstance = null;
- try
- {
- LoadSound(SoundFilePath, out Sound);
- SoundInstance = Sound.CreateInstance();
- }
- catch (NullReferenceException)
- {
- // Display an error message
- MessageBox.Show("Couldn't load sound instance " + SoundFilePath);
- }
- }