|
||||
|
vbAccelerator - Contents of code file: WaveStreamWriter.vbThis file is part of the download WaveStream VB, which is described in the article WaveStreamReader and WaveStreamWriter. Imports System.IO Imports System.Runtime.InteropServices Public Class WaveStreamWriter Inherits Stream Private waveFile As String Private hMmio As IntPtr = IntPtr.Zero Private disposed As Boolean = False Private format As WinMMInterop.WAVEFORMATEX Private dataOffset As Integer = 0 Private audioLength As Integer = 0 Private mmckInfoChild As WinMMInterop.MMCKINFO Private mmckInfoParent As WinMMInterop.MMCKINFO ''' <summary> ''' Default constructor: 16bit, 44.1kHz, Stereo. No file is created. ''' </summary> Public Sub New() MyBase.New() format.cbSize = 0 format.nChannels = 2 format.nSamplesPerSec = 44100 format.wBitsPerSample = 16 format.nBlockAlign = 4 format.wFormatTag = 1 End Sub ''' <summary> ''' Constructor: 16bit, 44.1kHz, Stereo, file is created. ''' </summary> ''' <param name="file">File name of the wave file to write</param> Public Sub New(ByVal file As String) Me.New() Filename = file End Sub ''' <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 Sub New(ByVal file As String, ByVal samplingFrequency As Integer) Me.New() samplingFrequency = samplingFrequency Filename = file End Sub ''' <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 Sub New(ByVal file As String, ByVal samplingFrequency As Integer, ByVal channels As Short) Me.New() format.nSamplesPerSec = samplingFrequency format.nChannels = channels Filename = file End Sub ''' <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 Sub New(ByVal file As String, ByVal samplingFrequency As Integer, ByVal channels As Short, ByVal bitsPerSample As Short) Me.New() format.nSamplesPerSec = samplingFrequency format.nChannels = channels format.wBitsPerSample = bitsPerSample Filename = file End Sub ''' <summary> ''' Destructor: ensures that the wave file handle is closed. ''' </summary> Protected Overrides Sub Finalize() DisposeResources(False) End Sub ''' <summary> ''' Clears up resources associated with this class. ''' </summary> Public Overloads Sub Dispose() DisposeResources(True) GC.SuppressFinalize(Me) End Sub ''' <summary> ''' Gets the Multi-media IO handle to the wave file. ''' </summary> Protected Overridable ReadOnly Property Handle() As IntPtr Get Return hMmio End Get End Property ''' <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 Overridable Sub DisposeResources(ByVal disposing As Boolean) If Not (disposed) Then If (disposing) Then 'nothing to do End If CloseWaveFile() disposed = True End If End Sub ''' <summary> ''' Gets/sets the wave file name. ''' </summary> Public Property Filename() As String Get Return waveFile End Get Set(ByVal Value As String) If Not (hMmio.Equals(IntPtr.Zero)) Then CloseWaveFile() End If waveFile = Value CreateWaveFile() End Set End Property ''' <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 Property Channels() As Short Get Return format.nChannels End Get Set(ByVal Value As Short) If Not (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("Cannot change number of audio channels on an open file.") End If format.nChannels = Value End Set End Property ''' <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 Property SamplingFrequency() As Integer Get Return format.nSamplesPerSec End Get Set(ByVal Value As Integer) If Not (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("Cannot change sampling frequency on an open file.") End If format.nSamplesPerSec = Value End Set End Property ''' <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 Property BitsPerSample() As Short Get Return format.wBitsPerSample End Get Set(ByVal Value As Short) If Not (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("Cannot change bits/sample on an open file.") End If format.wBitsPerSample = Value End Set End Property Public Overrides Sub Flush() ' ' End Sub ''' <summary> ''' Gets whether the stream can be read or not (true whenever a wave file ''' is open). ''' </summary> Public Overrides ReadOnly Property CanRead() As Boolean Get Return Not (hMmio.Equals(IntPtr.Zero)) End Get End Property ''' <summary> ''' Gets whether the stream is seekable or not (true whenever a wave file ''' is open). ''' </summary> Public Overrides ReadOnly Property CanSeek() As Boolean Get Return Not (hMmio.Equals(IntPtr.Zero)) End Get End Property ''' <summary> ''' Gets whether the stream can be written or not (true whenever a wave file ''' is open). ''' </summary> Public Overrides ReadOnly Property CanWrite() As Boolean Get Return Not (hMmio.Equals(IntPtr.Zero)) End Get End Property ''' <summary> ''' Gets the length of this wave file, in bytes. ''' </summary> Public Overrides ReadOnly Property Length() As Long Get Return audioLength End Get End Property ''' <summary> ''' Throws an exception setting the length of the stream is meaningless ''' </summary> ''' <exception cref="InvalidOperationException">Thrown exception</exception> Public Overrides Sub SetLength(ByVal length As Long) Throw New InvalidOperationException( _ "This class can only read files. Use the WaveStreamWriter class to write files.") End Sub ''' <summary> ''' Gets/sets the position within the wave file. ''' </summary> Public Overrides Property Position() As Long Get Return 0 End Get Set(ByVal Value As Long) Seek(Value, SeekOrigin.Begin) End Set End Property ''' <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 Overridable Overloads Function Read(ByVal buffer As Byte(), ByVal count As Integer) As Integer Return Read(buffer, 0, count) End Function ''' <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 Overloads Overrides Function Read(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) As Integer If (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("No wave data is open") End If If (offset <> 0) Then Seek(offset, SeekOrigin.Current) End If Dim handle As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned) Dim ptrBuffer As IntPtr = handle.AddrOfPinnedObject() Dim dataRemaining As Integer = (dataOffset + audioLength - _ WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR)) Dim amtRead As Integer = 0 If (count < dataRemaining) Then amtRead = WinMMInterop.mmioRead(hMmio, ptrBuffer, count) ElseIf (dataRemaining > 0) Then amtRead = WinMMInterop.mmioRead(hMmio, ptrBuffer, dataRemaining) End If If (handle.IsAllocated) Then handle.Free() End If Return amtRead End Function ''' <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 Overridable Function Read16Bit(ByVal buffer() As Short, ByVal count As Integer) As Integer Return Read16Bit(buffer, 0, count) End Function ''' <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 Overridable Function Read16Bit(ByVal buffer() As Short, ByVal offset As Integer, ByVal count As Integer) As Integer If (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("No wave data is open") End If If (offset <> 0) Then Seek((offset * 2), SeekOrigin.Current) End If Dim handle As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned) Dim ptrBuffer As IntPtr = handle.AddrOfPinnedObject() Dim dataRemaining As Integer = (dataOffset + audioLength - _ WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR)) / 2 Dim amtRead As Integer = 0 If (count < dataRemaining) Then amtRead = WinMMInterop.mmioRead(hMmio, ptrBuffer, count * 2) ElseIf (dataRemaining > 0) Then amtRead = WinMMInterop.mmioRead(hMmio, ptrBuffer, dataRemaining * 2) End If If (handle.IsAllocated) Then handle.Free() End If Return amtRead End Function ''' <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 Overridable Overloads Sub Write(ByVal buffer() As Byte, ByVal count As Integer) Write(buffer, 0, count) End Sub ''' <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 Overloads Overrides Sub Write(ByVal buffer() As Byte, ByVal offset As Integer, ByVal count As Integer) If (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("No wave file is open") End If If (offset <> 0) Then Seek(offset, SeekOrigin.Current) End If Dim handle As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned) Dim ptrBuffer As IntPtr = handle.AddrOfPinnedObject() Dim amtWrite = WinMMInterop.mmioWrite(hMmio, ptrBuffer, count) If (amtWrite <> count) Then Throw New IOException(String.Format( _ "Data truncation: only wrote {0} of {1} requested bytes", amtWrite, count)) End If If (handle.IsAllocated) Then handle.Free() End If End Sub ''' <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 Overridable Sub Write16Bit(ByVal buffer() As Short, ByVal count As Integer) Write16Bit(buffer, 0, count) End Sub ''' <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 Overridable Sub Write16Bit(ByVal buffer() As Short, ByVal offset As Integer, ByVal count As Integer) If (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("No wave file is open") End If If (offset <> 0) Then Seek((offset * 2), SeekOrigin.Current) End If Dim handle As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned) Dim ptrBuffer As IntPtr = handle.AddrOfPinnedObject() Dim amtWrite As Integer = WinMMInterop.mmioWrite(hMmio, ptrBuffer, count * 2) If (amtWrite <> (count * 2)) Then Throw New IOException(String.Format( _ "Data truncation: only wrote {0} of {1} requested bytes", amtWrite, count)) End If If (handle.IsAllocated) Then handle.Free() End If End Sub ''' <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 Overrides Function Seek(ByVal position As Long, ByVal origin As SeekOrigin) As Long If (hMmio.Equals(IntPtr.Zero)) Then Throw New InvalidOperationException("No wave file is open") End If Dim offset As Integer = position Dim mmOrigin As Integer = WinMMInterop.SEEK_CUR If (origin = SeekOrigin.Begin) Then offset += dataOffset mmOrigin = WinMMInterop.SEEK_SET ElseIf (origin = SeekOrigin.End) Then mmOrigin = WinMMInterop.SEEK_END End If Dim result As Integer = WinMMInterop.mmioSeek(hMmio, offset, mmOrigin) If (result = -1) Then Throw New WaveStreamException( _ String.Format("Failed to seek to position {0} in file", position)) End If Return result End Function Private Sub CreateWaveFile() CloseWaveFile() hMmio = WinMMInterop.mmioOpen(waveFile, IntPtr.Zero, _ WinMMInterop.MMIO_ALLOCBUF Or WinMMInterop.MMIO_READWRITE Or WinMMInterop.MMIO_CREATE) If (hMmio.Equals(IntPtr.Zero)) Then Throw New IOException( _ String.Format("Could not open file {0}", waveFile)) End If CreateWaveFormatHeader() End Sub Private Sub CreateWaveFormatHeader() Dim result As Integer = 0 '// Set derived fields for PCM format.nBlockAlign = ((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, mmckInfoParent, _ WinMMInterop.MMIO_CREATERIFF) If (result <> WinMMInterop.MMSYSERR_NOERROR) Then CloseWaveFile() Throw New WaveStreamException("Could not write the WAVE RIFF header chunk to the file.") End If ' Create the format chunk and write the format out: mmckInfoChild = New WinMMInterop.MMCKINFO() mmckInfoChild.ckid = WinMMInterop.mmioStringToFOURCC("fmt", 0) mmckInfoChild.ckSize = Marshal.SizeOf(format.GetType()) result = WinMMInterop.mmioCreateChunk(hMmio, mmckInfoChild, 0) If (result <> WinMMInterop.MMSYSERR_NOERROR) Then CloseWaveFile() Throw New WaveStreamException("Could not write the 'fmt' header chunk to the file.") End If Dim size As Integer = WinMMInterop.mmioWriteWaveFormat(hMmio, format, mmckInfoChild.ckSize) If (size <> mmckInfoChild.ckSize) Then CloseWaveFile() Throw New WaveStreamException("Could not write the format information into the 'fmt' header chunk of the file.") End If ' Back out to the WAVE header: result = WinMMInterop.mmioAscend(hMmio, mmckInfoChild, 0) If (result <> WinMMInterop.MMSYSERR_NOERROR) Then CloseWaveFile() Throw New WaveStreamException("Could not ascend out of 'fmt' header chunk.") End If ' Create the data chunk: mmckInfoChild.ckid = WinMMInterop.mmioStringToFOURCC("data", 0) result = WinMMInterop.mmioCreateChunk(hMmio, mmckInfoChild, 0) If (result <> WinMMInterop.MMSYSERR_NOERROR) Then CloseWaveFile() Throw New WaveStreamException("Could not create the 'data' chunk for the audio data.") End If ' Stay in the data chunk for writing. dataOffset = WinMMInterop.mmioSeek(hMmio, 0, WinMMInterop.SEEK_CUR) End Sub ''' <summary> ''' Closes the wave file. ''' </summary> Public Sub CloseWaveFile() If Not (hMmio.Equals(IntPtr.Zero)) Then Dim result As Integer ' Ascend the output file out of the output chunk: result = WinMMInterop.mmioAscend(hMmio, mmckInfoChild, 0) ' Ascend the output file out of the 'RIFF' chunk: result = WinMMInterop.mmioAscend(hMmio, mmckInfoParent, 0) ' Close the file WinMMInterop.mmioClose(hMmio, 0) hMmio = IntPtr.Zero audioLength = 0 End If End Sub End Class
|
|||
|