📄 wavefile.cs
字号:
using System;
using System.IO;
using System.Runtime.InteropServices;
//using System.Threading;
using System.Diagnostics;
namespace GXSoundLibrary
{
/// <summary>
/// Implements the functionality required to load and play a .wav file.
/// </summary>
public class WaveFile : IDisposable
{
/// <summary>
/// Number of data buffers used for file streaming.
/// </summary>
protected const int kNumBuffers = 2;
/// <summary>
/// Handle to the waveform audio output device associated with this file.
/// </summary>
public int hwo { get { return m_hwo.ToInt32(); } }
protected IntPtr m_hwo = IntPtr.Zero;
/// <summary>
/// Instance of WAVEFORMATEX associated with this file.
/// </summary>
protected Wave.WAVEFORMATEX m_wfmt = null;
/// <summary>
/// WAVEHDR's used to stream the data from this file.
/// </summary>
protected Wave.WAVEHDR[] m_whdr = null;
/// <summary>
/// Specifies if the file is loaded.
/// </summary>
protected bool m_loaded;
/// <summary>
/// Specifies the size of the buffers used to stream this file.
/// </summary>
protected int m_bufferSize;
/// <summary>
/// Specifies the number of blocks in the data file.
/// </summary>
protected int m_numBlocks;
/// <summary>
/// Specifies the block that is currently waiting to be loaded from the
/// file.
/// </summary>
protected int m_curBlock;
/// <summary>
/// The BinaryReader being used to stream the wave file.
/// </summary>
protected BinaryReader m_rdr = null;
/// <summary>
/// Specifies if the file is done being played.
/// </summary>
public bool Done { get { return !m_playing; } }
protected bool m_playing = false;
/// <summary>
/// Specifies the length of the audio data in milliseconds.
/// </summary>
public uint Milliseconds { get { return 1000 * m_dataLength / m_wfmt.nAvgBytesPerSec; } }
/// <summary>
/// Length of the data in bytes.
/// </summary>
protected uint m_dataLength;
/// <summary>
/// Create an instance of a wave file.
/// </summary>
public WaveFile()
{
m_wfmt = new Wave.WAVEFORMATEX();
m_whdr = new Wave.WAVEHDR[kNumBuffers];
for (int i = 0; i < kNumBuffers; i++)
{
m_whdr[i] = new Wave.WAVEHDR();
}
m_numBlocks = 0;
m_curBlock = 0;
m_loaded = false;
}
/// <summary>
/// Load a .wav file and initialize data buffers for playback.
/// </summary>
/// <param name="curDevice">Current wavefore audio device</param>
/// <param name="fileName">File to be loaded</param>
/// <param name="hwnd">Handle to owner control</param>
/// <param name="bufferSize">Total size of buffers to use, if this is 0
/// then the buffer will contain the entire file (no streaming)</param>
/// <returns>Error condition of load attempt</returns>
public Wave.MMSYSERR Load(uint curDevice, string fileName, IntPtr hwnd, int bufferSize)
{
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.SeekTo(strm);
m_dataLength = m_wfmt.Read(m_rdr);
Wave.MMSYSERR result = WaveOut.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.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);
m_numBlocks = (int)(m_dataLength / m_bufferSize);
if (m_dataLength % m_bufferSize != 0)
m_numBlocks++;
result = ReadBuffer(0);
if (result != Wave.MMSYSERR.NOERROR)
return result;
if (m_numBlocks == 1)
{
m_rdr.Close();
strm.Close();
m_rdr = null;
}
m_loaded = true;
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Reload a .wav file. This method will re-use as many resources
/// as possible from a previous load. If the audio was not being
/// streamed then no reloading needs to be done and this method will
/// simply prepare the buffer for playback.
/// </summary>
/// <param name="curDevice">Current wavefore audio device</param>
/// <param name="hwnd">Handle of owner control</param>
/// <returns>Error condition of reload attempt</returns>
protected Wave.MMSYSERR Reload(uint curDevice, IntPtr hwnd)
{
Wave.MMSYSERR result = WaveOut.waveOutOpen(ref m_hwo, curDevice, m_wfmt, hwnd, 0, Wave.CALLBACK_WINDOW);
if (result != Wave.MMSYSERR.NOERROR)
return result;
for (int i = 0; i < kNumBuffers; i++)
{
m_whdr[i].Reinit();
}
if (m_numBlocks > 1)
{
m_wfmt.Skip(m_rdr.BaseStream);
result = ReadBuffer(0);
if (result != Wave.MMSYSERR.NOERROR)
return result;
}
else
{
if ((m_whdr[0].dwFlags & Wave.WHDR_PREPARED) == 0)
{
return WaveOut.waveOutPrepareHeader(m_hwo, m_whdr[0], (uint)Marshal.SizeOf(m_whdr[0]));
}
}
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Play the audio file. If the file is not already loaded then it will
/// be loaded at this time. If the file is loaded then the fileName
/// parameter is ignored.
/// </summary>
/// <param name="curDevice">Current wavefore audio device</param>
/// <param name="fileName">Name of file to be played/loaded</param>
/// <param name="hwnd">Handle to owner MessageWindow</param>
/// <param name="bufferSize">Total buffer size</param>
/// <param name="volLeft">Left speaker volume level</param>
/// <param name="volRight">Right speaker volume level</param>
/// <returns>Error condition of play attempt</returns>
public Wave.MMSYSERR Play(uint curDevice, string fileName, IntPtr hwnd, int bufferSize, ushort volLeft, ushort volRight)
{
if (m_playing)
Stop();
Wave.MMSYSERR result;
if (!m_loaded)
{
result = Load(curDevice, fileName, hwnd, bufferSize);
if (result != Wave.MMSYSERR.NOERROR)
return result;
}
else
{
result = Reload(curDevice, hwnd);
if (result != Wave.MMSYSERR.NOERROR)
return result;
}
SetVolume(volLeft, volRight);
result = WaveOut.waveOutWrite(m_hwo, m_whdr[0], (uint)Marshal.SizeOf(m_whdr[0]));
if (result != Wave.MMSYSERR.NOERROR)
return result;
//Thread loadThread = new Thread(new ThreadStart(LoadBuffer));
//loadThread.Start();
LoadBuffer();
m_curBlock = 0;
m_playing = true;
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Read a buffer from the current stream.
/// </summary>
/// <param name="bufIndex">Index of buffer to be read</param>
/// <returns>Error condition of read attempt</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;
}
Wave.MMSYSERR result = m_whdr[bufIndex].Read(m_rdr, readLength);
if (result != Wave.MMSYSERR.NOERROR)
return result;
if ((m_whdr[bufIndex].dwFlags & Wave.WHDR_PREPARED) == 0)
{
return WaveOut.waveOutPrepareHeader(m_hwo, m_whdr[bufIndex], (uint)Marshal.SizeOf(m_whdr[bufIndex]));
}
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Create an empty buffer to be used as padding at the end of streaming.
/// </summary>
/// <param name="bufIndex">Index of buffer to be used</param>
/// <returns>Error condition of create attempt</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.WHDR_PREPARED) == 0)
{
return WaveOut.waveOutPrepareHeader(m_hwo, m_whdr[bufIndex], (uint)Marshal.SizeOf(m_whdr[bufIndex]));
}
return Wave.MMSYSERR.NOERROR;
}
/// <summary>
/// Load the next buffer in the stream.
/// </summary>
public void LoadBuffer()
{
lock(this)
{
if (!m_playing)
return;
int readBuffer = (m_curBlock + 3) % 2;
if (m_curBlock == m_numBlocks - 1)
CreateBuffer(readBuffer);
else
ReadBuffer(readBuffer);
WaveOut.waveOutWrite(m_hwo, m_whdr[readBuffer], (uint)Marshal.SizeOf(m_whdr[readBuffer]));
}
}
/// <summary>
/// The currently played block has finished so load the next block into
/// the next available buffer.
/// </summary>
public void BlockDone()
{
m_curBlock++;
if (m_curBlock < m_numBlocks)
{
Debug.Assert((m_whdr[(m_curBlock + 2) % 2].dwFlags & Wave.WHDR_INQUEUE) != 0,
"ERROR: A sound block finished before the subsequent block was written.");
//Thread loadThread = new Thread(new ThreadStart(LoadBuffer));
//loadThread.Start();
LoadBuffer();
}
else if (m_curBlock == m_numBlocks)
{
Stop();
}
}
/// <summary>
/// Stop playback.
/// </summary>
public void Stop()
{
m_playing = false;
lock(this)
{
WaveOut.waveOutReset(m_hwo);
for (int i = 0; i < kNumBuffers; i++)
{
if (m_whdr[i] != null)
{
if (m_hwo != IntPtr.Zero)
WaveOut.waveOutUnprepareHeader(m_hwo, m_whdr[i], (uint)Marshal.SizeOf(m_whdr[i]));
}
}
if (m_hwo != IntPtr.Zero)
WaveOut.waveOutClose(m_hwo);
m_hwo = IntPtr.Zero;
}
}
/// <summary>
/// Free any resources allocated by the wave file.
/// </summary>
public void Dispose()
{
Stop();
if (m_rdr != null)
{
m_rdr.BaseStream.Close();
m_rdr.Close();
m_rdr = null;
}
for (int i = 0; i < kNumBuffers; i++)
{
m_whdr[i].Dispose();
}
m_wfmt = null;
}
/// <summary>
/// Resume paused playback.
/// </summary>
/// <returns>Error condition of resume attempt</returns>
public Wave.MMSYSERR Resume()
{
return WaveOut.waveOutRestart(m_hwo);
}
/// <summary>
/// Pause current playback.
/// </summary>
/// <returns>Error condition of pause attempt</returns>
public Wave.MMSYSERR Pause()
{
return WaveOut.waveOutPause(m_hwo);
}
/// <summary>
/// Get the current speaker volume.
/// </summary>
/// <param name="volLeft">Left speaker volume</param>
/// <param name="volRight">Right speaker volume</param>
/// <returns>Error condition of volume attempt</returns>
public Wave.MMSYSERR GetVolume(ref ushort volLeft, ref ushort volRight)
{
uint vol = 0;
Wave.MMSYSERR result = WaveOut.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>
/// Set the volume of this audio playback.
/// </summary>
/// <param name="volLeft">Left speaker volume</param>
/// <param name="volRight">Right speaker volume</param>
/// <returns>Error condition of volume attempt</returns>
public Wave.MMSYSERR SetVolume(ushort volLeft, ushort volRight)
{
uint vol = ((uint)volLeft & 0x0000ffff) | ((uint)volRight << 16);
return WaveOut.waveOutSetVolume(m_hwo, vol);
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -