Implementation of a teamspeak like voice server

136 Views Asked by At

I'm implementing a voice chat server which will be used in my Virtual Class e-learning application for Windows, which makes use of the Remote Desktop API.

So far I 've been compressing the voice in with OPUS and I 've tested various options:

  1. To pass the voice through the RDP Virtual Channel. This works but it creates lots of lag despite the channel creation with CHANNEL_PRIORITY_HI.
  2. To use my own TCP (or UDP) voice server. For this option I have been wondering what would be the best method to implement.

Currently I 'm sending the udp datagram received, to all other clients (later on I will do server-side mixing).

The problem with my current UDP voice server is that is has lag even within the same pc: One server, and four clients connected, two of them have open mics, for example.

I get audible lag with this setup:

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();

}

Each client runs a VoiceServer thread:

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);
    }
}

Is this because I test in the same machine? But with a TeamSpeak client I had setup in the past there is no lag whatsoever.

Thanks for your opinion.

1

There are 1 best solutions below

0
Drake Wu On

SendTo():

For message-oriented sockets, care must be taken not to exceed the maximum packet size of the underlying subnets, which can be obtained by using getsockopt to retrieve the value of socket option SO_MAX_MSG_SIZE. If the data is too long to pass atomically through the underlying protocol, the error WSAEMSGSIZE is returned and no data is transmitted.

A typical IPv4 header is 20 bytes, and the UDP header is 8 bytes. The theoretical limit (on Windows) for the maximum size of a UDP packet is 65507 bytes(determined by the following formula: 0xffff - 20 - 8 = 65507). Is it actually the best way to send such a large packet? If we set a packet size too large, bottom of network protocol will splits packets at the IP layer. This takes up a lot of network bandwidth, cause the delay.

MTU(maximum transmission unit), is actually related to the link layer protocol. The structure of the EthernetII frame DMAC+SMAC+Type+Data+CRC has a minimum size of 64 bytes per Ethernet frame due to the electrical limitations of Ethernet transmission, and the maximum size can not exceed 1518 bytes. For Ethernet frames less than or greater than this limitation, we can regard it as a mistake. Since the largest data frame of Ethernet EthernetII is 1518 bytes, except for the frame header 14Bytes and the frame tail CRC check part 4Bytes, there is only 1500 bytes in the data domain left. That's MTU.

In the case that the MTU is 1500 bytes, the maximum size of UDP packet should be 1500 bytes - IP header (20 bytes) - UDP header (8 bytes) = 1472 bytes if you want IP layer not to split packets. However, since the standard MTU value on the Internet is 576 bytes, it is recommended that UDP data length should be controlled within (576-8-20) 548 bytes in a sendto/recvfrom when programming UDP on the Internet.

You need to reduce the bytes of a send/receive and then control the number of times.