WaveOut API (低レベルAPI) を利用して WAVファイルのサウンドを再生する - ファイル全体をバッファに読み込む - C#
概要
こちらの記事では、 WaveOut API を利用して、Wavファイルのサウンドを再生するコードを紹介しました。現在のPCではWAVファイルに対して十分大きいメモリを搭載しているため、最初にWAVファイルのすべてのデータを読み込んでしまうことも可能です。この記事では、ファイル全体を一度にバッファに読み込んで、WaveOut APIで再生するコードを紹介します。
プログラム
UI
下図のUIを作成します。
コード
下記のコードを記述します。
FormMain.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LowLevelWavePlayFullBuffered
{
public partial class FormMain : Form
{
private iPentecLowLevelWavePlay llwp = new iPentecLowLevelWavePlay();
public FormMain()
{
InitializeComponent();
}
private void button_FileOpen_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
llwp.OpenFile(openFileDialog1.FileName);
}
}
private void button_FileClose_Click(object sender, EventArgs e)
{
llwp.CloseFile();
}
private void button_Play_Click(object sender, EventArgs e)
{
llwp.Play();
}
private void button_Stop_Click(object sender, EventArgs e)
{
llwp.Stop();
}
}
}
iPentecLowLevelWavePlay.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;
namespace LowLevelWavePlayFullBuffered
{
public enum MMRESULT
{
MMSYSERR_NOERROR = 0,
MMSYSERR_ERROR = (MMSYSERR_NOERROR + 1),
MMSYSERR_BADDEVICEID = (MMSYSERR_NOERROR + 2),
MMSYSERR_NOTENABLED = (MMSYSERR_NOERROR + 3),
MMSYSERR_ALLOCATED = (MMSYSERR_NOERROR + 4),
MMSYSERR_INVALHANDLE = (MMSYSERR_NOERROR + 5),
MMSYSERR_NODRIVER = (MMSYSERR_NOERROR + 6),
MMSYSERR_NOMEM = (MMSYSERR_NOERROR + 7),
MMSYSERR_NOTSUPPORTED = (MMSYSERR_NOERROR + 8),
MMSYSERR_BADERRNUM = (MMSYSERR_NOERROR + 9),
MMSYSERR_INVALFLAG = (MMSYSERR_NOERROR + 10),
MMSYSERR_INVALPARAM = (MMSYSERR_NOERROR + 11),
MMSYSERR_HANDLEBUSY = (MMSYSERR_NOERROR + 12),
MMSYSERR_INVALIDALIAS = (MMSYSERR_NOERROR + 13),
MMSYSERR_BADDB = (MMSYSERR_NOERROR + 14),
MMSYSERR_KEYNOTFOUND = (MMSYSERR_NOERROR + 15),
MMSYSERR_READERROR = (MMSYSERR_NOERROR + 16),
MMSYSERR_WRITEERROR = (MMSYSERR_NOERROR + 17),
MMSYSERR_DELETEERROR = (MMSYSERR_NOERROR + 18),
MMSYSERR_VALNOTFOUND = (MMSYSERR_NOERROR + 19),
MMSYSERR_NODRIVERCB = (MMSYSERR_NOERROR + 20),
MMSYSERR_MOREDATA = (MMSYSERR_NOERROR + 21),
MMSYSERR_LASTERROR = (MMSYSERR_NOERROR + 21)
}
public enum WAVE_FORMAT
{
WAVE_FORMAT_1M08 = 0x00000001,
WAVE_FORMAT_1S08 = 0x00000002,
WAVE_FORMAT_1M16 = 0x00000004,
WAVE_FORMAT_1S16 = 0x00000008,
WAVE_FORMAT_2M08 = 0x00000010,
WAVE_FORMAT_2S08 = 0x00000020,
WAVE_FORMAT_2M16 = 0x00000040,
WAVE_FORMAT_2S16 = 0x00000080,
WAVE_FORMAT_4M08 = 0x00000100,
WAVE_FORMAT_4S08 = 0x00000200,
WAVE_FORMAT_4M16 = 0x00000400,
WAVE_FORMAT_4S16 = 0x00000800
}
public enum WAVEHDR_FLAG
{
WHDR_DONE = 0x00000001,
WHDR_PREPARED = 0x00000002,
WHDR_BEGINLOOP = 0x00000004,
WHDR_ENDLOOP = 0x00000008,
WHDR_INQUEUE = 0x00000010
}
public enum TIME_TYPE
{
TIME_MS = 0x0001,
TIME_SAMPLES = 0x0002,
TIME_BYTES = 0x0004,
TIME_SMPTE = 0x0008,
TIME_MIDI = 0x0010,
TIME_TICKS = 0x0020
}
[StructLayout(LayoutKind.Sequential)]
public struct WAVEFORMATEX
{
public ushort wFormatTag;
public ushort nChannels;
public uint nSamplesPerSec;
public uint nAvgBytesPerSec;
public ushort nBlockAlign;
public ushort wBitsPerSample;
public ushort cbSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct WAVEHDR
{
public IntPtr lpData;
public uint dwBufferLength;
public uint dwBytesRecorded;
public uint dwUser;
public uint dwFlags;
public uint dwLoops;
public IntPtr lpNext;
public uint reserved;
}
[StructLayout(LayoutKind.Explicit)]
public struct MMTIME
{
[FieldOffset(0)]
public uint wType;
[FieldOffset(4)]
public uint ms;
[FieldOffset(4)]
public uint sample;
[FieldOffset(4)]
public uint cb;
[FieldOffset(4)]
public uint ticks;
[FieldOffset(4)]
public ushort hour;
[FieldOffset(6)]
public ushort min;
[FieldOffset(8)]
public ushort sec;
[FieldOffset(10)]
public ushort frame;
[FieldOffset(12)]
public ushort fps;
[FieldOffset(14)]
public ushort dummy;
[FieldOffset(16)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public ushort[] pad;
[FieldOffset(4)]
public uint songptrpos;
}
[StructLayout(LayoutKind.Sequential)]
public struct WAVEINCAPS
{
public ushort wMid;
public ushort wPid;
public uint vDriverVersion;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] szPname;
uint dwFormats;
ushort wChannels;
ushort wReserved1;
}
[StructLayout(LayoutKind.Sequential)]
public struct WAVEOUTCAPS
{
public ushort wMid;
public ushort wPid;
public uint vDriverVersion;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public char[] szPname;
public uint dwFormats;
public ushort wChannels;
public ushort wReserved1;
public uint dwSupport;
}
public enum WaveOutMessage
{
Close = 0x3BC,
Done = 0x3BD,
Open = 0x3BB
}
public class iPentecLowLevelWavePlay
{
private const uint WAVE_MAPPER = unchecked((uint)(-1));
private const uint CALLBACK_NULL = 0x00000000; /* no callback */
private const uint CALLBACK_WINDOW = 0x00010000; /* dwCallback is a HWND */
private const uint CALLBACK_TASK = 0x00020000; /* dwCallback is a HTASK */
private const uint CALLBACK_FUNCTION = 0x00030000; /* dwCallback is a FARPROC */
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern MMRESULT waveOutOpen(ref IntPtr hWaveOut, uint uDeviceID, ref WAVEFORMATEX lpFormat, WaveOutProc dwCallback, IntPtr dwInstance, uint dwFlags);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern MMRESULT waveOutClose(IntPtr hwo);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern MMRESULT waveOutPrepareHeader(IntPtr hWaveOut, IntPtr lpWaveOutHdr, int uSize);
[DllImport("winmm.dll")]
public static extern MMRESULT waveOutUnprepareHeader(IntPtr hWaveOut, IntPtr lpWaveOutHdr, uint cbwh);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern MMRESULT waveOutReset(IntPtr hwo);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern MMRESULT waveOutWrite(IntPtr hwo, IntPtr pwh, uint cbwh);
//callbacks
public delegate void WaveOutProc(IntPtr hdrvr, WaveOutMessage uMsg, int dwUser, IntPtr wavhdr, int dwParam2);
private IntPtr WaveOutHandle;
private IntPtr WaveHandlePtr;
private WAVEFORMATEX WaveFormatEx;
private byte[] WaveDataBuffer;
private FileStream fs;
private BinaryReader br;
private WaveOutProc m_BufferProc;
private int BufferSize=0;
public iPentecLowLevelWavePlay()
{
WaveOutHandle = IntPtr.Zero;
}
public void OpenFile(string FileName)
{
fs = new FileStream(FileName, FileMode.Open);
br = new BinaryReader(fs, Encoding.ASCII);
char[] readbuf;
readbuf = br.ReadChars(4);
if (readbuf.ToString() == "RIFF") {
}
br.ReadBytes(4);//空
readbuf = br.ReadChars(4);
if (readbuf.ToString() == "WAVE") {
}
readbuf = br.ReadChars(4);
if (readbuf.ToString() == "fmt ") {
}
// 'fmt 'チャンクのサイズ読み取り
int chank = br.ReadInt32();
// ヘッダを読み取り waveformatex構造体に代入
byte[] WaveFormatExBuffer = br.ReadBytes(Marshal.SizeOf(typeof(WAVEFORMATEX)) - Marshal.SizeOf(typeof(ushort)) * 2);
GCHandle gch = GCHandle.Alloc(WaveFormatExBuffer, GCHandleType.Pinned);
WaveFormatEx = (WAVEFORMATEX)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(WAVEFORMATEX));
gch.Free();
WaveFormatEx.cbSize = (ushort)Marshal.SizeOf(typeof(WAVEFORMATEX));
readbuf = br.ReadChars(4);
if (readbuf.ToString() == "fact") {
//'fact'チャンクのサイズの読み込み
br.ReadBytes(4);
br.ReadBytes(1);
br.ReadBytes(4);
}
if (readbuf.ToString() == "data") {
}
BufferSize = br.ReadInt32();
OpenWaveOutHandle();
WaveHandlePtr = PrepareHeader();
br.Close();
fs.Close();
}
public void CloseFile()
{
Marshal.FreeHGlobal(WaveHandlePtr);
if (WaveOutHandle != IntPtr.Zero) {
waveOutReset(WaveOutHandle);
waveOutUnprepareHeader(WaveOutHandle, WaveHandlePtr, (uint)Marshal.SizeOf(typeof(WAVEHDR)));
waveOutClose(WaveOutHandle);
}
}
public void Play()
{
MMRESULT rc = waveOutWrite(WaveOutHandle, WaveHandlePtr, (uint)Marshal.SizeOf(typeof(WAVEHDR)));
}
public void Stop()
{
if (WaveOutHandle != IntPtr.Zero) {
MMRESULT mr = waveOutReset(WaveOutHandle);
if (mr == MMRESULT.MMSYSERR_NOERROR) {
}
mr = waveOutClose(WaveOutHandle);
if (mr == MMRESULT.MMSYSERR_NOERROR) {
}
WaveOutHandle = IntPtr.Zero;
}
}
public void OpenWaveOutHandle()
{
if (WaveOutHandle != IntPtr.Zero) {
MMRESULT mr = waveOutReset(WaveOutHandle);
if (mr == MMRESULT.MMSYSERR_NOERROR) {
}
mr = waveOutClose(WaveOutHandle);
if (mr == MMRESULT.MMSYSERR_NOERROR) {
}
WaveOutHandle = IntPtr.Zero;
}
m_BufferProc = new WaveOutProc(WaveOutProcCallback);
MMRESULT rc = waveOutOpen(ref WaveOutHandle, WAVE_MAPPER, ref WaveFormatEx, m_BufferProc, IntPtr.Zero, CALLBACK_FUNCTION);
}
//データをフォーマットヘッダに組み込み ヘッダの最終構成を行う
public IntPtr PrepareHeader()
{
WAVEHDR WaveHandle = new WAVEHDR();
IntPtr WaveHdrPtr = new IntPtr();
WaveHdrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(WaveHandle));
WaveDataBuffer = br.ReadBytes((int)BufferSize);
IntPtr parray = Marshal.AllocCoTaskMem(WaveDataBuffer.Length);
Marshal.Copy(WaveDataBuffer, 0, parray, WaveDataBuffer.Length);
WaveHandle.lpData = parray;
WaveHandle.dwBufferLength = (uint)WaveDataBuffer.Length;
WaveHandle.dwFlags = 0;
Marshal.StructureToPtr(WaveHandle, WaveHdrPtr, true);
MMRESULT mmrc = waveOutPrepareHeader(WaveOutHandle, WaveHdrPtr, Marshal.SizeOf(typeof(WAVEHDR)));
return WaveHdrPtr;
}
public void WaveOutProcCallback(IntPtr hdrvr, WaveOutMessage uMsg, int dwUser, IntPtr wavhdr, int dwParam2)
{
switch (uMsg) {
case WaveOutMessage.Close:
break;
case WaveOutMessage.Done:
break;
case WaveOutMessage.Open:
break;
}
}
}
}
解説
WAVの再生に関しては、
こちらの記事を参照してください。
今回は、ファイルを開いた際にWAVファイルのすべてのデータをバッファに読み込んでしまうため、WaveOutProcCallbackでの処理をしていません。
実行結果
プロジェクトを実行します。下図のウィンドウが表示されます。
[FileOpen]ボタンをクリックします。ファイルを開くダイアログボックスが表示されます。WAVファイルを選択して開きます。
[Play]ボタンをクリックします。先ほど開いたWAVファイルが再生されます。
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用