实现像语音服务器这样的teampeak

时间:2019-02-25 16:08:18

标签: winapi audio udp teamspeak

我正在实现一个语音聊天服务器,该服务器将在我的Windows虚拟课程电子学习应用程序中使用,该服务器使用Github documentation

到目前为止,我一直在使用OPUS压缩声音,并且已经测试了各种选项:

  1. 通过RDP Remote Desktop API传递声音。尽管可以使用CHANNEL_PRIORITY_HI创建频道,但这仍然有效,但仍会造成很多延迟。
  2. 使用我自己的TCP(或UDP)语音服务器。对于这个选项,我一直想知道什么是最佳实施方法。

目前,我正在将接收到的udp数据报发送给所有其他客户端(稍后,我将进行服务器端混合)。

我当前的UDP语音服务器存在的问题是,即使在同一台PC上也存在滞后:例如,一台服务器和四个客户端连接,其中两个具有开放式麦克风。

通过这种设置,我会听到可听见的延迟:

void VoiceServer(int port)
{
    XSOCKET Y = make_shared<XSOCKET>(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (!Y->Bind(port))
        return;

    auto VoiceServer2 = [&]() 
    {
        OPUSBUFF o;
        char d[200] = { 0 };
        map<int, vector<char>> udps;
        for (;;)
        {
            // get datagram
            int sle = sizeof(sockaddr_in6);
            int r = recvfrom(*Y, o.d, 4000, 0, (sockaddr*)d, &sle);
            if (r <= 0)
                break;

            // a MESSAGE is a header and opus data follows
            MESSAGE* m = (MESSAGE*)o.d;

            // have we received data from this client already?
            // m->arb holds the RDP ID of the user  
            if (udps.find(m->arb) == udps.end())
            {
                vector<char>& uu = udps[m->arb];
                uu.resize(sle);
                memcpy(uu.data(), d, sle);
            }

            for (auto& att2 : aatts) // attendee list
            {
                long lxid = 0;
                att2->get_Id(&lxid);
#ifndef _DEBUG
                if (lxid == m->arb) // if same
                    continue;
#endif
                const vector<char>& uud = udps[lxid];
                sendto(*Y, o.d + sizeof(MESSAGE), r - sizeof(MESSAGE), 0, (sockaddr*)uud.data(), uud.size());
            }
        }
    };

    // 10 threads receiving
    for (int i = 0; i < 9; i++)
    {
        std::thread t(VoiceServer2);
        t.detach();
    }
    VoiceServer2();

}

每个客户端运行一个VoiceServer线程:

void VoiceServer()
{
    char b[4000] = { 0 };
    vector<char> d2;
    for (;;)
    {
        int r = recvfrom(Socket, b, 4000, 0, 0,0);
        if (r <= 0)
            break;

        d2.resize(r);
        memcpy(d2.data(), b, r);

        if (audioin && wout)
            audioin->push(d2); // this pushes the buffer to a waveOut writing class
        SetEvent(hPlayEvent);
    }
}

这是因为我在同一台机器上进行测试吗?但是有了TeamSpeak客户,我过去就已经设置了,没有任何滞后。

感谢您的意见。

1 个答案:

答案 0 :(得分:0)

SendTo():

  

对于面向消息的套接字,必须注意不要超过   可以获取的基础子网的最大数据包大小   通过使用getsockopt来检索套接字选项的值   SO_MAX_MSG_SIZE。如果数据太长而无法自动通过   基础协议,则返回错误WSAEMSGSIZE且没有数据   被传输。

典型的IPv4标头为20个字节,而UDP标头为8个字节。 UDP数据包最大大小的理论限制(在Windows上)为65507字节(由以下公式确定:0xffff-20-8 = 65507)。实际上是发送这么大数据包的最佳方法吗? 如果我们将数据包大小设置得太大,则网络协议的底部会在IP层拆分数据包。这会占用大量网络带宽,导致延迟。

MTU(最大传输单位)实际上与链路层协议有关。由于以太网传输的电气限制,EthernetII帧DMAC + SMAC + Type + Data + CRC的结构每个以太网帧的最小大小为64字节,并且最大大小不能超过1518字节。对于小于或大于此限制的以太网帧,我们可以将其视为错误。由于以太网EthernetII的最大数据帧为1518字节,除了帧头14Bytes和帧尾CRC校验部分4Bytes外,数据域中仅剩下1500个字节。那是MTU。

在MTU为1500字节的情况下,如果希望IP层不拆分数据包,则UDP数据包的最大大小应为1500字节-IP报头(20字节)-UDP报头(8字节)= 1472字节。但是,由于Internet上的标准MTU值为576字节,因此建议在Internet上编程UDP时,应将sendto / recvfrom中的UDP数据长度控制在(576-8-20)548字节内。

您需要减少发送/接收的字节数,然后控制次数。