'***************************************************************************
|
'* Class frmAudio *
|
'* *
|
'* This form manages the audio transmission to and from the E301 modules. *
|
'* The audio format choosen is the ITU G.711-A law (8 bits/sample, 8Ks/s, *
|
'* non-linearized to reduce noise). *
|
'* *
|
'* The transport protocol is RTP over UDP, according to RFC's 3550 and *
|
'* 3551 *
|
'* *
|
'* For the computer audio management, Managed DirectX SDK is used. *
|
'* *
|
'* See more info in ClassRTPAgent.Vb, the Microsoft DirectX documentation *
|
'* and the correspondant RFC's and standards. *
|
'* *
|
'* This form includes two graphical signal analyzers (one for audio sent *
|
'* and one for the audio received) for debug and demonstration purposes *
|
'* only. *
|
'* *
|
'* Also provides a tone and a chirp generator very usefull in testing the *
|
'* audio quality of the phones. * *
|
'* *
|
'* IMPORTANT NOTE: The DirectX audio part of the application is not fully *
|
'* debugged. Is an adaptation of the microsoft examples provided with the *
|
'* DirectX SDK. It is given as a starting point and mainly to demonstrate *
|
'* the audio RTP encapsulation. *
|
'* *
|
'* EQUITEL C.V. February 2007 ALMDI*
|
'***************************************************************************
|
Imports System
|
Imports System.Math
|
Imports System.Threading
|
Imports Microsoft.DirectX
|
Imports Microsoft.DirectX.DirectSound
|
|
|
Public Class frmAudio
|
|
#Region "Definitions"
|
|
#Region "General definitions"
|
'When sending audio via RTP according to the G.711(A-law) standard,
|
'each packet has to be 20 miliseconds: 20 ms * 80 samples/ms = 160 samples
|
Private Const AudioSamplesPerPacket As Integer = 160
|
'Size in bytes of the audio packet from/to the computer audio system
|
'(160 Samples/packet * 2 Bytes/sample = 320 bytes/packet)
|
Private Const PCMAudioPacketLength As Integer = 320
|
'Object to send and receive audio packets with RTP/UDP protocol
|
Private RTPAgent As ClassRTPAgent
|
'Object to hold the format of the audio (see directX documentation)
|
Private AudioFormat As WaveFormat
|
'Indicates if the audio engine to read and send audio to the PC is created
|
Private AudioDevicesSelected As Boolean = False
|
'Indicates if the audio is active
|
Private AudioON As Boolean = False
|
'Indicates if we are sending a sinusoidal tone to the E301 instead the actual audio
|
Private ToneON As Boolean = False
|
'Indicates if we are sending a chirp tone to the E301...
|
Private ChirpON As Boolean = False
|
'Holds which phone attached to the E301 is active
|
Public MasterOrSlave As Byte
|
'Form of the tone generator
|
Private WithEvents ToneWindow As frmTone
|
'Form of the Chirp generator
|
Private WithEvents ChirpWindow As frmChirp
|
#End Region
|
|
#Region "DirectX Audio input definitions"
|
Private RecordDevice As Capture = Nothing
|
Private RecordBuffer As CaptureBuffer = Nothing
|
Private Const NumberRecordNotifications As Integer = 16
|
Private RecordPositionNotify(NumberRecordNotifications) As BufferPositionNotify
|
Private RecordNotificationEvent As AutoResetEvent = Nothing
|
Private RecordNotify As Notify = Nothing
|
Private RecordThread As Thread = Nothing
|
Private RecordBufferSize As Integer = 0
|
Private RecordNextOffset As Integer = 0
|
Private RecordNotifySize As Integer = 0
|
#End Region
|
|
#Region "DirectX Audio output definitions"
|
Private PlayDevice As Microsoft.DirectX.DirectSound.Device
|
Private PlayBuffer As Microsoft.DirectX.DirectSound.SecondaryBuffer
|
Private Const NumberPlayNotifications As Integer = 16
|
Private PlayPositionNotify(NumberRecordNotifications) As BufferPositionNotify
|
Private PlayBufferSize As Integer = 0
|
Private PlayNotifySize As Integer = 0
|
Private PlayNextOffset As Integer = 0
|
Private PlayThread As Thread = Nothing
|
Private PlayNotificationEvent As AutoResetEvent = Nothing
|
Private PlayNotify As Notify = Nothing
|
#End Region
|
|
#Region "Events"
|
Public Event Log(ByVal Texto As String)
|
#End Region
|
|
#End Region
|
|
#Region "Methods"
|
|
#Region "Audio management"
|
|
Private Sub SelectAudioDevices()
|
'Audio format: 16 bit/sample, 8000 samples/second, 1 channel, PCM
|
AudioFormat.BitsPerSample = 16
|
AudioFormat.Channels = 1
|
AudioFormat.SamplesPerSecond = 8000
|
AudioFormat.FormatTag = WaveFormatTag.Pcm
|
AudioFormat.BlockAlign = CShort(AudioFormat.Channels * (AudioFormat.BitsPerSample / 8))
|
AudioFormat.AverageBytesPerSecond = AudioFormat.BlockAlign * AudioFormat.SamplesPerSecond
|
'Select the audio input and ouput devices
|
If SelectInputDevice() Then
|
If Not SelectOutputDevice() Then
|
MsgBox("ERROR: unable to create the audio output device")
|
Close()
|
Else
|
AudioDevicesSelected = True
|
End If
|
Else
|
MsgBox("Error: unable to create the audio input device")
|
Close()
|
End If
|
End Sub
|
|
Public Sub AudioStart(ByVal DestinationIP As String, ByVal DestinationPort As Integer, ByVal LocalPort As Integer, ByVal mMasterOrSlave As Byte)
|
'Select the audio devices if not selcted yet
|
If Not AudioDevicesSelected Then SelectAudioDevices()
|
If Not AudioON Then
|
'Initialize analyzers
|
ToIPAnalyzer.Initialize()
|
FromIPAnalyzer.Initialize()
|
'Create the RTP Agent
|
RTPAgent = New ClassRTPAgent(DestinationIP, DestinationPort, LocalPort)
|
'Create the input notify engine and begin audio capture
|
CreateInputNotify()
|
RecordBuffer.Start(True)
|
'Create the output notify engine and begin audio output
|
CreateOutputNotify()
|
PlayBuffer.Play(0, BufferPlayFlags.Looping)
|
'Store the state
|
MasterOrSlave = mMasterOrSlave
|
AudioON = True
|
End If
|
End Sub
|
|
Public Sub AudioStop()
|
If AudioON Then
|
'Dispose the event monitoring threads
|
PlayThread.Abort()
|
RecordThread.Abort()
|
'Stop the audio capture and output
|
If Not RecordBuffer Is Nothing Then
|
RecordBuffer.Stop()
|
End If
|
If Not PlayBuffer Is Nothing Then
|
PlayBuffer.Stop()
|
End If
|
'Dispose the RTP agent
|
RTPAgent.Dispose()
|
RTPAgent = Nothing
|
AudioON = False
|
End If
|
End Sub
|
|
#End Region
|
|
#Region "Audio input from soundcard"
|
|
Private Function SelectInputDevice() As Boolean
|
Dim DevCol As New CaptureDevicesCollection
|
Dim CaptureDeviceGuid As Guid = Guid.Empty
|
|
'We select the first audio interface avalaible in the computer
|
'In computer with more than one audio interfaces, this code may
|
'be changed to select wich one to use.
|
'The sound source is the choosen one by the user in the operative system.
|
Try
|
CaptureDeviceGuid = DevCol(0).DriverGuid
|
RecordDevice = New Capture(CaptureDeviceGuid)
|
Catch
|
End Try
|
|
Return Not (RecordDevice Is Nothing)
|
End Function
|
|
Private Sub CreateInputNotify()
|
'Creates the input object to read data from the PC audio card.
|
Dim Desc As New CaptureBufferDescription()
|
Dim i As Integer
|
'If it is already created, dispose it and create again
|
If Not RecordNotify Is Nothing Then
|
RecordNotify.Dispose()
|
RecordNotify = Nothing
|
End If
|
If Not RecordBuffer Is Nothing Then
|
RecordBuffer.Dispose()
|
RecordBuffer = Nothing
|
End If
|
'We want to be advised each time a audio packet of PCMAudioPacket
|
'bytes is ready to be read. Tis size must be multiple of AudioFormat.BlockAlign
|
RecordNotifySize = PCMAudioPacketLength
|
RecordNotifySize -= RecordNotifySize Mod AudioFormat.BlockAlign
|
'The total buffer size must be the product of the RecordNotifySize by the
|
'number of notifications we want
|
RecordBufferSize = RecordNotifySize * NumberRecordNotifications
|
'Buffer creation
|
Desc.BufferBytes = RecordBufferSize
|
Desc.Format = AudioFormat
|
Desc.WaveMapped = True
|
Try
|
RecordBuffer = New CaptureBuffer(Desc, RecordDevice)
|
Catch e As Exception
|
MsgBox("ERROR opening audio device.", MsgBoxStyle.Critical)
|
Close()
|
End Try
|
RecordNextOffset = 0
|
If Nothing Is RecordBuffer Then
|
MsgBox("ERROR opening audio device.", MsgBoxStyle.Critical)
|
Close()
|
End If
|
'This object will indicate when an audio packet is ready to be read from
|
'the audio card
|
RecordNotificationEvent = New AutoResetEvent(False)
|
'We configure the object RecordPositionNotify to indicate the postitions of the buffer
|
'that must signal an event when reached
|
For i = 0 To NumberRecordNotifications - 1
|
'Position of the buffer
|
RecordPositionNotify(i).Offset = RecordNotifySize * (i + 1) - 1
|
'Handler to the event generator object
|
RecordPositionNotify(i).EventNotifyHandle = RecordNotificationEvent.SafeWaitHandle.DangerousGetHandle
|
Next i
|
'Tell DirectSound when to notify the app. The notification will come in the from
|
'of signaled events that are handled in the notify thread.
|
RecordNotify = New Notify(RecordBuffer)
|
RecordNotify.SetNotificationPositions(RecordPositionNotify, NumberRecordNotifications)
|
'Create a thread to monitor the notify events
|
RecordThread = New Thread(New ThreadStart(AddressOf InputProcess))
|
RecordThread.Start()
|
End Sub
|
|
Private Sub InputProcess()
|
'Monitoring thread for the audio input events
|
While True
|
'Wait until a new audio packet is ready
|
If RecordNotificationEvent.WaitOne(1000, True) Then
|
ReadAudio()
|
Else
|
RaiseEvent Log("Timeout waiting for audio input from PC")
|
End If
|
End While
|
End Sub
|
|
Private Sub ReadAudio()
|
'We enter here each 20 miliseconds to read a new audio packet
|
'from the PC audio card
|
'Packet to be read
|
Dim ReadPacket(AudioSamplesPerPacket) As Short
|
'Pointers to read the DirectSound buffer
|
Dim ReadPos As Integer
|
Dim CapturePos As Integer
|
Dim LockSize As Integer
|
'If recordBuffer is not created then do nothing
|
If RecordBuffer Is Nothing Then
|
Return
|
End If
|
RecordBuffer.GetCurrentPosition(CapturePos, ReadPos)
|
LockSize = ReadPos - RecordNextOffset
|
If LockSize < 0 Then
|
LockSize += RecordBufferSize
|
End If
|
'Block align lock size so that we are always write on a boundary
|
LockSize -= LockSize Mod RecordNotifySize
|
If 0 = LockSize Then
|
Return
|
End If
|
'Read the capture buffer.
|
Try
|
'If we must send tone instead real audio, get the packet from the tone generation window
|
'If ToneON Then
|
'ToneWindow.ToneGenerator.OutputPacket(ReadPacket, AudioSamplesPerPacket)
|
'Else
|
'The same with the Chirp tone
|
'If ChirpON Then
|
'ChirpWindow.ChirpGenerator.OutputPacket(ReadPacket, AudioSamplesPerPacket)
|
'Else
|
'Actual read from the PC audio card
|
ReadPacket = RecordBuffer.Read(RecordNextOffset, GetType(Short), LockFlag.None, LockSize)
|
'End If
|
'End If
|
'RTP packet send
|
RTPAgent.SendAudioPacket(ReadPacket)
|
Catch e As Exception
|
RaiseEvent Log("Error in audio input" + e.ToString)
|
End Try
|
'Signal analyzer update
|
''''''' ToIPAnalyzer.NewAudioPacket(ReadPacket, AudioSamplesPerPacket)
|
' Move the capture offset along
|
RecordNextOffset += ReadPacket.Length
|
RecordNextOffset = RecordNextOffset Mod RecordBufferSize ' Circular buffer
|
End Sub
|
|
#End Region
|
|
#Region "Audio output to soundcard"
|
|
Private Function SelectOutputDevice() As Boolean
|
Dim DevCol As New Microsoft.DirectX.DirectSound.DevicesCollection()
|
Dim DeviceGuid As Guid
|
|
'We select the first audio interface avalaible in the computer
|
'In computer with more than one audio interfaces, this code may
|
'be changed to select wich one to use.
|
Try
|
DeviceGuid = DevCol(0).DriverGuid
|
PlayDevice = New Microsoft.DirectX.DirectSound.Device(DeviceGuid)
|
Catch
|
End Try
|
If Not PlayDevice Is Nothing Then
|
PlayDevice.SetCooperativeLevel(Me, CooperativeLevel.Priority)
|
End If
|
Return Not (PlayDevice Is Nothing)
|
End Function
|
|
Private Sub CreateOutputNotify()
|
'Creates the output system to send data to the PC audio card
|
Dim Desc As New Microsoft.DirectX.DirectSound.BufferDescription()
|
Dim Silencio As Byte()
|
Dim i As Integer
|
'If it is already created, dispose it and create again
|
If Not PlayNotify Is Nothing Then
|
PlayNotify.Dispose()
|
PlayNotify = Nothing
|
End If
|
If Not PlayBuffer Is Nothing Then
|
PlayBuffer.Dispose()
|
PlayBuffer = Nothing
|
End If
|
'We want to be advised each time buffer of PCMAudioPacket
|
'bytes is ready to be written. Tis size must be multiple of AudioFormat.BlockAlign
|
PlayNotifySize = PCMAudioPacketLength
|
PlayNotifySize -= PlayNotifySize Mod AudioFormat.BlockAlign
|
'The total buffer size must be the product of the RecordNotifySize by the
|
'number of notifications we want
|
PlayBufferSize = RecordNotifySize * NumberPlayNotifications
|
'Create the output buffer with the same format than the input one
|
Desc.Format = RecordBuffer.Format
|
Desc.BufferBytes = PlayBufferSize
|
Desc.ControlPositionNotify = True
|
Desc.CanGetCurrentPosition = True
|
If PlayBuffer Is Nothing Then
|
PlayBuffer = New Microsoft.DirectX.DirectSound.SecondaryBuffer(Desc, PlayDevice)
|
End If
|
'Fill the buffer with silience
|
ReDim Silencio(PlayBufferSize - 1)
|
For i = 0 To PlayBufferSize - 1
|
Silencio(i) = 0
|
Next
|
PlayBuffer.Write(0, Silencio, LockFlag.None)
|
'This object will indicate when an audio packet is needed to be written to
|
'the audio card
|
PlayNotificationEvent = New AutoResetEvent(False)
|
'We configure the object RecordPositionNotify to indicate the postitions of the buffer
|
'that must signal an event when reached
|
For i = 0 To NumberPlayNotifications - 1
|
PlayPositionNotify(i).Offset = PlayNotifySize * i + PlayNotifySize - 1
|
PlayPositionNotify(i).EventNotifyHandle = PlayNotificationEvent.SafeWaitHandle.DangerousGetHandle
|
Next i
|
PlayNotify = New Notify(PlayBuffer)
|
'Tell DirectSound when to notify the app. The notification will come in the from
|
'of signaled events that are handled in the notify thread.
|
PlayNotify.SetNotificationPositions(PlayPositionNotify, NumberPlayNotifications)
|
'Create a thread to monitor the notify events
|
PlayThread = New Thread(New ThreadStart(AddressOf OutputProcess))
|
PlayThread.Start()
|
End Sub
|
|
Private Sub OutputProcess()
|
'Monitoring thread for the audio output events
|
While True
|
'Wait until a new audio packet is needed
|
If PlayNotificationEvent.WaitOne(1000, True) Then
|
OutputAudio()
|
Else
|
RaiseEvent Log("Timeout waiting for audio output to PC")
|
End If
|
End While
|
End Sub
|
|
Private Sub OutputAudio()
|
'Each 20 miliseconsd we must send a new audio packet to the
|
'PC audio card
|
Dim ReceivedPacket(AudioSamplesPerPacket) As Short
|
Dim WritePos As Integer
|
Dim PlayPos As Integer
|
Dim LockSize As Integer
|
'If PlayBuffer is not created yet, do nothing
|
If PlayBuffer Is Nothing Then
|
Return
|
End If
|
'Get the writting position in the buffer
|
PlayBuffer.GetCurrentPosition(PlayPos, WritePos)
|
LockSize = WritePos - PlayNextOffset
|
If LockSize < 0 Then
|
LockSize += PlayBufferSize
|
End If
|
' Block align lock size so that we are always write on a boundary
|
LockSize -= LockSize Mod PlayNotifySize
|
If 0 = LockSize Then
|
Return
|
End If
|
'Get a new audio packet from the RTP agent (i.e. from the E301 module)
|
ReceivedPacket = RTPAgent.ReceivedAudioPacket
|
'If we have to send the audio to the PC sound card, copy this packet to the
|
'play buffer. Else, the silent play buffer continues playing.
|
'If Not ChkSilent.Checked Then
|
PlayBuffer.Write(PlayNextOffset, ReceivedPacket, LockFlag.None)
|
'End If
|
'Update the signal analyzer
|
FromIPAnalyzer.NewAudioPacket(ReceivedPacket, AudioSamplesPerPacket)
|
' Move the capture offset along
|
PlayNextOffset += PCMAudioPacketLength
|
PlayNextOffset = PlayNextOffset Mod PlayBufferSize ' Circular buffer
|
End Sub
|
|
#End Region
|
|
#Region "Form and controls behaviour"
|
|
Private Sub ChkSilent_CheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ChkSilent.CheckedChanged
|
'Sometimes, can be usefull to not send the actual audio to the PC audio system.
|
'If you send to the E301 the output of the PC wave mixer, the same output
|
'you are hearing, sending the real incoming audio to the PC will make a loop.
|
'With this utility you can break the loop and allows you to send easily any audio
|
'you can hear in the PC to the sos phone (music etc.)
|
Dim i As Integer
|
Dim Silence As Byte()
|
If ChkSilent.Checked Then
|
If Not PlayBuffer Is Nothing Then
|
'Clear the play buffer
|
ReDim Silence(PlayBufferSize - 1)
|
For i = 0 To PlayBufferSize - 1
|
Silence(i) = 0
|
Next
|
PlayBuffer.Write(0, Silence, LockFlag.None)
|
End If
|
End If
|
End Sub
|
|
Private Sub MainForm_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
|
If Not RecordNotificationEvent Is Nothing Then
|
RecordNotificationEvent.Set()
|
End If
|
If Not PlayNotificationEvent Is Nothing Then
|
PlayNotificationEvent.Set()
|
End If
|
AudioStop()
|
End Sub
|
|
Private Sub ToneWindow_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles ToneWindow.FormClosing
|
ToneON = False
|
BtnTone.Enabled = True
|
BtnChirp.Enabled = True
|
End Sub
|
|
Private Sub BtnTone_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnTone.Click
|
BtnTone.Enabled = False
|
BtnChirp.Enabled = False
|
ToneWindow = New frmTone
|
ToneON = True
|
ToneWindow.Show()
|
End Sub
|
|
Private Sub BtnChirp_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnChirp.Click
|
BtnTone.Enabled = False
|
BtnChirp.Enabled = False
|
ChirpWindow = New frmChirp
|
ChirpON = True
|
ChirpWindow.Show()
|
End Sub
|
|
Private Sub ChirpWindow_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles ChirpWindow.FormClosing
|
ChirpON = False
|
BtnTone.Enabled = True
|
BtnChirp.Enabled = True
|
End Sub
|
#End Region
|
|
#End Region
|
|
Public Sub New()
|
|
' Llamada necesaria para el Diseñador de Windows Forms.
|
InitializeComponent()
|
|
' Agregue cualquier inicialización después de la llamada a InitializeComponent().
|
'Hide()
|
End Sub
|
|
Private Sub GroupBox1_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GroupBox1.Enter
|
|
End Sub
|
End Class
|