C#基于UDP实现的P2P语音聊天工具

 更新时间:2020年6月25日 11:26  点击:2105

语音获取

要想发送语音信息,首先得获取语音,这里有几种方法,一种是使用DirectX的DirectXsound来录音,我为了简便使用一个开源的插件NAudio来实现语音录取。 在项目中引用NAudio.dll

//------------------录音相关----------------------------- 
    private IWaveIn waveIn; 
    private WaveFileWriter writer; 
 
    private void LoadWasapiDevicesCombo() 
    { 
      var deviceEnum = new MMDeviceEnumerator(); 
      var devices = deviceEnum.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active).ToList(); 
      comboBox1.DataSource = devices; 
      comboBox1.DisplayMember = "FriendlyName"; 
    } 
    private void CreateWaveInDevice() 
    { 
 
      waveIn = new WaveIn(); 
      waveIn.WaveFormat = new WaveFormat(8000, 1); 
      waveIn.DataAvailable += OnDataAvailable; 
      waveIn.RecordingStopped += OnRecordingStopped; 
    } 
    void OnDataAvailable(object sender, WaveInEventArgs e) 
    { 
      if (this.InvokeRequired) 
      { 
        this.BeginInvoke(new EventHandler<WaveInEventArgs>(OnDataAvailable), sender, e); 
      } 
      else 
      { 
        writer.Write(e.Buffer, 0, e.BytesRecorded); 
        int secondsRecorded = (int)(writer.Length / writer.WaveFormat.AverageBytesPerSecond); 
        if (secondsRecorded >= 10)//最大10s 
        { 
          StopRecord(); 
        } 
        else 
        { 
          l_sound.Text = secondsRecorded + " s"; 
        } 
      } 
    } 
    void OnRecordingStopped(object sender, StoppedEventArgs e) 
    { 
      if (InvokeRequired) 
      { 
        BeginInvoke(new EventHandler<StoppedEventArgs>(OnRecordingStopped), sender, e); 
      } 
      else 
      { 
        FinalizeWaveFile(); 
      } 
    } 
    void StopRecord() 
    { 
      AllChangeBtn(btn_luyin, true); 
      AllChangeBtn(btn_stop, false); 
      AllChangeBtn(btn_sendsound, true); 
      AllChangeBtn(btn_play, true); 
 
      //btn_luyin.Enabled = true; 
      //btn_stop.Enabled = false; 
      //btn_sendsound.Enabled = true; 
      //btn_play.Enabled = true; 
      if (waveIn != null) 
        waveIn.StopRecording(); 
      //Cleanup(); 
    } 
 private void Cleanup() 
    { 
      if (waveIn != null) 
      { 
        waveIn.Dispose(); 
        waveIn = null; 
      } 
      FinalizeWaveFile(); 
    } 
    private void FinalizeWaveFile() 
    { 
      if (writer != null) 
      { 
        writer.Dispose(); 
        writer = null; 
      } 
    } 
  //开始录音 
    private void btn_luyin_Click(object sender, EventArgs e) 
    { 
      btn_stop.Enabled = true; 
      btn_luyin.Enabled = false; 
      if (waveIn == null) 
      { 
        CreateWaveInDevice(); 
      } 
      if (File.Exists(soundfile)) 
      { 
        File.Delete(soundfile); 
      } 
 
      writer = new WaveFileWriter(soundfile, waveIn.WaveFormat); 
      waveIn.StartRecording(); 
    } 

上面的代码实现了录音,并且写入文件p2psound_A.wav

语音发送

获取到语音后我们要把语音发送出去

当我们录好音后点击发送,这部分相关代码是

MsgTranslator tran = null; 
ublic Form1() 
    { 
      InitializeComponent(); 
      LoadWasapiDevicesCombo();//显示音频设备 
 
      Config cfg = SeiClient.GetDefaultConfig(); 
      cfg.Port = 7777; 
      UDPThread udp = new UDPThread(cfg); 
      tran = new MsgTranslator(udp, cfg); 
      tran.MessageReceived += tran_MessageReceived; 
      tran.Debuged += new EventHandler<DebugEventArgs>(tran_Debuged); 
    } 
 private void btn_sendsound_Click(object sender, EventArgs e) 
    { 
      if (t_ip.Text == "") 
      { 
        MessageBox.Show("请输入ip"); 
        return; 
      } 
      if (t_port.Text == "") 
      { 
        MessageBox.Show("请输入端口号"); 
        return; 
      } 
      string ip = t_ip.Text; 
      int port = int.Parse(t_port.Text); 
      string nick = t_nick.Text; 
      string msg = "语音消息"; 
 
      IPEndPoint remote = new IPEndPoint(IPAddress.Parse(ip), port); 
      Msg m = new Msg(remote, "zz", nick, Commands.SendMsg, msg, "Come From A"); 
      m.IsRequireReceive = true; 
      m.ExtendMessageBytes = FileContent(soundfile); 
      m.PackageNo = Msg.GetRandomNumber(); 
      m.Type = Consts.MESSAGE_BINARY; 
      tran.Send(m); 
    } 
 private byte[] FileContent(string fileName) 
    { 
      FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); 
      try 
      { 
        byte[] buffur = new byte[fs.Length]; 
        fs.Read(buffur, 0, (int)fs.Length); 
 
        return buffur; 
      } 
      catch (Exception ex) 
      { 
        return null; 
      } 
      finally 
      { 
        if (fs != null) 
        { 
 
          //关闭资源 
          fs.Close(); 
        } 
      } 
    } 

如此一来我们就把产生的语音文件发送出去了

语音的接收与播放

其实语音的接收和文本消息的接收没有什么不同,只不过语音发送的时候是以二进制发送的,因此我们在收到语音后 就应该写入到一个文件里面去,接收完成后,播放这段语音就行了。

下面这段代码主要是把收到的数据保存到文件中去,这个函数式我的NetFrame里收到消息时所触发的事件,在文章前面提过的那篇文章里

void tran_MessageReceived(object sender, MessageEventArgs e) 
    { 
      Msg msg = e.msg; 
 
      if (msg.Type == Consts.MESSAGE_BINARY) 
      { 
        string m = msg.Type + "->" + msg.UserName + "发来二进制消息!"; 
        AddServerMessage(m); 
        if (File.Exists(recive_soundfile)) 
        { 
          File.Delete(recive_soundfile); 
        } 
        FileStream fs = new FileStream(recive_soundfile, FileMode.Create, FileAccess.Write); 
        fs.Write(msg.ExtendMessageBytes, 0, msg.ExtendMessageBytes.Length); 
        fs.Close(); 
        //play_sound(recive_soundfile); 
        ChangeBtn(true); 
 
      } 
      else 
      { 
        string m = msg.Type + "->" + msg.UserName + "说:" + msg.NormalMsg; 
        AddServerMessage(m); 
      } 
    } 

收到语音消息后,我们要进行播放,播放时仍然用刚才那个插件播放

//--------播放部分---------- 
    private IWavePlayer wavePlayer; 
    private WaveStream reader; 
 
    public void play_sound(string filename) 
    { 
      if (wavePlayer != null) 
      { 
        wavePlayer.Dispose(); 
        wavePlayer = null; 
      } 
      if (reader != null) 
      { 
        reader.Dispose(); 
      } 
      reader = new MediaFoundationReader(filename, new MediaFoundationReader.MediaFoundationReaderSettings() { SingleReaderObject = true }); 
 
      if (wavePlayer == null) 
      { 
 
        wavePlayer = new WaveOut(); 
        wavePlayer.PlaybackStopped += WavePlayerOnPlaybackStopped; 
        wavePlayer.Init(reader); 
      } 
      wavePlayer.Play(); 
    } 
    private void WavePlayerOnPlaybackStopped(object sender, StoppedEventArgs stoppedEventArgs) 
    { 
      if (stoppedEventArgs.Exception != null) 
      { 
        MessageBox.Show(stoppedEventArgs.Exception.Message); 
      } 
      if (wavePlayer != null) 
      { 
        wavePlayer.Stop(); 
      } 
      btn_luyin.Enabled = true; 
    }private void btn_play_Click(object sender, EventArgs e) 
    { 
      btn_luyin.Enabled = false; 
      play_sound(soundfile); 
    } 

在上面演示了接收和发送一段语音消息的界面

技术总结

主要用到的技术就是UDP和NAudio的录音和播放功能

希望这篇文章能够给大家提供一个思路,帮助大家实现P2P语音聊天工具。

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25