📄 waveout.cs
字号:
using System;
using System.Runtime.InteropServices;
using System.IO;
using Microsoft.WindowsCE.Forms;
using System.Threading;
using System.Windows.Forms;
using System.Diagnostics;
namespace NiceTracker.Libraries
{
/// <summary>
/// Encapsulates Waveform Audio Interface playback functions and provides a simple
/// interface for playing audio.
/// </summary>
public class WaveOut : IDisposable
{
/// <summary>
/// Supplies an inteface for loading and playing a .wav file.
/// </summary>
public class WaveFile : IDisposable
{
/// <summary>
/// Hardware interface instance for this wave file.
/// </summary>
protected IntPtr m_hwo = IntPtr.Zero;
/// <summary>
/// Instance of WAVEFORMATEX associated with this file.
/// </summary>
protected Wave.WAVEFORMATEX m_wfmt = null;
/// <summary>
/// Buffers used to read and play wave data.
/// </summary>
protected Wave.WAVEHDR[] m_whdr = null;
/// <summary>
/// Size of each buffer used for playback
/// </summary>
protected int m_bufferSize;
/// <summary>
/// Number of buffers needed for playback
/// </summary>
protected int m_numBlocks;
/// <summary>
/// Current buffer being read
/// </summary>
protected int m_curBlock;
/// <summary>
/// BinaryRead used to access the streaming audio file.
/// </summary>
protected BinaryReader m_rdr = null;
/// <summary>
/// Determines if file is done playing.
/// </summary>
public bool Done { get { return !m_playing; } }
protected bool m_playing = false;
/// <summary>
/// Length, in milliseconds, of the audio file.
/// </summary>
public uint Milliseconds { get { return 1000 * m_dataLength / m_wfmt.nAvgBytesPerSec; } }
protected uint m_dataLength;
/// <summary>
/// Create an instance of a wave file. Allocate the two buffers that will
/// be used for streaming audio.
/// </summary>
public WaveFile()
{
m_whdr = new Wave.WAVEHDR[2];
}
/// <summary>
/// Play a wave file.
/// </summary>
/// <param name="curDevice">Hardware device to use for playback</param>
/// <param name="fileName">Name of file to play</param>
/// <param name="hwnd">Handle to a message window to use for messaging</param>
/// <param name="bufferSize">Size of streaming buffers, a 0 value specifies
/// that the buffer should be created big enough to fit the entire file</param>
/// <param name="volLeft">Left channel volume level</param>
/// <param name="volRight">Right channel volume level</param>
/// <returns>MMSYSERR.NOERROR if successful</returns>
public Wave.MMSYSERR Play(uint curDevice, String fileName, IntPtr hwnd, int bufferSize, ushort volLeft, ushort volRight)
{
if (m_playing)
return Wave.MMSYSERR.NOERROR;
if (!File.Exists(fileName))
return Wave.MMSYSERR.ERROR;
FileInfo fi = new FileInfo(fileName);
if ((fi.Attributes & FileAttributes.ReadOnly) != 0)
fi.Attributes -= FileAttributes.ReadOnly;
FileStream strm = new FileStream(fileName, FileMode.Open);
if (strm == null)
return Wave.MMSYSERR.ERROR;
m_rdr = new BinaryReader(strm);
if (m_rdr == null)
return Wave.MMSYSERR.ERROR;
m_wfmt = new Wave.WAVEFORMATEX();
m_wfmt.SeekTo(strm);
// Read in the WAVEFORMATEX structure and attempt to open the
// device for playback.
m_wfmt.Read(m_rdr);
Wave.MMSYSERR result = waveOutOpen(ref m_hwo, curDevice, m_wfmt, hwnd, 0, Wave.CALLBACK_WINDOW);
if (result != Wave.MMSYSERR.NOERROR)
return result;
m_dataLength = (uint)(m_rdr.BaseStream.Length - Wave.WAVEFORMATEX.WF_OFFSET_DATA);
if (bufferSize == 0)
m_bufferSize = (int)m_dataLength;
else
m_bufferSize = bufferSize / 2;
if (m_bufferSize % m_wfmt.nBlockAlign != 0)
m_bufferSize += m_wfmt.nBlockAlign - (m_bufferSize % m_wfmt.nBlockAlign);
// Determine the number of buffer reads required to play the entire
// file
m_numBlocks = (int)(m_dataLength / m_bufferSize);
if ((m_numBlocks * m_bufferSize) < m_dataLength)
m_numBlocks++;
m_whdr[0] = new Wave.WAVEHDR();
m_whdr[1] = new Wave.WAVEHDR();
// Read in the first buffer
result = ReadBuffer(0);
if (result != Wave.MMSYSERR.NOERROR)
return result;
// If the entire file fits in the buffer then close the file
if (m_numBlocks == 1)
{
m_rdr.BaseStream.Close();
m_rdr.Close();
m_rdr = null;
}
SetVolume(volLeft, volRight);
// Start playback of the first buffer
result = waveOutWrite(m_hwo, m_whdr[0], (uint)Marshal.SizeOf(m_whdr[0]));
if (result != Wave.MMSYSERR.NOERROR)
return result;
m_curBlock = 0;
// Create the second buffer. If the audio is being streamed, this will
// be the next audio block, otherwise it will be padding
Thread loadThread = new Thread(new ThreadStart(LoadBuffer));
loadThread.Start();
m_playing = true;
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Read in the specified buffer.
/// </summary>
/// <param name="bufIndex">Index of buffer to use for the read</param>
/// <returns>MMSYSERR.NOERROR if successful</returns>
protected Wave.MMSYSERR ReadBuffer(int bufIndex)
{
uint readLength = (uint)m_bufferSize;
if (bufIndex < m_numBlocks)
{
uint remainingDataLength = (uint)(m_rdr.BaseStream.Length - m_rdr.BaseStream.Position);
if (m_bufferSize > remainingDataLength)
readLength = remainingDataLength;
}
// Read in the next block of data
Wave.MMSYSERR result = m_whdr[bufIndex].Read(m_rdr, readLength, m_wfmt.nBlockAlign);
if (result != Wave.MMSYSERR.NOERROR)
return result;
// If the header is not prepared then prepare it
if ((m_whdr[bufIndex].dwFlags & Wave.WAVEHDR.WHDR_PREPARED) == 0)
{
return waveOutPrepareHeader(m_hwo, m_whdr[bufIndex], (uint)Marshal.SizeOf(m_whdr[bufIndex]));
}
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Create an empty buffer to append to the end of the sound. This protects
/// the playback because the system will sometimes continue reading after the
/// BlockDone method is called.
/// </summary>
/// <param name="bufIndex">Index of buffer to be created</param>
/// <returns>MMSYSERR.NOERROR is successful</returns>
protected Wave.MMSYSERR CreateBuffer(int bufIndex)
{
Wave.MMSYSERR result = m_whdr[bufIndex].Init((uint)m_bufferSize, true);
if (result != Wave.MMSYSERR.NOERROR)
return result;
if ((m_whdr[bufIndex].dwFlags & Wave.WAVEHDR.WHDR_PREPARED) == 0)
{
return waveOutPrepareHeader(m_hwo, m_whdr[bufIndex], (uint)Marshal.SizeOf(m_whdr[bufIndex]));
}
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Load a buffer. If there are blocks left to be read, then data will
/// be read into the buffer that is not being played. Otherwise, the buffer
/// will be filled with 0's for padding to detect the end of playback.
/// </summary>
public void LoadBuffer()
{
int readBuffer = (m_curBlock + 3) % 2;
lock(m_whdr[readBuffer])
{
if (m_curBlock == m_numBlocks - 1)
CreateBuffer(readBuffer);
else
ReadBuffer(readBuffer);
waveOutWrite(m_hwo, m_whdr[readBuffer], (uint)Marshal.SizeOf(m_whdr[readBuffer]));
}
}
/// <summary>
/// Called by the MessageWindow when the buffer currently being played has
/// finished. This method starts the loading of the next block on a
/// separate thread. If the current block is the last one then playback is
/// stopped.
/// </summary>
public void BlockDone()
{
m_curBlock++;
if (m_curBlock < m_numBlocks)
{
Debug.Assert((m_whdr[(m_curBlock + 2) % 2].dwFlags & Wave.WAVEHDR.WHDR_INQUEUE) != 0,
"ERROR: A sound block finished before the subsequent block was written.");
Thread loadThread = new Thread(new ThreadStart(LoadBuffer));
loadThread.Start();
}
else if (m_curBlock == m_numBlocks)
{
Stop();
}
}
/// <summary>
/// Stop playing the current file and clean up all resources.
/// </summary>
public void Stop()
{
waveOutReset(m_hwo);
m_playing = false;
if (m_rdr != null)
{
m_rdr.BaseStream.Close();
m_rdr.Close();
m_rdr = null;
}
for (int i = 0; i < 2; i++)
{
if (m_whdr[i] != null)
{
lock(m_whdr[i])
{
if (m_hwo != IntPtr.Zero)
waveOutUnprepareHeader(m_hwo, m_whdr[i], (uint)Marshal.SizeOf(m_whdr[i]));
m_whdr[i].Dispose();
m_whdr[i] = null;
}
}
}
if (m_hwo != IntPtr.Zero)
waveOutClose(m_hwo);
m_hwo = IntPtr.Zero;
m_wfmt = null;
}
/// <summary>
/// Resume the playback of a paused sound.
/// </summary>
/// <returns>MMSYSERR.NOERROR if successful</returns>
public Wave.MMSYSERR Resume()
{
return waveOutRestart(m_hwo);
}
/// <summary>
/// Pause the playback of a sound.
/// </summary>
/// <returns>MMSYSERR.NOERROR if successful</returns>
public Wave.MMSYSERR Pause()
{
return waveOutPause(m_hwo);
}
/// <summary>
/// Get the volume of this sound.
/// </summary>
/// <param name="volLeft">Left channel volume level</param>
/// <param name="volRight">Right channel volume level</param>
/// <returns>MMSYSERR.NOERROR if successful</returns>
public Wave.MMSYSERR GetVolume(ref ushort volLeft, ref ushort volRight)
{
uint vol = 0;
Wave.MMSYSERR result = waveOutGetVolume(m_hwo, ref vol);
if (result != Wave.MMSYSERR.NOERROR)
return result;
volLeft = (ushort)(vol & 0x0000ffff);
volRight = (ushort)(vol >> 16);
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Sets the volume of this sound.
/// </summary>
/// <param name="volLeft">Left channel volume level</param>
/// <param name="volRight">Right channel volume level</param>
/// <returns>MMSYSERR.NOERROR if successful</returns>
public Wave.MMSYSERR SetVolume(ushort volLeft, ushort volRight)
{
uint vol = ((uint)volLeft & 0x0000ffff) | ((uint)volRight << 16);
return waveOutSetVolume(m_hwo, vol);
}
/// <summary>
/// Clean up all resources.
/// </summary>
public void Dispose()
{
Stop();
}
}
/// <summary>
/// Defines the MessageWindow used to receive messages from the audio
/// system.
/// </summary>
public class SoundMessageWindow : MessageWindow
{
public const int MM_WOM_OPEN = 0x03BB;
public const int MM_WOM_CLOSE = 0x03BC;
public const int MM_WOM_DONE = 0x03BD;
// Instance of a playback interface
protected WaveOut m_wo = null;
public SoundMessageWindow(WaveOut wo)
{
m_wo = wo;
}
protected override void WndProc(ref Message msg)
{
switch (msg.Msg)
{
// When this message is encountered, a block is
// done playing, so notify the WaveOut instance.
case MM_WOM_DONE:
m_wo.BlockDone(msg.WParam);
break;
}
base.WndProc(ref msg);
}
}
/// <summary>
/// Maintain an instance of a MessageWindow that handles audio messages.
/// </summary>
protected SoundMessageWindow m_msgWindow = null;
/// <summary>
/// An instance of WaveFile used as the source for playing audio.
/// </summary>
protected WaveFile m_file = null;
/// <summary>
/// Create an instance of WaveOut.
/// </summary>
public WaveOut()
{
m_msgWindow = new SoundMessageWindow(this);
m_file = new WaveFile();
}
/// <summary>
/// Determine the number of available playback devices.
/// </summary>
/// <returns>Number of output devices</returns>
public uint NumDevices()
{
return (uint)waveOutGetNumDevs();
}
/// <summary>
/// Get the name of the specified playback device.
/// </summary>
/// <param name="deviceId">ID of the device</param>
/// <param name="prodName">Destination string assigned the name</param>
/// <returns>MMSYSERR.NOERROR if successful</returns>
public Wave.MMSYSERR GetDeviceName(uint deviceId, ref string prodName)
{
WAVEOUTCAPS caps = new WAVEOUTCAPS();
Wave.MMSYSERR result = waveOutGetDevCaps(deviceId, caps, caps.Size);
if (result != Wave.MMSYSERR.NOERROR)
return result;
prodName = caps.szPname;
return Wave.MMSYSERR.NOERROR;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -