音を多重再生

標準の Windows API だと、1つのプロセス内では複数の音を同時に出すことができない。複数の音を同時に出すためには、プロセスを別にするか、DirectSound(DirectX) などの API を使う必要がある。


.NET から DirectSound を使う方法にはつぎのものが考えられる。

  1. DirectSound の Unmanaged な API を直接呼ぶ。
  2. DirectSound 用の Managed なクラスを使う。
  3. 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();
        }

        // MDXDirectSound を再生する。
        private void button1_Click(object sender, EventArgs e)
        {
            DirectSound.SecondaryBuffer tempBuffer = secondaryBuffer.Clone(device);
            tempBuffer.Play(0, DirectSound.BufferPlayFlags.Default);
        }

        // VBCOM で再生する。
        private void button2_Click(object sender, EventArgs e)
        {
            DxVBLibA.DirectSoundSecondaryBuffer8 tempBuf = sound.DuplicateSoundBuffer(comBuf);
            tempBufs.Add(tempBuf);
            tempBuf.Play(DxVBLibA.CONST_DSBPLAYFLAGS.DSBPLAY_DEFAULT);
        }

        // VBCOM の参照カウンターの始末?これで十分とは思えないが。
        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);
                }
            }
        }

        // MDXDirectX(昔の DirectShow 相当?) を再生する。
        private void button3_Click(object sender, EventArgs e)
        {
            Audio audio = new Audio(@"C:\abc.wav");
            audio.Play();
        }
    }
}

私見だが、VB 用 COM ライブラリー(DxVBLibA)が、将来も安定して使えそうな予感がして良さそうに思う。


http://ap.atmarkit.co.jp/bbs/core/fdotnet/21583