連載
» 2012年12月13日 15時04分 公開

WinRT/Metro TIPS:アプリに埋め込んだ効果音を鳴らすには?[Win 8/WP 8]

Windowsストア・アプリやWindows Phone 8で効果音を鳴らす方法として、今回はMediaElementコントロールを使う方法と、WP 8向けにXNAフレームワークを使う方法を説明。

[山本康彦,BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 デスクトップ向けのWindowsアプリで効果音を鳴らしたい場合には、System.Media名前空間のSoundPlayerクラスを使って簡単に.wavファイルを再生できた。しかし、Windowsストア・アプリではSystem.Media名前空間はなくなっている。

 本稿ではWindowsストア・アプリで音を再生する方法として、Windows.UI.Xaml.Controls名前空間のMediaElementコントロールを使う方法と、Windows Phone 8(以降、WP 8)でXNAフレームワークを使う方法を説明する。本稿のサンプルは「Windows Store app samples:MetroTips #17のWindows 8版」と「同MetroTips #17のWP 8版」からダウンロードできる。

事前準備

 Windows 8(以降、Win 8)向けのWindowsストア・アプリを開発するには、Win 8とVisual Studio 2012(以降、VS 2012)が必要である。これらを準備するには、第1回のTIPSを参考にしてほしい。本稿では64bit版Win 8 ProとVS 2012 Express for Windows 8を使用している。

 また、WP 8向けのアプリを開発するには、SLAT対応PC上の64bit版Win 8 Pro以上Windows Phone SDK 8.0が必要となる。

MediaElementコントロールとは?

 同名のコントロールがWPFにもあるが(System.Windows.Controls名前空間)、Windowsストア・アプリ用のものはWindows.UI.Xaml.Controls名前空間にある。基本的には動画を再生するためのコンポーネントである。

動画を再生しているMediaElementコントロール(左:Win 8、右:WP 8)

MediaElementコントロールでサウンド・ファイルを再生するには?[Win 8/WP 8]

 ページのXAMLコードにMediaElementコントロールを配置し、Sourceプロパティにサウンド・ファイルを設定する。ただし、コントロールを画面に表示する必要はないので、WidthプロパティとHeightプロパティは設定しない。

 音を出すためのボタンも一緒に記述すると、次のようなXAMLコードになる。

<MediaElement x:Name="SoundGoo" Source="/Assets/17goo.wav" AutoPlay="False" />
<Button Content="グー" Click="GooButtonClicked" ……略…… />


サウンド・ファイルをSourceプロパティに設定したMediaElementと、Buttonコントロール


 ここでAutoPlayプロパティをTrueにしておくと、画面が表示されたときに自動的に再生される。なお、WP 8のMediaElementコントロールではプロパティの数が少ないものの、基本的な使い方は同じである。

 そして、ボタンをタップしたときに再生するためのコードは、次のようにMediaElementコントロールのPlayメソッドを呼び出すだけでよい。Playメソッドは直ちに制御を返し、非同期で動画や音声を再生する。

private void GooButtonClicked(object sender, RoutedEventArgs e)
{
  this.SoundGoo.Play();
}


Private Sub GooButtonClicked(sender As Object, e As RoutedEventArgs)
  SoundGoo.Play()
End Sub


ボタンのタップ時に音を再生するコード(上:C#、下:VB)


MediaElementコントロールを直接使って再生するには?[Win 8/WP 8]

 再生したいサウンド・ファイルの数が多くなってくると、XAMLコードが煩雑になってくるし、MediaElementコントロールが多数同時にインスタンス化されるのでリソースも心配になってくる*1

*1 実際にWP 8で十数個のMediaElementコントロールをXAMLコードに配置して順番に再生してみたところ、途中から音が出なくなってしまった(エミュレータとHTC 8Xで確認)。なお、Windows 8は数十個くらいでは問題ないようである


 次のようにすれば、コードからMediaElementコントロールをインスタンス化して使うことができる。

private async void ChokiButtonClicked(object sender, RoutedEventArgs e)
{
  //コード内でMediaElementコントロールをインスタンス化して使う方法
  var uri = new Uri("ms-appx:///Assets/19choki.wav");
  var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(uri);
  var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);

  var media = new MediaElement();
  media.SetSource(stream, file.ContentType);
  media.Play();
}


  Private Async Sub ChokiButtonClicked(sender As Object, e As RoutedEventArgs)

    'コード内でMediaElementコントロールをインスタンス化して使う方法
    Dim uri = New Uri("ms-appx:///Assets/19choki.wav")
    Dim file = Await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(uri)
    Dim stream = Await file.OpenAsync(Windows.Storage.FileAccessMode.Read)

    Dim media = New MediaElement()
    media.SetSource(stream, file.ContentType)
    media.Play()
  End Sub


XAMLコードに定義せずにMediaElementコントロールを使うコード(上:C#、下:VB)


 メソッドの最後でstreamオブジェクトのDisposeメソッドを呼び出したくなるが、ガベージ・コレクタに任せること。Playメソッドは非同期実行されるので、ここでDisposeメソッドを呼び出してしまうと再生時にstreamオブジェクトが存在しなくなり、音が出ない。

 なお、これでMediaElementコントロールが多数インスタンス化されてしまう問題は解決したが、その代わりにファイル読み込みのタイム・ラグが発生してしまう。このコードではボタンがタップされてからファイルを読み込むからだ。

 それを解決するには、あらかじめファイルをbyte配列に読み込んでおいてMediaElementコントロールのSetSourceメソッドに渡すことができればよい。しかし、byte配列をSetSourceメソッドに渡せる型(Windows.Storage.Streams.IRandomAccessStreamインターフェイス型)に変換するAPIは用意されていないので、その部分を自分でコーディングしなければならない。*2

*2 本稿では説明しないが、例えばCan Bilgin氏のMemoryRandomAccessStreamクラスを利用すると実現できる。


XNAフレームワークを使って音を再生するには?[WP 8]

 WP 8ではXNAフレームワークが使える。そのためには、まずMSDNの記事「Windows Phone アプリケーションでの XNA Framework イベントの有効化」に従ってXNAFrameworkDispatcherServiceクラスを用意する。

public sealed class XNAFrameworkDispatcherService : IApplicationService
{
  private DispatcherTimer frameworkDispatcherTimer;
  public XNAFrameworkDispatcherService()
  {
    this.frameworkDispatcherTimer = new DispatcherTimer();
    this.frameworkDispatcherTimer.Interval = TimeSpan.FromTicks(333333);
    this.frameworkDispatcherTimer.Tick += frameworkDispatcherTimer_Tick;
    Microsoft.Xna.Framework.FrameworkDispatcher.Update();
  }
  void frameworkDispatcherTimer_Tick(object sender, EventArgs e)
  {
    Microsoft.Xna.Framework.FrameworkDispatcher.Update();
  }
  void IApplicationService.StartService(ApplicationServiceContext context)
  {
    this.frameworkDispatcherTimer.Start();
  }
  void IApplicationService.StopService()
  {
    this.frameworkDispatcherTimer.Stop();
  }
}


Public Class XNAFrameworkDispatcherService
  Implements IApplicationService

  Private frameworkDispatcherTimer As DispatcherTimer

  Public Sub New()
    frameworkDispatcherTimer = New DispatcherTimer()
    frameworkDispatcherTimer.Interval = TimeSpan.FromTicks(333333)
    AddHandler frameworkDispatcherTimer.Tick, _
               AddressOf frameworkDispatcherTimer_Tick
    Microsoft.Xna.Framework.FrameworkDispatcher.Update()
  End Sub

  Sub frameworkDispatcherTimer_Tick(sender As Object, e As EventArgs)
    Microsoft.Xna.Framework.FrameworkDispatcher.Update()
  End Sub

  Sub StartService(context As ApplicationServiceContext) _
        Implements IApplicationService.StartService
    frameworkDispatcherTimer.Start()
  End Sub

  Sub StopService() Implements IApplicationService.StopService
    frameworkDispatcherTimer.Stop()
  End Sub
End Class


XNAFrameworkDispatcherServiceクラス(上:C#、下:VB)


 次に、XNAFrameworkDispatcherServiceクラスのインスタンスを、App.xamlファイル内の<Application.ApplicationLifetimeObjects>要素に設定する(次のコード)。

<Application.ApplicationLifetimeObjects>
  <!--アプリケーションのライフタイム イベントを処理する必須オブジェクト-->
  <shell:PhoneApplicationService
         Launching="Application_Launching" Closing="Application_Closing"
         Activated="Application_Activated" Deactivated="Application_Deactivated"/>

  <local:XNAFrameworkDispatcherService /><!-- XNA フレームワークを利用するために追加した -->

</Application.ApplicationLifetimeObjects>


App.xamlファイルの一部
<Application.ApplicationLifetimeObjects>要素の配下にXNAFrameworkDispatcherServiceクラスのインスタンスを追加した。


 XNAフレームワークでは事前にサウンド・ファイルを読み込んでおける。ここではページのコンストラクタの最後で処理をするようにしてみよう。

 SoundEffectクラス(Microsoft.Xna.Framework.Audio名前空間)のFromStreamメソッドを使ってサウンド・ファイルを読み込んだ状態のSoundEffectクラスのインスタンスを作り、そのCreateInstanceメソッドを呼び出すと、SoundEffectInstanceクラスのインスタンスが生成される。それをメンバ変数に保持しておくことができる。

// コンストラクタ
public MainPage()
{
  // ……略……

  LoadSounds(); // 追加
}

// SoundEffectInstance=音を再生するクラスのインスタンス
private Microsoft.Xna.Framework.Audio.SoundEffectInstance gooSound;
private Microsoft.Xna.Framework.Audio.SoundEffectInstance chokiSound;
private Microsoft.Xna.Framework.Audio.SoundEffectInstance parSound;

private void LoadSounds()
{
  gooSound = LoadSound("Assets/17goo.wav");
  chokiSound = LoadSound("Assets/19choki.wav");
  parSound = LoadSound("Assets/18par.wav");
}

// サウンド・ファイルを読み込み、SoundEffectInstanceインスタンスを作成する
private Microsoft.Xna.Framework.Audio.SoundEffectInstance LoadSound(string filePath)
{
  var streamInfo = Application.GetResourceStream(
      new Uri(filePath, UriKind.RelativeOrAbsolute));
  var se = Microsoft.Xna.Framework.Audio.SoundEffect.FromStream(streamInfo.Stream);
  return se.CreateInstance();
}


' コンストラクター
Public Sub New()
  ' ……略……

  LoadSounds() '追加
End Sub
' SoundEffectInstance=音を再生するクラスのインスタンス
Private gooSound As Microsoft.Xna.Framework.Audio.SoundEffectInstance
Private chokiSound As Microsoft.Xna.Framework.Audio.SoundEffectInstance
Private parSound As Microsoft.Xna.Framework.Audio.SoundEffectInstance

Private Sub LoadSounds()
  gooSound = LoadSound("Assets/17goo.wav")
  chokiSound = LoadSound("Assets/19choki.wav")
  parSound = LoadSound("Assets/18par.wav")
End Sub

' サウンド・ファイルを読み込み、SoundEffectInstanceインスタンスを作成する
Private Function LoadSound(filePath As String) As Microsoft.Xna.Framework.Audio.SoundEffectInstance
  Dim streamInfo = Application.GetResourceStream(
        New Uri(filePath, UriKind.RelativeOrAbsolute))
  Dim se = Microsoft.Xna.Framework.Audio.SoundEffect.FromStream(streamInfo.Stream)
  Return se.CreateInstance()
End Function


サウンド・ファイルを読み込んでSoundEffectInstanceインスタンスを作成するコード(上:C#、下:VB)


 後はPlayメソッドを呼び出して音を出すだけである。

private void GooButtonClicked(object sender, RoutedEventArgs e)
{
  gooSound.Play();
}


Private Sub GooButtonClicked(sender As Object, e As RoutedEventArgs)
  gooSound.Play()
End Sub


SoundEffectInstanceインスタンスを使って音を出すコード(上:C#、下:VB)


 なお、SoundEffectInstanceインスタンスを使っての同時再生には16個までという制限がある。

まとめ

 サウンド・ファイルを再生するにはMediaElementコントロールが使える。WP 8ではXNAフレームワークも利用できる。また、本稿では説明しなかったが、Win 8ではC++でXAudio2を使うことも可能だ。

 詳しくは、次のドキュメントを参考にしてほしい。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。