基于UDP协议实现P2P语音聊天系统(C#版本)

原创性申明

此博文的出处 为 http://blog.csdn.net/zhujunxxxxx/article/details/40124773如果进行转载请注明出处。本文作者原创,邮箱zhujunxxxxx@163.com,如有问题请联系作者

概述

之前发过一篇文章【C#使用UDP实现可靠的传输传输(数据包的分组发送)】以及【基于事件模型的UDP通讯框架(适用于网络包编解码)已经实现过了UDP的分包发送数据的功能,而这篇文章主要是一个应用,使用UDP协议传送语音和文本等信息。在这个系统中没有服务端和客户端,相互通讯都是直接相互联系的,能够很好的实现效果。

具体实现

1、语言数据源获取

要想发送语音信息,首先得获取语音,这里有几种方法,一种是使用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;
 public 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的录音和播放功能,其中用到的UDP传输类我放在了github上面 地址在我的博客左边的个人介绍里有地址  项目地址 https://github.com/zhujunxxxxx/ZZNetFrame希望这篇文章能够提供一个思路。


更新程序下载地址 http://download.csdn.net/detail/zhujunxxxxx/8061125 很好的代码,希望大家喜欢!


  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是一个基于 UDP 协议P2P 代码示例: ```csharp using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; class P2PExample { static void Main(string[] args) { // 创建一个新的 UdpClient 对象 UdpClient udpClient = new UdpClient(); // 获取本地 IP 地址 IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); // 创建一个 IPEndPoint 对象,用于指定要监听的 IP 地址和端口号 IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 8080); // 将 UdpClient 绑定到指定的 IPEndPoint 上 udpClient.Client.Bind(localEndPoint); Console.WriteLine("等待连接..."); // 创建一个新的线程来监听传入的数据包 Thread receiveThread = new Thread(() => { while (true) { // 接收来自其他客户端的数据 IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); byte[] buffer = udpClient.Receive(ref remoteEndPoint); string data = Encoding.ASCII.GetString(buffer); Console.WriteLine("{0}:{1} - {2}", remoteEndPoint.Address, remoteEndPoint.Port, data); } }); receiveThread.Start(); // 向其他客户端发送数据 while (true) { string message = Console.ReadLine(); byte[] messageBuffer = Encoding.ASCII.GetBytes(message); // 获取要发送的目标 IP 地址和端口号 IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081); // 发送数据包 udpClient.Send(messageBuffer, messageBuffer.Length, remoteEndPoint); } } } ``` 在上面的示例中,我们创建了一个 UdpClient 对象并将其绑定到本地 IP 地址和端口号。然后,我们创建一个新的线程来监听传入的数据包。一旦收到数据包,我们就可以从数据包中获取发送方的 IP 地址和端口号,以及发送方发送的数据。最后,我们通过 UdpClient 对象向其他客户端发送数据。 请注意,上面的示例只是一个简单的 P2P 通信示例,实际的 P2P 应用程序可能需要更复杂的逻辑来实现更高级的功能。并且此示例仅仅是在本机上模拟了两个客户端进行通信,如果要在不同的网络环境下进行 P2P 通信,需要考虑 NAT 网络下的穿透问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值