|
|
||||
|
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
|
|||
|
|
||||