بسم الله نبدأ تتلخص الفكرة الأساسية من نقل الصوت عبر بروتكول الإنترنت IP بتحويل الصوت
إلى مجموعة من ال Bits تجمع في Byte Array ثم كبسلته ليتم نقله ك Datagram
Packets عبر الشبكة ، وللاستقبال الصوت في الطرف الأخر يتم تجميع ال
Packets مرة أخرى في مصفوفة Byte Array , وتتم عملية القراءة وفق مبدأ ال
FIFO – First In First Out أي القادم أولا يعرض أولا ...
تمر عملية التقاط الصوت بمجموعة من المراحل تبدأ بالتقاط الصوت من
المايكروفون وتمثيل الذبذبات الصوتية ثم تحويلها إلى مجموعة من ال Bits
وذلك بعمل Sampling لذبذبات الصوتية الملتقطة وبعد هذه العملية يمكننا نقل
الصوت عبر الشبكة
كما قلنا سابقا فإن الدوت نيت لم تدعم أي من عمليات التقاط وعرض الصوت ،
لاكن لإجراء هذه العمليات لابد من استخدام مجموعة ملفات ال DLL والتي تأتي
مع نظام التشغيل ومنها ملف
winmm.dll الشهير ،
والخاص بالتعامل مع وسائل ال Multimedia في نظام التشغيل ، حيث يدعم هذا
الملف مجموعة من ال Methods لالتقاط الصوت عبر المايكروفون وتخزينه في Byte
Array Buffer ومن ثم عرضه مرة أخرى وهذه ال Method هي :
waveInGetNumDevs والتي تستخدم لتحديد عدد أجهزة الإدخال والمربوطة مع ال Sound Card ولا تأخذ أي باروميترات.
waveInAddBuffer وتستخدم لتخزين ال Bits الواردة من جهاز الإدخال في Byte Array Buffer وتأخذ هذه ال Method ثلاثة باروميترات وهي:
كود:
waveInAddBuffer(hwi As IntPtr , ByRef pwh As WaveHdr ,cbwh As integer)
حيث يمرر للأول جهاز الإدخال والذي تم اختياره و يحدد في الثاني Reference
لموقع تخزين ال Buffer وفي الثالث يحدد حجم ال Buffer المستلم
الميثود
waveInOpen و
waveInClose لفتح وإغلاق الاتصال مع جهاز الإدخال.
الميثود
waveInPrepareHeader لتجهيز وحجز ال Buffer وتأخذ نفس الباروميترات الموجودة في
waveInAddBuffer.
الميثود
waveInUnprepareHeader ويتم استدعائها بعد تعبئة ال Buffer حتى يتم إرسال ال Buffer ومن ثم تفريغه للاستعداد لتعبئته مرة أخرى.
الميثود
waveInStart و الميثود
waveInStop بدأ وإغلاق عملية الإدخال من المايكروفون.
ولاستخدام هذه الميثود في الدوت نيت نقوم بتعريفها أولا باستخدام DllImport وكما يلي: كود:
<DllImport("winmm.dll")> _
Public Shared Function waveInGetNumDevs() As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInAddBuffer(ByVal hwi As IntPtr, ByRef pwh As WaveHdr, ByVal cbwh As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInClose(ByVal hwi As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInOpen(<System.Runtime.InteropServices.Out()> ByRef phwi As IntPtr, ByVal uDeviceID As Integer, ByVal lpFormat As WaveFormat, ByVal dwCallback As WaveDelegate, ByVal dwInstance As Integer, ByVal dwFlags As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInPrepareHeader(ByVal hWaveIn As IntPtr, ByRef lpWaveInHdr As WaveHdr, ByVal uSize As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInUnprepareHeader(ByVal hWaveIn As IntPtr, ByRef lpWaveInHdr As WaveHdr, ByVal uSize As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInReset(ByVal hwi As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInStart(ByVal hwi As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveInStop(ByVal hwi As IntPtr) As Integer
End Function
وكما سوف نستخدم مجموعة ال Methods التالية لتحويل ال Byte Array Buffer إلى صوت مرة أخرى وعرضه على جهاز الإخراج :
كود:
<DllImport("winmm.dll")> _
Public Shared Function waveOutGetNumDevs() As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutPrepareHeader(ByVal hWaveOut As IntPtr, ByRef lpWaveOutHdr As WaveHdr, ByVal uSize As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutUnprepareHeader(ByVal hWaveOut As IntPtr, ByRef lpWaveOutHdr As WaveHdr, ByVal uSize As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutWrite(ByVal hWaveOut As IntPtr, ByRef lpWaveOutHdr As WaveHdr, ByVal uSize As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutOpen(<System.Runtime.InteropServices.Out()> ByRef hWaveOut As IntPtr, ByVal uDeviceID As Integer, ByVal lpFormat As WaveFormat, ByVal dwCallback As WaveDelegate, ByVal dwInstance As Integer, ByVal dwFlags As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutReset(ByVal hWaveOut As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutClose(ByVal hWaveOut As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutPause(ByVal hWaveOut As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutRestart(ByVal hWaveOut As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutGetPosition(ByVal hWaveOut As IntPtr, <System.Runtime.InteropServices.Out()> ByRef lpInfo As Integer, ByVal uSize As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutSetVolume(ByVal hWaveOut As IntPtr, ByVal dwVolume As Integer) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function waveOutGetVolume(ByVal hWaveOut As IntPtr, <System.Runtime.InteropServices.Out()> ByRef dwVolume As Integer) As Integer
End Function
البدء بإنشاء برنامج المحادثة الصوتية Voice Chat :سوف نجزئ عملية التقاط الصوت وتخزينه في ال Buffer ثم عرضه مرة أخرى في
مجموعة من ال Classes وهو تقسيم تم استخدامه في الكثير من البرمجيات
الخاصة ب Microsoft ومنها برنامج Windows Sound Recorder وهذه ال Classes
هي:
WaveIn Class وسوف نستخدمه لوضع كافة ال Methods الخاصة بالتقاط الصوت وتخزينه في Byte Array
WaveOut Clasas وسوف نستخدمه لعرض الصوت الأتي من ال Buffer ثم عرضه
WaveStream Class والذي سوف نستخدمه لتحويل الصوت إلى Stream حيث يسهل إرساله عبر الشبكة ويشبه عمله عمل MemoryStream المستخدمة في الدوت نيت
الميثود FifoStream لتنظيم ال Stream بحيث يتم عرض الداخل أولا خارج أولا
الميثود WaveNative ويتم فيها وضع آافة التعريفات لل Methods الخاصة بالملف winmm.dll والتي شرحناها سابقاّ.
سوف أستخدم في هذا المثال أداة الوينسوك (
Winsock) لعملية النقل وللبدء سوف يكون الشكل العام لبرنامج الاتصال كما يلي :
نقوم بتعريف ال Winsock وال Thread والذي سوف نستخدمه في البرنامج ويفضل
وضع هذه التعريفات في بداية البرنامج أي بعد تعريف ال Class الرئيسي والهدف
من هذه العملية هي القدرة على إغلاق ال Winsock وال Thread عند إطفاء
البرنامج وحتى لا تبقى في الذاآرة عند إغلاق برنامج الاتصال ، ويتم ذلك كما
يلي:
كود:
Public Class Form1
Public WithEvents WinsockIn As New MSWinsockLib.Winsock
Public WithEvents WinsockOut As New MSWinsockLib.Winsock
Public WithEvents Winsock As New MSWinsockLib.Winsock
Public Thread As Thread
وسوف نعرف Object من ال Classes السابقة ونعرف ال Buffer الذي سيتم تسجيل
الصوت المراد إرساله وال Buffer الذي سيتم عرض الصوت المستلم من ال Winsock
كود:
Public m_Player As WaveOutPlayer
Public m_Recorder As WaveInRecorder
Public m_Fifo As FifoStream = New FifoStream
Public m_PlayBuffer As Byte()
Public m_RecBuffer As Byte()
سوف نضع في ال
Voice_In Method الكود الخاص بعملية استقبال الصوت من ال Winsock وكما يلي:
كود:
<STAThread()> _
Private Sub Voice_In()
If WinsockIn.State = MSWinsockLib.StateConstants.sckConnected Then
Dim br As Byte()
While WinsockIn.State = MSWinsockLib.StateConstants.sckConnected
br = New Byte(16383) {}
WinsockIn.GetData(br)
m_Fifo.Write(br, 0, br.Length)
End While
End If
End Sub
حيث يتم استقبال الصوت من الشبكة باستخدام ال Receive Method ثم نمرر الصوت
المستقبل إلى ال m_Fifo.Write Method وحتى يتم تنفيذه وتحويله إلى صوت مرة
أخرى.
أما ال Method التي تقوم بتسجيل الصوت وإرساله إلى الجهاز الأخر فهي:
كود:
Private Sub Voice_Out(ByVal data As IntPtr, ByVal size As Integer)
If WinsockOut.State = MSWinsockLib.StateConstants.sckConnected Then
'for Recorder
If m_RecBuffer Is Nothing OrElse m_RecBuffer.Length < size Then
m_RecBuffer = New Byte(size - 1) {}
End If
System.Runtime.InteropServices.Marshal.Copy(data, m_RecBuffer, 0, size)
'Microphone ==> data ==> m_RecBuffer ==> m_Fifo
WinsockOut.SendData(m_RecBuffer)
End If
End Sub
لاحظ أنه في حالة إذا ما أردنا عمل برنامج Full Duplex
بحيث يرسل ويستقبل في نفس الوقت فإننا بحاجة إلى تعريف Tow Ports واحد
للإرسال وأخرى للاستقبال وفي الطرف الأخر تكون Port الإرسال لديك هي Port
لاستقبال لديه والعكس صحيح ...في زر البدء يتم تنفيذ الميثود التالية: كود:
Private Sub StartVoice()
StopVoice()
Try
Dim fmt As WaveFormat = New WaveFormat(44100, 16, 2)
m_Player = New WaveOutPlayer(-1, fmt, 16384, 3, New BufferFillEventHandler(AddressOf Filler))
m_Recorder = New WaveInRecorder(-1, fmt, 16384, 3, New BufferDoneEventHandler(AddressOf Voice_Out))
Detector.Start()
Catch ex As Exception
MsgBox(ex.ToString)
StopVoice()
' Throw ex.Message
End Try
End Sub
أما في زر الإيقاف فيتم تنفيذ الميثود التالية: كود:
Private Sub StopVoice()
Dim img As New Bitmap(Me.Width, Me.Height)
PnlMain.DrawToBitmap(img, PnlMain.Bounds)
Me.BackgroundImage = SetBrightness(ConvertImageColorToGray(img.Clone), BrightnessValue.Brightness_ToBlack_02)
PnlMain.Visible = False
If Not m_Player Is Nothing Then
Try
m_Player.Dispose()
Finally
m_Player = Nothing
End Try
End If
LblStopStatus.Text = "Stop Voice Please Wait... (50%)"
If Not m_Recorder Is Nothing Then
Try
m_Recorder.Dispose()
Finally
m_Recorder = Nothing
End Try
End If
Detector.Stop()
ProgHe.Value = 0
ProgYou.Value = 0
LblStopStatus.Text = "Stop Voice Please Wait... (100%)"
m_Fifo.Flush() ' clear all pending data
PnlMain.Visible = True
Me.BackgroundImage = Nothing
ProgHe.Value = 0
ProgYou.Value = 0
End Sub
الميثود التي تقوم بعرض ال Voice Buffer والمستلم من Winsock على السماعة:
كود:
Private Sub Filler(ByVal data As IntPtr, ByVal size As Integer)
If m_PlayBuffer Is Nothing OrElse m_PlayBuffer.Length < size Then
m_PlayBuffer = New Byte(size - 1) {}
End If
If m_Fifo.Length >= size Then
m_Fifo.Read(m_PlayBuffer, 0, size)
Else
Dim i As Integer = 0
Do While i < m_PlayBuffer.Length
m_PlayBuffer(i) = 0
i += 1
Loop
End If
System.Runtime.InteropServices.Marshal.Copy(m_PlayBuffer, 0, data, size)
' m_Fifo ==> m_PlayBuffer==> data ==> Speakers
End Sub
وسنستخدم الأداة
SoundDetector لكي نقوم بجلب قوة الصوت بحيث تقوم بفحص قوة الصوت عن طريق الإتصال بالدايركت إكس
والآن بالنسبة للمثال قم بتحميله من هنا
الأداة Winsock مرفقة مع المثال وآخر دعوانا أن الحمد لله رب العالمين
وصلى الله على سيدنا محمد وعلى أله وصحبه وسلم