Alejandro Acuña
2024-08-12 831c7bd85763b5eb5ef46664c65f0181824f9e13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
'***************************************************************************
'* 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