|
||||
|
vbAccelerator - Contents of code file: WaveStreamWriter.csThis file is part of the download WaveStream CSharp, which is described in the article WaveStreamReader and WaveStreamWriter. using System; using System.IO; using System.Runtime.InteropServices; namespace vbAccelerator.Audio.WaveStream { /// <summary> /// A <c>Stream</c> implementation which wraps the Windows Multi-media /// IO to write a Wave file. /// </summary> public class WaveStreamWriter : Stream { private string waveFile; private IntPtr hMmio = IntPtr.Zero; private bool disposed = false; private WinMMInterop.WAVEFORMATEX format; private int dataOffset = 0; private int audioLength = 0; private WinMMInterop.MMCKINFO mmckInfoChild; private WinMMInterop.MMCKINFO mmckInfoParent; /// <summary> /// Default constructor: 16bit, 44.1kHz, Stereo. No file is created. /// </summary> public WaveStreamWriter() : base() { format.cbSize = 0; format.nChannels = 2; format.nSamplesPerSec = 44100; format.wBitsPerSample = 16; format.nBlockAlign = 4; format.wFormatTag = 1; } /// <summary> /// Constructor: 16bit, 44.1kHz, Stereo, file is created. /// </summary> /// <param name="file">File name of the wave file to write</param> public WaveStreamWriter(string file) : this() { Filename = file; } /// <summary> /// Constructor: 16bit, Stereo, file is created. /// </summary> /// <param name="file">File name of the wave file to write</param> /// <param name="samplingFrequency">Sampling frequency</param> public WaveStreamWriter(string file, int samplingFrequency) : this() { SamplingFrequency = samplingFrequency; Filename = file; } /// <summary> /// Constructor: 16bit, file is created. /// </summary> /// <param name="file">File name of the wave file to write</param> /// <param name="samplingFrequency">Sampling frequency</param> /// <param name="channels">Number of audio channels</param> public WaveStreamWriter(string file, int samplingFrequency, short channels) : this() { format.nSamplesPerSec = samplingFrequency; format.nChannels = channels; Filename = file; } /// <summary> /// Constructor: file is created. /// </summary> /// <param name="file">File name of the wave file to write</param> /// <param name="samplingFrequency">Sampling frequency</param> /// <param name="channels">Number of audio channels</param> /// <param name="bitsPerSample">Number of bits per sample</param> public WaveStreamWriter(string file, int samplingFrequency, short channels, short bitsPerSample) : this() { format.nSamplesPerSec = samplingFrequency; format.nChannels = channels; format.wBitsPerSample = bitsPerSample; Filename = file; } /// <summary> /// Destructor: ensures that the wave file handle is closed. /// </summary> ~WaveStreamWriter() { Dispose(false); } /// <summary> /// Clears up resources associated with this class. /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Gets the Multi-media IO handle to the wave file. /// </summary> protected virtual IntPtr Handle { get { return hMmio; } } /// <summary> /// Clears up resources associated with this class. /// </summary> /// <param name="disposing"><code>true</code> if disposing from the <c>Dispose</c> /// method, otherwise <c>false</c>.</param> protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // nothing to do } CloseWaveFile(); disposed = true; } } public override void Flush() { // } /// <summary> /// Gets/sets the wave file name. /// </summary> public string Filename { get { return waveFile; } set { if (hMmio != IntPtr.Zero) { CloseWaveFile(); } waveFile = value; CreateWaveFile(); } } /// <summary> /// Gets/sets the number of audio channels in the file. /// </summary> /// <exception cref="InvalidOperationException">If attempt made to change the value when /// a file is open. Must set this prior to setting the filename.</exception> public short Channels { get { return format.nChannels; } set { if (hMmio != IntPtr.Zero) { throw new InvalidOperationException("Cannot change number of audio channels on an open file."); } format.nChannels = value; } } /// <summary> /// Gets/sets the sample frequency of the file. /// </summary> /// <exception cref="InvalidOperationException">If attempt made to change the value when /// a file is open. Must set this prior to setting the filename.</exception> public int SamplingFrequency { get { return format.nSamplesPerSec; } set { if (hMmio != IntPtr.Zero) { throw new InvalidOperationException("Cannot change sampling frequency on an open file."); } format.nSamplesPerSec = value; } } /// <summary> /// Gets/sets the number of bits per sample in the wave file. /// </summary> /// <exception cref="InvalidOperationException">If attempt made to change the value when /// a file is open. Must set this prior to setting the filename.</exception> public short BitsPerSample { get { return format.wBitsPerSample; } set { if (hMmio != IntPtr.Zero) { throw new InvalidOperationException("Cannot change bits/sample on an open file."); } format.wBitsPerSample = value; } } /// <summary> /// Gets whether the stream can be read or not (true whenever a wave file /// is open). /// </summary> public override bool CanRead { get { return (hMmio != IntPtr.Zero); } } /// <summary> /// Gets whether the stream is seekable or not (true whenever a wave file /// is open). /// </summary> public override bool CanSeek { get { return (hMmio != IntPtr.Zero); } } /// <summary> /// Gets whether the stream can be written or not (true whenever a wave file /// is open). /// </summary> public override bool CanWrite { get { return (hMmio != IntPtr.Zero); } } /// <summary> /// Gets the length of this wave file, in bytes. /// </summary> public override long Length { get { return audioLength; } } /// <summary> /// Throws an exception; setting the length of the stream is meaningless /// </summary> /// <exception cref="InvalidOperationException">Thrown exception</exception> public override void SetLength(long length) { throw new InvalidOperationException( "This class can only read files. Use the WaveStreamWriter class to write files."); } /// <summary> /// Gets/sets the position within the wave file. /// </summary> public override long Position { get { return 0; } set { Seek(value, SeekOrigin.Begin); } } /// <summary> /// Reads <c>count</c> bytes into the buffer. /// </summary> /// <param name="buffer">Buffer to read into</param> /// <param name="count">Number of bytes to read</param> /// <returns>Number of bytes read.</returns> public virtual int Read ( byte[] buffer, int count ) { return Read(buffer, 0, count); } /// <summary> /// Reads <c>count</c> bytes into the buffer. /// </summary> /// <param name="buffer">Buffer to read into</param> /// <param name="count">Number of bytes to read</param> /// <param name="offset">Offset from the current file position to start reading from</param> /// <returns>Number of bytes read.</returns> public override int Read ( byte[] buffer , int offset , int count ) { if (hMmio == IntPtr.Zero) { throw new InvalidOperationException("No wave data is open"); } if (offset != 0) { Seek((long) offset, SeekOrigin.Current); } GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr ptrBuffer = handle.AddrOfPinnedObject(); int dataRemaining = (dataOffset + audioLength - WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR)); int read = 0; if (count < dataRemaining) { read = WinMMInterop.mmioRead(hMmio, ptrBuffer, count); } else if (dataRemaining > 0) { read = WinMMInterop.mmioRead(hMmio, ptrBuffer, dataRemaining); } if (handle.IsAllocated) { handle.Free(); } return read; } /// <summary> /// Reads <c>count</c> shorts into the buffer. /// </summary> /// <param name="buffer">Buffer to read into</param> /// <param name="count">Number of shorts to read</param> /// <returns>Number of bytes read.</returns> public virtual int Read (short[] buffer, int count) { return Read(buffer, 0, count); } /// <summary> /// Reads <c>count</c> shorts into the buffer. /// </summary> /// <param name="buffer">Buffer to read into</param> /// <param name="count">Number of shorts to read</param> /// <param name="offset">Offset in shorts (2 bytes) from the current file position to start /// reading from</param> /// <returns>Number of bytes read.</returns> public virtual int Read ( short[] buffer, int offset, int count ) { if (hMmio == IntPtr.Zero) { throw new InvalidOperationException("No wave data is open"); } if (offset != 0) { Seek((long) (offset * 2), SeekOrigin.Current); } GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr ptrBuffer = handle.AddrOfPinnedObject(); int dataRemaining = (dataOffset + audioLength - WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR)) / 2; int read = 0; if (count < dataRemaining) { read = WinMMInterop.mmioRead(hMmio, ptrBuffer, count * 2); } else if (dataRemaining > 0) { read = WinMMInterop.mmioRead(hMmio, ptrBuffer, dataRemaining * 2); } if (handle.IsAllocated) { handle.Free(); } return read; } /// <summary> /// Writes <c>count</c> bytes from the buffer. /// </summary> /// <param name="buffer">Buffer to read from</param> /// <param name="count">Number of bytes to write</param> public virtual void Write( byte[] buffer, int count) { Write(buffer, 0, count); } /// <summary> /// Writes <c>count</c> bytes from the buffer. /// </summary> /// <param name="buffer">Buffer to read from</param> /// <param name="count">Number of bytes to write</param> /// <param name="offset">Offset from the current file position to start writing</param> public override void Write( byte[] buffer, int offset, int count) { if (hMmio == IntPtr.Zero) { throw new InvalidOperationException("No wave file is open"); } if (offset != 0) { Seek((long) offset, SeekOrigin.Current); } GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr ptrBuffer = handle.AddrOfPinnedObject(); int write = WinMMInterop.mmioWrite(hMmio, ptrBuffer, count); if (write != count) { throw new IOException(String.Format( "Data truncation: only wrote {0} of {1} requested bytes", write, count)); } if (handle.IsAllocated) { handle.Free(); } } /// <summary> /// Writes <c>count</c> shorts from the buffer. /// </summary> /// <param name="buffer">Buffer to read from</param> /// <param name="count">Number of shorts to write</param> public virtual void Write( short[] buffer, int count) { Write(buffer, 0, count); } /// <summary> /// Writes <c>count</c> shorts from the buffer. /// </summary> /// <param name="buffer">Buffer to read from</param> /// <param name="count">Number of shorts to write</param> /// <param name="offset">Offset from the current file position to start writing</param> /// <returns>Number of shorts write.</returns> public virtual void Write( short[] buffer, int offset, int count) { if (hMmio == IntPtr.Zero) { throw new InvalidOperationException("No wave file is open"); } if (offset != 0) { Seek((long) (offset * 2), SeekOrigin.Current); } GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr ptrBuffer = handle.AddrOfPinnedObject(); int write = WinMMInterop.mmioWrite(hMmio, ptrBuffer, count * 2); if (write != (count * 2)) { throw new IOException(String.Format( "Data truncation: only wrote {0} of {1} requested bytes", write, count)); } if (handle.IsAllocated) { handle.Free(); } } /// <summary> /// Seeks to the specified position in the stream, in bytes /// </summary> /// <param name="position">Position to seek to</param> /// <param name="origin">Specifies the starting postion of the seek</param> public override long Seek(long position, SeekOrigin origin) { if (hMmio == IntPtr.Zero) { throw new InvalidOperationException("No wave file is open"); } int offset = (int) position; int mmOrigin = WinMMInterop.SEEK_CUR; if (origin == SeekOrigin.Begin) { offset += dataOffset; mmOrigin = WinMMInterop.SEEK_SET; } else if (origin == SeekOrigin.End) { mmOrigin = WinMMInterop.SEEK_END; } int result = WinMMInterop.mmioSeek(hMmio, offset, mmOrigin); if (result == -1) { throw new WaveStreamException( String.Format("Failed to seek to position {0} in file", position)); } return (long) result; } private void CreateWaveFile() { CloseWaveFile(); hMmio = WinMMInterop.mmioOpen(waveFile, IntPtr.Zero, WinMMInterop.MMIO_ALLOCBUF | WinMMInterop.MMIO_READWRITE | WinMMInterop.MMIO_CREATE); if (hMmio == IntPtr.Zero) { throw new IOException( String.Format("Could not open file {0}", waveFile)); } CreateWaveFormatHeader(); } private void CreateWaveFormatHeader() { int result = 0; // Set derived fields for PCM format.nBlockAlign = (short) ((format.nChannels * format.wBitsPerSample) / 8); format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; // Write the WAVE header: mmckInfoParent = new WinMMInterop.MMCKINFO(); mmckInfoParent.fccType = WinMMInterop.mmioStringToFOURCC("WAVE", 0); result = WinMMInterop.mmioCreateChunk(hMmio, ref mmckInfoParent, WinMMInterop.MMIO_CREATERIFF); if (result != WinMMInterop.MMSYSERR_NOERROR) { CloseWaveFile(); throw new WaveStreamException("Could not write the WAVE RIFF header chunk to the file."); } // Create the format chunk and write the format out: mmckInfoChild = new WinMMInterop.MMCKINFO(); mmckInfoChild.ckid = WinMMInterop.mmioStringToFOURCC("fmt", 0); mmckInfoChild.ckSize = Marshal.SizeOf(typeof(WinMMInterop.WAVEFORMATEX)); result = WinMMInterop.mmioCreateChunk(hMmio, ref mmckInfoChild, 0); if (result != WinMMInterop.MMSYSERR_NOERROR) { CloseWaveFile(); throw new WaveStreamException("Could not write the 'fmt' header chunk to the file."); } int size = WinMMInterop.mmioWriteWaveFormat(hMmio, ref format, mmckInfoChild.ckSize); if (size != mmckInfoChild.ckSize) { CloseWaveFile(); throw new WaveStreamException("Could not write the format information into the 'fmt' header chunk of the file."); } // Back out to the WAVE header: result = WinMMInterop.mmioAscend(hMmio, ref mmckInfoChild, 0); if (result != WinMMInterop.MMSYSERR_NOERROR) { CloseWaveFile(); throw new WaveStreamException("Could not ascend out of 'fmt' header chunk."); } // Create the data chunk: mmckInfoChild.ckid = WinMMInterop.mmioStringToFOURCC("data", 0); result = WinMMInterop.mmioCreateChunk(hMmio, ref mmckInfoChild, 0); if (result != WinMMInterop.MMSYSERR_NOERROR) { CloseWaveFile(); throw new WaveStreamException("Could not create the 'data' chunk for the audio data."); } // Stay in the data chunk for writing. dataOffset = WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR); } /// <summary> /// Closes the wave file. /// </summary> public void CloseWaveFile() { if (hMmio != IntPtr.Zero) { int result; // Ascend the output file out of the output chunk: result = WinMMInterop.mmioAscend(hMmio, ref mmckInfoChild, 0); // Ascend the output file out of the 'RIFF' chunk: result = WinMMInterop.mmioAscend(hMmio, ref mmckInfoParent, 0); // Close the file WinMMInterop.mmioClose(hMmio, 0); hMmio = IntPtr.Zero; audioLength = 0; } } } }
|
|||
|