The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

WP7 修練 DAY 10:Silverlight 應用程式中如何正確播放音效

在以 Silverlight 為主的 Windows Phone 7 應用程式中播放音效,其實只有幾種方法,若要播放音效檔,比較常見的會用 XNA Framework 裡的 SoundEffect SoundEffectInstance 類別來播放,在者就是用 MediaElement 來播放較長的音樂或影片,本篇文章的主軸會以播放音效為主。使用不同的方法來播放音效有許多不同的注意事項,一不小心應用程式就會發生非預期例外,在此整理與分享一些使用上的心得筆記。

由於這兩個類別被編譯在 Microsoft.Xna.Framework.dll 組件中,因此使用前必須先從專案加入組件參考:

 

使用時要記得引用 Microsoft.Xna.FrameworkMicrosoft.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 物件,然後再透過 SoundEffectCreateInstance 來取得一個新的 SoundEffectInstance 實體,不需要先執行 FrameworkDispatcher.Update(); 就可以播放聲音,而且也能透過其他方法來暫停、繼續或停止播放音效,這是與直接使用 SoundEffect 物件來播放聲音時最大的差異。

另一個與 SoundEffect 播放音效的差異在於,使用 SoundEffectInstance 可以設定「重複播放音效」功能,這樣的功能可以運用在需要提供背景環境音效時使用。啟用的方法也很簡單,只要在 SoundEffectInstance 物件上設定 IsLooped 屬性為 true 即可。

另外,若要播放長時間並重複的背景音效,建議可以調整 SoundEffectInstance 物件的 Volume 屬性,將聲音調小一點點,就很像背景音樂了,如下程式範例:

soundInstance.Volume = 0.8f;

 

§ 特別注意 §

無論使用 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) 的特性,所以請務必確認在從頁面離開時,必須將 DispatcherTimerSoundEffectInstance 物件都設定停止,否則音樂即便到了下一頁還是會繼續播放的!

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);
    }
}

 

今日修練總結

一個看似簡單的類別,其實也有許多學問在,由於只寫 Silverlight 應用程式的人可能不知道 XNA Framework 中的一些注意事項,所以當在 WP7 應用程式中混用 Silverlight 與 XNA 的時候,的確有可能遇到一些不可預期的錯誤,希望透過這篇心得筆記可以讓那些需要載入音效的人少走些冤望路!

 

相關連結