ここでは、アセンブリにリソースとして埋め込んだWAVファイルを再生する方法を幾つか紹介します。
.NET Framework 2.0から追加されたSoundPlayerクラスを使用すれば、簡単にストリームやバイト配列のWAVEオーディオデータを再生できますので、オーディオリソースを再生するのに最適です。
早速、例を示します。ここでは、「Visual Studioでリソースを管理する」で紹介した方法により、WAVEファイルが"Voice1"という名前のリソースとして追加されているものとし、このリソースを再生しています。
'オーディオリソースを取り出す Dim strm As System.IO.Stream = My.Resources.Voice1 '同期再生する Dim player As New System.Media.SoundPlayer(strm) player.PlaySync() '後始末 player.Dispose()
//オーディオリソースを取り出す System.IO.Stream strm = Properties.Resources.Voice1; //同期再生する System.Media.SoundPlayer player = new System.Media.SoundPlayer(strm); player.PlaySync(); //後始末 player.Dispose();
ここではSoundPlayer.PlaySyncメソッドにより同期再生しましたが、PlayやPlayLoopingメソッドにより非同期(バックグラウンド)再生することもできます。ただしこれらのメソッドをそのまま使用して非同期再生させると、うまくいかないことがあるようです。この問題は、後述します。
.NET Framework 2.0以降のVB.NETでは、My.Computer.Audio.Playメソッドで再生することもできます。Audio.Playメソッドは内部でSoundPlayerクラスを使用しているため、SoundPlayerクラスを使用した方法と根本的には同じです。非同期再生した際の不具合も同じです。
'オーディオリソースを取り出す Dim strm As System.IO.Stream = My.Resources.Voice1 '再生する My.Computer.Audio.Play(strm, AudioPlayMode.WaitToComplete)
//参照に"Microsoft.VisualBasic.dll"を追加する必要がある //オーディオリソースを取り出す System.IO.Stream strm = Properties.Resources.Voice1; //再生する Microsoft.VisualBasic.Devices.Audio myAudio = new Microsoft.VisualBasic.Devices.Audio(); myAudio.Play(strm, Microsoft.VisualBasic.AudioPlayMode.WaitToComplete);
まず、埋め込まれたWAVファイルを一時ファイルに保存し、再生するという方法があります。これは、例えば次のようになります。(この例では、作成した一時ファイルを削除していません。実際には適当なタイミングで削除すべきです。)
Imports System.Runtime.InteropServices Imports System.Resources Class MainClass 'サウンドを再生するWin32 APIの宣言 <Flags()> _ Public Enum PlaySoundFlags SND_SYNC = &H0 SND_ASYNC = &H1 SND_NODEFAULT = &H2 SND_MEMORY = &H4 SND_LOOP = &H8 SND_NOSTOP = &H10 SND_NOWAIT = &H2000 SND_ALIAS = &H10000 SND_ALIAS_ID = &H110000 SND_FILENAME = &H20000 SND_RESOURCE = &H40004 SND_PURGE = &H40 SND_APPLICATION = &H80 End Enum <System.Runtime.InteropServices.DllImport("winmm.dll", _ CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _ Private Shared Function PlaySound(ByVal pszSound As String, _ ByVal hmod As IntPtr, ByVal fdwSound As PlaySoundFlags) As Boolean End Function Public Shared Sub Main() Dim asm As System.Reflection.Assembly = _ System.Reflection.Assembly.GetExecutingAssembly() 'リソースの名前 Dim resourceName As String = asm.GetName().Name + ".test.wav" 'リソースを読み込む Dim strm As System.IO.Stream = _ asm.GetManifestResourceStream(resourceName) Dim buffer() As Byte = New Byte(strm.Length) {} strm.Read(buffer, 0, CInt(buffer.Length)) strm.Close() '一時ファイルに書き込む Dim tempName As String = System.IO.Path.GetTempFileName() Dim fs As New System.IO.FileStream( _ tempName, System.IO.FileMode.Create) fs.Write(buffer, 0, CInt(buffer.Length)) fs.Close() '音を鳴らす PlaySound(tempName, IntPtr.Zero, PlaySoundFlags.SND_FILENAME) End Sub End Class
using System.Runtime.InteropServices; using System.Resources; class MainClass { //サウンドを再生するWin32 APIの宣言 [Flags] public enum PlaySoundFlags : int { SND_SYNC = 0x0000, SND_ASYNC = 0x0001, SND_NODEFAULT = 0x0002, SND_MEMORY = 0x0004, SND_LOOP = 0x0008, SND_NOSTOP = 0x0010, SND_NOWAIT = 0x00002000, SND_ALIAS = 0x00010000, SND_ALIAS_ID = 0x00110000, SND_FILENAME = 0x00020000, SND_RESOURCE = 0x00040004, SND_PURGE = 0x0040, SND_APPLICATION = 0x0080 } [System.Runtime.InteropServices.DllImport("winmm.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)] private static extern bool PlaySound( string pszSound, IntPtr hmod, PlaySoundFlags fdwSound); public static void Main() { System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); //リソースの名前 string resourceName = asm.GetName().Name + ".test.wav"; //リソースを読み込む System.IO.Stream strm = asm.GetManifestResourceStream(resourceName); byte[] buffer = new Byte[strm.Length]; strm.Read(buffer, 0, (int) buffer.Length); strm.Close(); //一時ファイルに書き込む string tempName = System.IO.Path.GetTempFileName(); System.IO.FileStream fs = new System.IO.FileStream( tempName, System.IO.FileMode.Create); fs.Write(buffer, 0, (int) buffer.Length); fs.Close(); //音を鳴らす PlaySound(tempName, IntPtr.Zero, PlaySoundFlags.SND_FILENAME); } }
WAVEファイルを再生する方法については、「WAVEファイルを再生する」を参考にしてください。
PlaySound(もしくはsndPlaySound)関数を使ってWAVを再生する場合は、一時ファイルを作成する必要はありません。次のようにメモリ内に読み込まれたデータをそのまま再生することができます。
Imports System.Runtime.InteropServices Imports System.Resources Class MainClass 'サウンドを再生するWin32 APIの宣言 <Flags()> _ Public Enum PlaySoundFlags SND_SYNC = &H0 SND_ASYNC = &H1 SND_NODEFAULT = &H2 SND_MEMORY = &H4 SND_LOOP = &H8 SND_NOSTOP = &H10 SND_NOWAIT = &H2000 SND_ALIAS = &H10000 SND_ALIAS_ID = &H110000 SND_FILENAME = &H20000 SND_RESOURCE = &H40004 SND_PURGE = &H40 SND_APPLICATION = &H80 End Enum <System.Runtime.InteropServices.DllImport("winmm.dll", _ CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _ Private Shared Function PlaySound(ByVal pszSound() As Byte, _ ByVal hmod As IntPtr, ByVal fdwSound As PlaySoundFlags) As Boolean End Function Public Shared Sub Main() Dim asm As System.Reflection.Assembly = _ System.Reflection.Assembly.GetExecutingAssembly() 'リソースの名前 Dim resourceName As String = asm.GetName().Name + ".test.wav" 'リソースを読み込む Dim strm As System.IO.Stream = _ asm.GetManifestResourceStream(resourceName) Dim buffer() As Byte = New Byte(strm.Length) {} strm.Read(buffer, 0, CInt(buffer.Length)) strm.Close() '音を鳴らす PlaySound(buffer, IntPtr.Zero, PlaySoundFlags.SND_MEMORY) End Sub End Class
using System.Runtime.InteropServices; using System.Resources; class MainClass { //サウンドを再生するWin32 APIの宣言 [Flags] public enum PlaySoundFlags : int { SND_SYNC = 0x0000, SND_ASYNC = 0x0001, SND_NODEFAULT = 0x0002, SND_MEMORY = 0x0004, SND_LOOP = 0x0008, SND_NOSTOP = 0x0010, SND_NOWAIT = 0x00002000, SND_ALIAS = 0x00010000, SND_ALIAS_ID = 0x00110000, SND_FILENAME = 0x00020000, SND_RESOURCE = 0x00040004, SND_PURGE = 0x0040, SND_APPLICATION = 0x0080 } [System.Runtime.InteropServices.DllImport("winmm.dll")] private static extern bool PlaySound( byte[] pszSound, IntPtr hmod, PlaySoundFlags fdwSound); public static void Main() { System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); //リソースの名前 string resourceName = asm.GetName().Name + ".test.wav"; //リソースを読み込む System.IO.Stream strm = asm.GetManifestResourceStream(resourceName); byte[] buffer = new Byte[strm.Length]; strm.Read(buffer, 0, (int) buffer.Length); strm.Close(); //音を鳴らす PlaySound(buffer, IntPtr.Zero, PlaySoundFlags.SND_MEMORY); } }
補足:上記の例では音を再生するのにPlaySound関数を使用しましたが、代わりにsndPlaySound関数を使用すると、Windows XP(Professional Editionで確認)ではうまく行かないようです。sndPlaySound関数についてMSDNでは「この関数は、PlaySound関数のサブセットであり、Windowsの以前のバージョンとの互換性のために残されています。」と書かれており、PlaySound関数を使った方がよいでしょう。
SoundPlayerクラスや、My.Computer.Audio.Playメソッドを使ってストリームのWAVEデータを非同期再生すると、うまく再生されないことがあります。PlaySound関数を使用してメモリ内のWAVEデータを非同期再生する場合も同じです。この問題に関しては、「SoundPlayer bug - Calling unmanaged APIs」で詳しく説明されています。
この記事によると、再生しているWAVEデータがガベージコレクタによって移動させられてしまう場合があることが原因ということです。解決法として、非同期再生する前にWAVEデータをガベージコレクタが移動できないように固定する方法が紹介されています。
この方法を使って非同期再生する方法を以下に示します。Button1で再生、Button2で停止します。
'サウンドを再生するWin32 APIの宣言 <Flags()> _ Public Enum PlaySoundFlags SND_SYNC = &H0 SND_ASYNC = &H1 SND_NODEFAULT = &H2 SND_MEMORY = &H4 SND_LOOP = &H8 SND_NOSTOP = &H10 SND_NOWAIT = &H2000 SND_ALIAS = &H10000 SND_ALIAS_ID = &H110000 SND_FILENAME = &H20000 SND_RESOURCE = &H40004 SND_PURGE = &H40 SND_APPLICATION = &H80 End Enum <System.Runtime.InteropServices.DllImport("winmm.dll", _ CharSet:=System.Runtime.InteropServices.CharSet.Auto)> _ Private Shared Function PlaySound(ByVal pszSound As IntPtr, _ ByVal hmod As IntPtr, ByVal fdwSound As PlaySoundFlags) As Boolean End Function Private gcHandle As System.Runtime.InteropServices.GCHandle Private waveBuffer As Byte() = Nothing 'Waveを再生する Public Sub PlayWaveSound(ByVal strm As System.IO.Stream) If Not (Me.waveBuffer Is Nothing) Then Me.StopWaveSound() End If 'byte配列にする Me.waveBuffer = New Byte(strm.Length) {} strm.Read(Me.waveBuffer, 0, Me.waveBuffer.Length) 'GCによって移動されないようにする Me.gcHandle = System.Runtime.InteropServices.GCHandle.Alloc( _ Me.waveBuffer, System.Runtime.InteropServices.GCHandleType.Pinned) '非同期再生する PlaySound(Me.gcHandle.AddrOfPinnedObject(), IntPtr.Zero, _ PlaySoundFlags.SND_MEMORY Or PlaySoundFlags.SND_ASYNC) End Sub '再生中のWaveを停止する Public Sub StopWaveSound() If Me.waveBuffer Is Nothing Then Return End If '再生しているWAVEを停止する PlaySound(IntPtr.Zero, IntPtr.Zero, PlaySoundFlags.SND_PURGE) '解放する Me.gcHandle.Free() Me.waveBuffer = Nothing End Sub 'Button1のClickイベントハンドラ Private Sub Button1_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles Button1.Click 'オーディオリソースを取り出す Dim strm As System.IO.Stream = My.Resources.Voice1 '再生する Me.PlayWaveSound(strm) strm.Close() End Sub 'Button2のClickイベントハンドラ Private Sub Button2_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Button2.Click '停止する Me.StopWaveSound() End Sub
public enum PlaySoundFlags : int { SND_SYNC = 0x0000, SND_ASYNC = 0x0001, SND_NODEFAULT = 0x0002, SND_MEMORY = 0x0004, SND_LOOP = 0x0008, SND_NOSTOP = 0x0010, SND_NOWAIT = 0x00002000, SND_ALIAS = 0x00010000, SND_ALIAS_ID = 0x00110000, SND_FILENAME = 0x00020000, SND_RESOURCE = 0x00040004, SND_PURGE = 0x0040, SND_APPLICATION = 0x0080 } [System.Runtime.InteropServices.DllImport("winmm.dll")] private static extern bool PlaySound( IntPtr pszSound, IntPtr hmod, PlaySoundFlags fdwSound); private System.Runtime.InteropServices.GCHandle gcHandle; private byte[] waveBuffer = null; //Waveを再生する public void PlayWaveSound(System.IO.Stream strm) { if (this.waveBuffer != null) this.StopWaveSound(); //byte配列にする this.waveBuffer = new byte[strm.Length]; strm.Read(this.waveBuffer, 0, this.waveBuffer.Length); //GCによって移動されないようにする this.gcHandle = System.Runtime.InteropServices.GCHandle.Alloc( this.waveBuffer, System.Runtime.InteropServices.GCHandleType.Pinned); //非同期再生する PlaySound(this.gcHandle.AddrOfPinnedObject(), IntPtr.Zero, PlaySoundFlags.SND_MEMORY | PlaySoundFlags.SND_ASYNC); } //再生中のWaveを停止する public void StopWaveSound() { if (this.waveBuffer == null) return; //再生しているWAVEを停止する PlaySound(IntPtr.Zero, IntPtr.Zero, PlaySoundFlags.SND_PURGE); //解放する this.gcHandle.Free(); this.waveBuffer = null; } //Button1のClickイベントハンドラ private void Button1_Click(object sender, EventArgs e) { //オーディオリソースを取り出す System.IO.Stream strm = Properties.Resources.Voice1; //再生する this.PlayWaveSound(strm); strm.Close(); } //Button2のClickイベントハンドラ private void Button2_Click(object sender, EventArgs e) { //停止する this.StopWaveSound(); }
(この記事は、「.NETプログラミング研究 第50号」で紹介したものを基にしています。)