DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

埋め込まれたWAVEのリソースファイルを再生する

ここでは、アセンブリにリソースとして埋め込んだWAVファイルを再生する方法を幾つか紹介します。

.NET Framework 2.0以降で、SoundPlayerクラスを使用する方法

.NET Framework 2.0から追加されたSoundPlayerクラスを使用すれば、簡単にストリームやバイト配列のWAVEオーディオデータを再生できますので、オーディオリソースを再生するのに最適です。

早速、例を示します。ここでは、「Visual Studioでリソースを管理する」で紹介した方法により、WAVEファイルが"Voice1"という名前のリソースとして追加されているものとし、このリソースを再生しています。

VB.NET
コードを隠すコードを選択
'オーディオリソースを取り出す
Dim strm As System.IO.Stream = My.Resources.Voice1
'同期再生する
Dim player As New System.Media.SoundPlayer(strm)
player.PlaySync()
'後始末
player.Dispose()
C#
コードを隠すコードを選択
//オーディオリソースを取り出す
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メソッドを使用する方法

.NET Framework 2.0以降のVB.NETでは、My.Computer.Audio.Playメソッドで再生することもできます。Audio.Playメソッドは内部でSoundPlayerクラスを使用しているため、SoundPlayerクラスを使用した方法と根本的には同じです。非同期再生した際の不具合も同じです。

VB.NET
コードを隠すコードを選択
'オーディオリソースを取り出す
Dim strm As System.IO.Stream = My.Resources.Voice1
'再生する
My.Computer.Audio.Play(strm, AudioPlayMode.WaitToComplete)
C#
コードを隠すコードを選択
//参照に"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ファイルを一時ファイルに保存し、再生するという方法があります。これは、例えば次のようになります。(この例では、作成した一時ファイルを削除していません。実際には適当なタイミングで削除すべきです。)

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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関数で再生する方法

PlaySound(もしくはsndPlaySound)関数を使ってWAVを再生する場合は、一時ファイルを作成する必要はありません。次のようにメモリ内に読み込まれたデータをそのまま再生することができます。

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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関数を使った方がよいでしょう。

メモリ内のWAVEデータを非同期再生する際の不具合について

SoundPlayerクラスや、My.Computer.Audio.Playメソッドを使ってストリームのWAVEデータを非同期再生すると、うまく再生されないことがあります。PlaySound関数を使用してメモリ内のWAVEデータを非同期再生する場合も同じです。この問題に関しては、「SoundPlayer bug - Calling unmanaged APIs」で詳しく説明されています。

この記事によると、再生しているWAVEデータがガベージコレクタによって移動させられてしまう場合があることが原因ということです。解決法として、非同期再生する前にWAVEデータをガベージコレクタが移動できないように固定する方法が紹介されています。

この方法を使って非同期再生する方法を以下に示します。Button1で再生、Button2で停止します。

VB.NET
コードを隠すコードを選択
'サウンドを再生する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
C#
コードを隠すコードを選択
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();
}
  • 履歴:
  • 2007/2/5 My.Computer.Audio.Playメソッドを使用した方法を追加。
  • 2007/2/16 非同期再生での不具合の説明を追加。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。