Windowsストア・アプリやWindows Phone 8で効果音を鳴らす方法として、今回はMediaElementコントロールを使う方法と、WP 8向けにXNAフレームワークを使う方法を説明。
powered by Insider.NET
デスクトップ向けの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]
ページの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を使うことも可能だ。
詳しくは、次のドキュメントを参考にしてほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.