音を多重再生
標準の Windows API だと、1つのプロセス内では複数の音を同時に出すことができない。複数の音を同時に出すためには、プロセスを別にするか、DirectSound(DirectX) などの API を使う必要がある。
.NET から DirectSound を使う方法にはつぎのものが考えられる。
- DirectSound の Unmanaged な API を直接呼ぶ。
- DirectSound 用の Managed なクラスを使う。
- DirectSound の VB 用 COM ライブラリーを使う。
さすがに Unmanaged な API を直接呼ぶのは unibon には難しく、Managed なクラス(MDX)と VB 用 COM ライブラリーを使うことを試してみた。
参照設定はつぎのとおり
MDX: Core DirectSound classes for Managed DirectX (Apr2006_MDX1_x86.cab の中の microsoft.directx.directsound.dll, Microsoft.DirectX.DirectSound 名前空間)
MDX: Core AudioVideoPlayback classes for Managed DirectX(〃 microsoft.directx.audiovideoplayback.dll, Microsoft.DirectX.AudioVideoPlayback 名前空間)
COM: DirectX 8 for Visual Basic Type Library (c:\windows\system32\dx8vb.dll)
これらは DirectX のランタイムか SDK のどっちかに入っているはず。
これらの API を使った自前のサンプルプログラムはつぎのとおり。
SetCooperativeLevel は Normal か Priority のどっちにすべきなんだろう?とりあえず Priority にしてみた。Priority のほうが Normal よりも「強い」(?)らしい。
MDX も COM もイベントというものがないのか?再生終了のタイミングなどは取得できないのか?ポーリングしかないの?
using System; using System.Collections.Generic; using System.Diagnostics; using System.Windows.Forms; using DirectSound = Microsoft.DirectX.DirectSound; using Microsoft.DirectX.AudioVideoPlayback; namespace WindowsFormsApplication1 { public partial class Form1 : Form { // MDX(DirectSound) private DirectSound.Device device; private DirectSound.SecondaryBuffer secondaryBuffer; // COM private DxVBLibA.DirectSound8 sound; private DxVBLibA.DirectSoundSecondaryBuffer8 comBuf; private List<DxVBLibA.DirectSoundSecondaryBuffer8> tempBufs = new List<DxVBLibA.DirectSoundSecondaryBuffer8>(); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { // MDX(DirectSound) device = new DirectSound.Device(); device.SetCooperativeLevel(this, DirectSound.CooperativeLevel.Priority); DirectSound.BufferDescription desc = new DirectSound.BufferDescription(); // これがないと自身のウィンドウが非アクティブになっている間、音が出なくなる。 desc.Flags = DirectSound.BufferDescriptionFlags.GlobalFocus; secondaryBuffer = new DirectSound.SecondaryBuffer(@"C:\abc.wav", desc, device); // COM DxVBLibA.DirectX8 directX = new DxVBLibA.DirectX8(); sound = directX.DirectSoundCreate(""); sound.SetCooperativeLevel(this.Handle.ToInt32(), DxVBLibA.CONST_DSSCLFLAGS.DSSCL_PRIORITY); DxVBLibA.DSBUFFERDESC comDesc = new DxVBLibA.DSBUFFERDESC(); // これがないと自身のウィンドウが非アクティブになっている間、音が出なくなる。 comDesc.lFlags = DxVBLibA.CONST_DSBCAPSFLAGS.DSBCAPS_GLOBALFOCUS; comBuf = sound.CreateSoundBufferFromFile(@"C:\abc.wav", comDesc); timer1.Interval = 500; timer1.Start(); } // MDX で DirectSound を再生する。 private void button1_Click(object sender, EventArgs e) { DirectSound.SecondaryBuffer tempBuffer = secondaryBuffer.Clone(device); tempBuffer.Play(0, DirectSound.BufferPlayFlags.Default); } // VB 用 COM で再生する。 private void button2_Click(object sender, EventArgs e) { DxVBLibA.DirectSoundSecondaryBuffer8 tempBuf = sound.DuplicateSoundBuffer(comBuf); tempBufs.Add(tempBuf); tempBuf.Play(DxVBLibA.CONST_DSBPLAYFLAGS.DSBPLAY_DEFAULT); } // VB 用 COM の参照カウンターの始末?これで十分とは思えないが。 private void timer1_Tick(object sender, EventArgs e) { for (int i = tempBufs.Count - 1; i >= 0; i--) { DxVBLibA.DirectSoundSecondaryBuffer8 tempBuf = tempBufs[i]; DxVBLibA.CONST_DSBSTATUSFLAGS status = tempBuf.GetStatus(); if ((status & DxVBLibA.CONST_DSBSTATUSFLAGS.DSBSTATUS_PLAYING) == 0) { tempBufs.RemoveAt(i); int c = System.Runtime.InteropServices.Marshal.ReleaseComObject(tempBuf); //Debug.WriteLine("counter = " + c); } } } // MDX で DirectX(昔の DirectShow 相当?) を再生する。 private void button3_Click(object sender, EventArgs e) { Audio audio = new Audio(@"C:\abc.wav"); audio.Play(); } } }