使用 VB.NET 和托管 DirectSound 播放和可视化 WAV 文件
“Circular Buffers”是一款使用 VB.NET (VS 2003) 开发的应用程序。
引言
作为一名 VB.NET 开发者,我必须承认,在网上很难找到关于 DirectSound
的优质代码。大多数示例要么是晦涩难懂的 C/C++,要么是 C#(与 VB.NET 关系密切)。后者的优点是使用了熟悉的 .NET Framework。
本教程将引导您创建一个简单的实用程序应用程序,该程序可以显示和播放 WAV 文件或其中的一部分,这些内容可从简单的用户界面中选择。
对于不熟悉托管 DirectSound 的初学者,我将简要介绍 DirectSound
,然后深入探讨 DirectSound 中的循环缓冲区。对于已经熟悉的人,我将深入研究 WAV 文件结构,最终创建一个解析 WAV 文件的类。为了使应用程序正常工作,将使用系统计时器。最后,也是最重要的,是将所有代码整合在一起。
假设您已经熟悉 DirectX,并且已在开发平台上安装了 DirectX SDK。
关于应用程序
“Circular Buffers”是一款使用 VB.NET (VS 2003) 开发的应用程序。它演示了以下概念:
- 使用
DirectSound
静态缓冲区播放 WAV 文件 - 读取 WAV 文件并进行解析,为播放做准备
- 使用
DirectSound
循环缓冲区播放 WAV 文件流 - 使用系统计时器对定时事件进行精确控制
- 将 WAV 文件数据可视化为声音值图和图形双缓冲
- 使用
DirectSound
循环缓冲区播放 WAV 文件数据数组 - 选择 WAV 文件的一部分并使用静态缓冲区播放
- 简单混合两种声音
DirectSound 入门
DirectSound
是 DirectX 组件的一部分,专门负责声音的播放,包括单声道、立体声、3D 音效和多声道音效。首先,将 DirectSound
引用添加到您的 VB.NET 项目中,如下图所示。
通过添加 DirectSound 的引用,您将公开本项目的四个基本对象,这些对象对于播放和操作声音是必需的:
对象 | 目的 |
Microsoft.DirectX.DirectSound.Device | 这是使用 DirectSound 所需的主要音频设备对象。 |
Microsoft.DirectX.DirectSound.WaveFormat | 包含 WAV 所需的头属性。对于自定义声音,您必须设置所有参数。 |
Microsoft.DirectX.DirectSound.SecondaryBuffer
| 这是我们将声音数据写入的缓冲区,然后由主硬件混合并播放声音。您可以拥有任意数量的辅助缓冲区,只要 RAM 允许,但只有一个主缓冲区,它位于硬件中。 |
Microsoft.DirectX.DirectSound.BufferDescription | 根据 WAV 格式定义音频设备的功能。可以设置 3D 音效、音量控制、频率和声像。 |
使用 DirectSound 的最低要求代码
'Play a sound wave using a default static buffer
Private Sub cmdDefault_Click(……) Handles cmdDefault.Click
Try
SoundDevice = New Microsoft.DirectX.DirectSound.Device
SoundDevice.SetCooperativeLevel(Me.Handle_
, Microsoft.DirectX.DirectSound.CooperativeLevel.Normal)
SbufferOriginal = New _ Microsoft.DirectX.DirectSound._
SecondaryBuffer(SoundFile, SoundDevice)
SbufferOriginal.Play(0,_
Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
Catch ex As Exception
End Try
End Sub
上面显示的代码是播放 WAV 文件声音所需的最低代码。所需的步骤是:
- 创建一个新的声音设备,并将一个有效的窗口句柄作为参数,然后设置合作级别。
- Priority:当应用程序获得焦点时,只有它的声音是可听见的
- Normal:将所有声音输出限制为 8 位
- WritePrimary:允许应用程序写入主缓冲区
- 创建一个辅助声音缓冲区,并将一个有效的文件名/文件流和音频设备作为输入。
- 声音文件只能是有效的 WAV 文件。
- 调用辅助缓冲区的 play 方法。
使用 DirectSound 静态缓冲区播放 WAV 文件
静态缓冲区的名称即为其特性:静态。其内容不会随时间而改变,且一次加载。对于连续播放声音,辅助缓冲区的 play 方法使用循环选项。上述步骤使用了一个静态缓冲区。WAV 文件的内容被加载到辅助缓冲区中并进行播放。
当 WAV 文件较小且其大小不会消耗过多资源时,静态缓冲区最适用。上面代码中的 `cmdDefault_Click` 过程会创建并播放一个静态缓冲区。
读取 WAV 文件并进行解析,为播放做准备
WAV 文件格式是 Microsoft RIFF 规范的一个子集,用于存储多媒体文件。RIFF 文件以文件头开始,后跟一系列数据块。WAVE 文件通常就是一个 RIFF 文件,其中包含一个“WAVE”块,该块又包含两个子块——一个指定数据格式的“fmt”块和一个包含实际采样数据的“data”块。
下图描绘了 WAV 文件结构。`CWAVReader` 类解析 WAV 文件结构,提取头信息(WAV 格式)和实际声音数据。
下面的代码描绘了带有解析 WAV 文件结构一系列方法的构造函数。
‘Constructor to open stream
Sub New(ByVal SoundFilePathName As String)
mWAVFileName = SoundFilePathName
mOpen = OpenWAVStream(mWAVFileName)
'******************* MAIN WORK HERE ******************
'Parse the WAV file and read the
If mOpen Then
'Read the Header Data in THIS ORDER
'Each Read results in the File Pointer Moving
mChunkID = ReadChunkID(mWAVStream)
mChunkSize = ReadChunkSize(mWAVStream)
mFormatID = ReadFormatID(mWAVStream)
mSubChunkID = ReadSubChunkID(mWAVStream)
mSubChunkSize = ReadSubChunkSize(mWAVStream)
mAudioFormat = ReadAudioFormat(mWAVStream)
mNumChannels = ReadNumChannels(mWAVStream)
mSampleRate = ReadSampleRate(mWAVStream)
mByteRate = ReadByteRate(mWAVStream)
mBlockAlign = ReadBlockAlign(mWAVStream)
mBitsPerSample = ReadBitsPerSample(mWAVStream)
mSubChunkIDTwo = ReadSubChunkIDTwo(mWAVStream)
mSubChunkSizeTwo = ReadSubChunkSizeTwo(mWAVStream)
mWaveSoundData = ReadWAVSampleData(mWAVStream)
mWAVStream.Close()
End If
End Sub
注意:必须保持顺序!代码使用二进制流读取,该读取会推进文件指针。
解析二进制流并不难。这涉及使用二进制读取器的 `ReadBytes` 方法按需读取一定数量的字节。
下面的代码从 WAV 文件读取块 ID。请注意,块 ID 是大端序。
计算机体系结构在字节序方面存在差异。有些从左到右存储数据,称为大端序。其他一些则从右到左存储数据,称为小端序。一种使用大端字节序的著名计算机体系结构是 Sun 的 Sparc。Intel 体系结构使用小端字节序,Compaq Alpha 处理器也是如此。
'Read the ChunkID and return a string
Private Function ReadChunkID(….) As String
Dim DataBuffer() As Byte
Dim DataEncoder As System.Text.ASCIIEncoding
Dim TempString As Char()
DataEncoder = New System.Text.ASCIIEncoding
DataBuffer = WAVIOstreamReader.ReadBytes(4)
'Ensure we have data to spit out
If DataBuffer.Length <> 0 Then
TempString = DataEncoder.GetChars(DataBuffer, 0, 4)
Return TempString(0) & TempString(1) & TempString(2) & TempString(3)
Else
Return ""
End If
End Function
由于我们正在读取数据并根据相对位置(数组从位置 0 读取到位置 3)将其转换为文本,因此这是一个大端值。
小端值需要一个更复杂的函数。会读取二进制流,但会对值进行反转和填充以确保正确的对齐,然后将其转换为文本或值。下面的代码就是这样一个函数,它接收一个字节数组并将其反转以返回小端值。
'Get the small endian value
Private Function GetLittleEndianStringValue(..) As String
Dim ValueString As String = "&h"
If DataBuffer.Length <> 0 Then
'In little endian, we reverse the array data
and pad the same where the length is 1
If Hex(DataBuffer(3)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(3))
Else
ValueString &= Hex(DataBuffer(3))
End If
If Hex(DataBuffer(2)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(2))
Else
ValueString &= Hex(DataBuffer(2))
End If
If Hex(DataBuffer(1)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(1))
Else
ValueString &= Hex(DataBuffer(1))
End If
If Hex(DataBuffer(0)).Length = 1 Then
ValueString &= "0" & Hex(DataBuffer(0))
Else
ValueString &= Hex(DataBuffer(0))
End If
Else
ValueString = "0"
End If
GetLittleEndianStringValue = ValueString
End Function
读取 WAV 的属性后,最后一个函数是读取整个声音数据。声音被读取为一系列 `int16` 数据块。内存流有一个名为 `ReadInt16` 的方法,该方法被反复调用。下面的代码读取实际的声音值,并将数据从无符号数据转换为有符号 `int16`。
'returns the wave data as a byte array
Public Function GetSoundDataValue() As Int16()
Dim DataCount As Integer
Dim tempStream As IO.BinaryReader
tempStream = New IO.BinaryReader(New IO.MemoryStream(mWaveSoundData))
tempStream.BaseStream.Position = 0
'Create a data array to hold the data read from the stream
'Read chunks of int16 from the stream (already aligned!)
Dim tempData(CInt(tempStream.BaseStream.Length / 2)) As Int16
While DataCount <= tempData.Length - 2
tempData(DataCount) = tempStream.ReadInt16()
DataCount += 1
End While
tempStream.Close()
tempStream = Nothing
Return tempData
End Function
使用系统计时器对定时事件进行精确控制
system.timers.timer
的工作方式与 Windows Forms 计时器非常相似,但不需要 Windows 消息泵。除此之外,服务器计时器和 Windows Forms 计时器之间的主要区别在于,服务器计时器的事件处理程序在线程池线程上执行。这使得即使事件处理程序执行时间较长,也能保持用户界面的响应能力。在此音频编程的案例中,另一个关键区别是更高的精度和线程安全。
计时器类的构造函数接受时间间隔和一个在间隔结束后调用的函数。system.timers.timer
对象设置为自动重置,因此会持续在间隔后调用该函数。委托(指向函数/子程序的指针)的使用。因此,计时器对象获取计时器间隔以及类型为 System.Timers.ElapsedEventHandler
的委托(指针)以进行调用。此类用于监控声音缓冲区并“补充”要播放的数据,以及在播放音乐时绘制播放条的进度。
使用 DirectSound 循环缓冲区播放 WAV 文件流
根据 Wikipedia,“循环缓冲区或环形缓冲区是一种将单个固定大小的缓冲区视为连接在一起的数据结构。这种结构非常适合缓冲数据流。” 如下图所示,循环缓冲区是这样的:
写入指针标识了一个我们可以写入声音数据的位置。播放指针标识了声音缓冲区将从中播放数据的位置。红色的框表示可以写入数据的位置。
在第一种情况下,写入指针位于比播放指针大的位置。随着播放指针的推进,写入指针需要以不至于产生失真的延迟来推进。在第二种情况下,写入指针已经回绕,现在位于比播放指针小的一个位置。
当写入指针到达位置 7 时,它必须回绕。以下代码启用了写入指针的回绕,并返回已播放数据的量,也就是需要写入新数据的位置。
'get the played data size
Function GetPlayedSize() As Integer
Dim Pos As Integer
Pos = SbufferOriginal.PlayPosition
If Pos < NextWritePos Then
Return Pos + (SbufferOriginal.Caps.BufferBytes - NextWritePos)
Else
Return Pos - NextWritePos
End If
End Function
PlayPosition
是辅助声音缓冲区的属性,它返回播放指针的位置。NextWritePos
是一个内部指针,用于标识要写入数据的位置。
随着播放指针的推进,我们必须不断地向循环缓冲区添加数据。在这种情况下,我们将使用内存流来读取数据并写入循环流。如前所述,如果 WAV 文件很大且您打算分块播放数据,而不是将整个数据读入内存,那么循环缓冲区非常有用。有两种方法可以“填充”循环流:使用通知或使用轮询技术。我已经实现了后者。系统计时器用于不断用数据“填充”循环流。
每隔 75 毫秒,计时器对象就会调用 `PlayEventHandler` 函数。该函数调用其他函数来确定要写入的数据量,然后将这些数据从流写入辅助声音缓冲区。
'Update the Circular buffer based on either a stream of data array
Sub PlayerEventHandler(…)
'Stop if we have read all data
If PlayerPosition >= MYwave.SubChunkSizeTwo Then
StopPlay()
End If
'If an array, use the dataArray to top up new data to the circular buffer
If IsArray = False Then
'Get the amount of data to write and thereafter write the data
WriteData(GetPlayedSize())
Else
WriteDataArray(GetPlayedSize())
End If
End Sub
`GetPlayedSize` 函数利用循环缓冲区的概念,返回可以安全写入辅助声音缓冲区的数据量。然后 `WriteData` 函数将数据写入辅助缓冲区。下面的代码演示了填充辅助缓冲区(循环缓冲区)所需的功能。
'Write data to the circular buffer (secondary buffer that is)
Sub WriteData(ByVal DataSize As Integer)
Dim Tocopy As Integer
'make sure the data is less than the latency amount of 300ms
Tocopy = Math.Min(DataSize, TimeToDataSize(Latency))
'Only write data if there is something to write!
If Tocopy > 0 Then
'Restore the buffer
If SbufferOriginal.Status.BufferLost Then
SbufferOriginal.Restore()
End If
'Copy the data to the buffer
'The DataMemStream is a binary stream object.
'This can also be a large WAV file still on harddisk
SbufferOriginal.Write(NextWritePos, DataMemStream, Tocopy, _
Microsoft.DirectX.DirectSound.LockFlag.None)
'As data is read form the DataMemStream, the position
'(internal of the structure) advances
'by the size of Tcopy
'Advance the total cumulative bytes read
PlayerPosition += Tocopy
'advance the NextWrite pointer
NextWritePos += Tocopy
'If we are at the end, we wrap round
If NextWritePos >= SbufferOriginal.Caps.BufferBytes Then
NextWritePos = NextWritePos - SbufferOriginal.Caps.BufferBytes
End If
End If
End Sub
system.timers.timer
对象还用于更新屏幕,显示播放指针相对于正在播放的数据的位置,而不是相对于辅助缓冲区的位置。`MyPainPoint` 函数以相同的 75 毫秒间隔调用。但我使用了不同的计时器来减少延迟和声音失真。
'The handler to the timer tick event :
'Paints the location of the player pointer on the sound graph as the data is played
Sub MyPainPoint(ByVal obj As Object, ByVal Args As System.Timers.ElapsedEventArgs)
'Control to ensure we stop when the total cumulated bytes read is equal to
'the total data size
If PlayerPosition >= MYwave.SubChunkSizeTwo Then
StopPlay()
'Update the labels
lblPos.Text = MYwave.SubChunkSizeTwo.ToString
lbltime.Text = MYwave.PlayTimeSeconds().ToString
End If
'Draw a line red from top to bottom showing the current position based on play position
'The PlayerPOsition is the absolute location of the current played data
'This is taken as a ratio of the total data size and scaled to the width of the picture
'control
Dim XPos As Single = CSng((PlayerPosition / MYwave.SubChunkSizeTwo) * picWave.Width)
Dim posgraphic As Graphics
'Clone the original canvas (this has the original sound graph). We do not need to
're-draw this large graph as it willtake much processing time!
PlayPicture = CType(myPicture.Clone, Bitmap)
posgraphic = Graphics.FromImage(PlayPicture)
'Draw the pointer
Dim Mypen As Pen = New Pen(Color.Red)
posgraphic.DrawLine(Mypen, XPos, 0, XPos, picWave.Height)
'Draw line to myPicture
MyTime += TimerStep
posgraphic.DrawImage(PlayPicture, picWave.Width, picWave.Height)
'Update the status on the labels
lblPos.Text = PlayerPosition.ToString
lbltime.Text = (MyTime / 1000).ToString
'force a redraw of the picture updated.
Me.Invalidate(New Drawing.Rectangle(picWave.Left, picWave.Top, picWave.Width, _
picWave.Height))
End Sub
声音数据图的初始绘制存储为位图。该位图被不断克隆,并在不同位置绘制一条红线,以产生移动效果。
将 WAV 文件数据可视化为声音值图
`DrawGraph` 函数接收一个 `int16` 数据数组,并将其绘制在(垂直中点)图片控件上。使用双缓冲来加速绘图过程。首先创建一个位图,然后绘制图形。绘制完整个折线图后,将图形绘制到位图上。然后将位图传输到图片控件。这个过程比直接在图片控件上绘图要快。
'Draws the Sound data as a wave onto a canvas using double buffering to speed up work
Function DrawGraph(ByVal Data() As Int16) As Bitmap
'Create the Canvas
Dim myBitmap As System.Drawing.Bitmap
'Create an array to hold the wav data which we can sort
Dim tempData(Data.Length) As Integer
'Copy the data to the temporary location ... can be done better
Data.CopyTo(tempData, 0)
'Sort the array to get the maximum and minimum Y values
Array.Sort(tempData)
'generate the Canvas (drawing board in memory
myBitmap = New Bitmap(picWave.Width, picWave.Height)
'Create your paint brush, pens and drawing objects
Dim myGraphic As System.Drawing.Graphics
myGraphic = Graphics.FromImage(myBitmap)
'draw the background with a custom color
myGraphic.Clear(Color.FromArgb(181, 223, 225))
'Get the parameters to draw the data and scale to fit the canvas but
'draw from the middle
Dim YMax As Integer = tempData(Data.Length - 1)
Dim YMin As Integer = tempData(0)
Dim XMax As Integer = picWave.Width
Dim Xmin As Integer = 0
'Create an array of points to draw a line
Dim PicPoint(Data.Length - 1) As System.Drawing.PointF
Dim Count As Integer
Dim Step1 As Single 'Scale the data between Ymax and Ymin
Dim Step2 As Single 'Scale the data further to fit between the canvas height
Dim step3 As Single 'Draw the point form the middle of the canvas
Dim Mypen As New Pen(Color.FromArgb(24, 101, 123))
'Draw the lines from the series of points representing the sound wav
For Count = 0 To Data.Length - 1
Step1 = CSng(Data(Count) / (YMax - YMin))
Step2 = CSng(Step1 * picWave.Height / 2)
step3 = CSng(Step2 + (picWave.Height / 2))
PicPoint(Count) = New System.Drawing.PointF(CSng(XMax * _
(Count / Data.Length)), step3)
Next
'Draw the lines
myGraphic.DrawLines(Mypen, PicPoint)
'Draw graphics onto canvas
myGraphic.DrawImage(myBitmap, picWave.Width, picWave.Height)
'return the picture memory object
Return (myBitmap)
End Function
使用 Direct Sound 循环缓冲区播放 WAV 文件数据数组
从数组播放数据与从内存流播放数据没有太大区别。唯一的区别是辅助缓冲区的 `Add` 方法有一个重载,可以从数组读取数据。作为程序员,您必须从源(内存流)获取数据并创建数据数组。如代码所示,将内存流重新定位到读取的最后一个位置(`playerposition`),然后从那里读取到安全写入数据的长度。
'Write data to a Data Array.... similar to the above. using memory a stream
Sub WriteDataArray(ByVal DataSize As Integer)
Dim Tocopy As Integer
'ensure we do not have a big latency . the maximum is 300 ms
Tocopy = Math.Min(DataSize, TimeToDataSize(Latency))
'is we have data, then write to the array and play
If Tocopy > 0 Then
'Restore the buffer
If SbufferOriginal.Status.BufferLost Then
SbufferOriginal.Restore()
End If
'Copy the data to the Array
're-create the data array (this is very slow!!)
ReDim DataArray(Tocopy - 1)
'Position the memory stream to the last location we read from.
DataMemStream.Position = PlayerPosition
'Copy the data from the stream to the array
DataMemStream.Read(DataArray, 0, Tocopy - 1)
'Write the data to the secondary buffer
SbufferOriginal.Write(NextWritePos, DataArray, _
Microsoft.DirectX.DirectSound.LockFlag.None)
'Advance the pointers
PlayerPosition += Tocopy
NextWritePos += Tocopy
If NextWritePos >= SbufferOriginal.Caps.BufferBytes Then
NextWritePos = NextWritePos - SbufferOriginal.Caps.BufferBytes
End If
End If
End Sub
选择 WAV 文件的一部分并使用静态缓冲区播放
要播放声音文件选定的部分,请突出显示该部分并选择 capture 1 或 capture 2。如果两个按钮都选定了不同的部分,则可以同时播放两种不同的声音(混合)。
要选择声音的一部分,请在图片控件上单击鼠标左键并向右移动鼠标。一旦松开鼠标左键,将要播放的部分就会被高亮显示。单击 capture 1 按钮。对另一部分重复相同的操作,然后单击 capture 2。
选择是通过使用图片控件的 mousedown、mousemove 和 mouseup 事件来实现的。通过使用 alpha 混合使矩形透明。以下代码是实现:
'Draw the band over the selection
Sub DrawSelection()
'Draw a rubber band
Dim posgraphic As Graphics
Dim RubberRect As Rectangle
RubberRect = New Rectangle(StartPoint.X, 0, _
EndPoint.X - StartPoint.X, picWave.Height - 3)
'Clone the original canvas
PlayPicture = CType(myPicture.Clone, Bitmap)
posgraphic = Graphics.FromImage(PlayPicture)
'Draw the pointer
Dim Mypen As Pen = New Pen(Color.Green)
'Create a transparent brush using alpha blending techniques
Dim MyBrush As SolidBrush = New SolidBrush(Color.FromArgb(85, 204, 32, 92))
'Draw the boarder
posgraphic.DrawRectangle(Mypen, RubberRect)
'Fill the color
posgraphic.FillRectangle(MyBrush, RubberRect)
'Draw the picture on the form with the updated section of music to clip
posgraphic.DrawImage(PlayPicture, picWave.Width, picWave.Height)
'redraw the portion only
Me.Invalidate(New Drawing.Rectangle(picWave.Left, picWave.Top, _
picWave.Width, picWave.Height))
End Sub
要播放自定义选择的声音,数据会被读取到一个数据数组中。您必须确保读取的数据根据 `blockalign` 值对齐。否则,就会产生不令人愉悦的噪音!
'Plays a segment of data based on the rubber-band we have drawn prior to
'raising this event
Public Sub SetSegment(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles cmdSeg1.Click, cmdSeg2.Click
'set local variables to use
Dim tag As Int16
Dim theButton As Button
Dim DataStart As Integer
Dim DataStop As Integer
Dim BufferSize As Integer
'initialize the direct sound objects
Dim Format As Microsoft.DirectX.DirectSound.WaveFormat
Dim Desc As Microsoft.DirectX.DirectSound.BufferDescription
Dim MixBuffer As Microsoft.DirectX.DirectSound.SecondaryBuffer
cmdBrowse.Enabled = False
cmdDefault.Enabled = False
cmdCustom.Enabled = False
cmdCircular.Enabled = False
cmdSeg1.Enabled = False
cmdSeg2.Enabled = True
cmdStop.Enabled = False
theButton = CType(sender, Button)
theButton.Enabled = False
'get the locations from where to read data from
DataStart = CInt(MYwave.SubChunkSizeTwo * (StartPoint.X / picWave.Width))
DataStop = CInt(MYwave.SubChunkSizeTwo * (EndPoint.X / picWave.Width))
StartPoint = Nothing
EndPoint = Nothing
'ensure that the data is aligned.. if not, noise results
DataStart = DataStart - (DataStart Mod CInt(MYwave.BlockAlign))
DataStop = DataStop - (DataStop Mod CInt(MYwave.BlockAlign))
'Get the data into a Data array
Dim DataSegment(DataStop - DataStart) As Byte
'Read the data from the Stream
DataMemStream.Position = DataStart
'Read from the stream to the array buffer
DataMemStream.Read(DataSegment, 0, DataStop - DataStart)
'Now play the sound.
'For custom sound, you must set the format
Format = New Microsoft.DirectX.DirectSound.WaveFormat
Format.AverageBytesPerSecond = CInt(MYwave.ByteRate)
Format.BitsPerSample = CShort(MYwave.BitsPerSample)
Format.BlockAlign = CShort(MYwave.BlockAlign)
Format.Channels = CShort(MYwave.NumChannels)
Format.FormatTag = Microsoft.DirectX.DirectSound.WaveFormatTag.Pcm
Format.SamplesPerSecond = CInt(MYwave.SampleRate)
Desc = New Microsoft.DirectX.DirectSound.BufferDescription(Format)
BufferSize = DataStop - DataStart + 1
'ensure the size is also aligned
BufferSize = BufferSize + (BufferSize Mod CInt(MYwave.BlockAlign))
Desc.BufferBytes = BufferSize
Desc.ControlFrequency = True
Desc.ControlPan = True
Desc.ControlVolume = True
Desc.GlobalFocus = True
'Play the sound
Try
MixBuffer = New Microsoft.DirectX.DirectSound.SecondaryBuffer(Desc, SoundDevice)
MixBuffer.Stop()
MixBuffer.SetCurrentPosition(0)
If MixBuffer.Status.BufferLost Then
MixBuffer.Restore()
End If
MixBuffer.Write(0, DataSegment, Microsoft.DirectX.DirectSound.LockFlag.None)
MixBuffer.Play(0, Microsoft.DirectX.DirectSound.BufferPlayFlags.Looping)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
关注点
使用 DirectSound
播放非常有趣,尤其是在经过数小时数天的挣扎后终于听到真实的声音时。我花了几天时间才让循环缓冲区正常工作。WAV 解析器让我思考了很久,尤其是关于字节序的问题!我打算在此基础上进一步开发,并集成 FFT(快速傅里叶变换)来进行真正的音乐混合!
就是这样!希望这对那些尚未从网上 C/C++、C# 资料中获益的 VB.NET DirectSound 爱好者有所帮助。由于资料和书籍稀少,我在这里所做的大部分工作都是通过反复试验完成的!
祝您编码愉快!