Prośba o ocenę algorytmu serwera pośredniczącego między dwoma klientami do rozmowy w czasie rzeczywistym

0

Cześć. Po wielu próbach udało mi się uruchomić serwer pośredniczący miedzy dwoma klientami do rozmów głosowych za pomocą gniazd UDP. Niestety opóźnienia rzędu 1-2 sekund w sieci lokalnej dyskwalifikuje program do używalności. Zastanawiam się gdzie popełniłem błąd. Program na ten moment nie jest odporny na zerwanie połączenia. Proszę o ocenę i sugestie jak można by to lepiej zrobić. Czym różnią się profesjonalne programy typu Skype? Kod poniżej.

connect(socket, &QUdpSocket::readyRead, this, &server::socketReadyRead);

void server::socketReadyRead()
{
    QByteArray buffer; 
    
    while (socket->hasPendingDatagrams())                   // dopóki są datagramy do odbioru to pętla się kręci
    {
        buffer.resize(socket->pendingDatagramSize());       // buffer przyjmuje wielkość datagramu
        socket->readDatagram(buffer.data(), buffer.size(), &sender, &senderPort); 
        // odczytuje datagram i zapisuje adres IP oraz port do zmiennych

        if (!hasTwoClients)                                       // ten warunek wykonuje się jeśli tylko jeden klient wysyła datagramy
        {                        // gdy z dwóch źródeł przyjdą datagramy to warunek jest fałszywy (służy do "skomunikowania" dwóch klientów)
            if (clientOne.isNull())         // jeśli początkowa wartość nie została zmieniona oznacza to, że to pierwszy datagram
            {
                clientOne = sender;         // clientOne otrzymuje adres IP pierwszego datagramu
                clientOnePort = senderPort; // podobnie z portem
            }
            else if ((clientOne != sender) || (clientOnePort != senderPort))      // w innym wypadku clientOne jest już zapamiętany
            {                                                                     // sprawdzamy czy kolejny datagram to nie ten sam klient
                clientTwo = sender;                   // zapisujemy klienta drugiego
                clientTwoPort = senderPort;           // to samo z portem
                hasTwoClients = true;                 // warunek pierwszego if'a nie będzie prawdziwy
            }
        }
        else if (sender == clientOne)                                          // sprawdzamy czy IP nadawcy datagramu to clientOne
            socket->writeDatagram(buffer.data(), buffer.size(), clientTwo, clientTwoPort);     // jeśli tak to wysyłamy do clientTwo
        else if (sender == clientTwo)                                                          // podobnie w drugą stronę
            socket->writeDatagram(buffer.data(), buffer.size(), clientOne, clientOnePort);
    }
}
1
  1. nie UDP
  2. dane są spakowane.
  3. programy zazwyczaj są przygotowany dla listy klientów skype/teams/TeamViewer więc trzymają listę klientów i przekazują wszystkim oprócz wysyłającego, co pozwala znacznie skrócić program.
0
haracz napisał(a):
        else if (sender == clientOne)                                          
            socket->writeDatagram(buffer.data(), buffer.size(), clientTwo, clientTwoPort);     
        else if (sender == clientTwo)                                                      
            socket->writeDatagram(buffer.data(), buffer.size(), clientOne, clientOnePort);
    }
}

ja tylko mam pytanie odnośnie tego kawałka. Zakładasz, że rozmowa będzie prowadzona z dwoma klientami ? A co jeśli będziesz chciał rozmawiać z 10 na raz ?

0

Zacząłbym od podstaw czyli sprawdzenie/obliczenie jaki jest bitrate strumienia oraz jak duże są bufory i czy przypadkiem w nich nie są zawarte te 1-2sekundy opóźnień. Jeśli sieć byłaby zbyt wolna(w co nie uwierzę jeśli to jest lokalne) lub kod który tu pokazałeś działałby zbyt wolno to opóźnienie by narastało aż pojawiły by się różne artefakty(jako że to jest UDP to dodatkowo przy dużym transferze kolejność pakietów może się pomieszać). Można też zrobić prosty test czyli pominąć kod który tu pokazałeś i sprawdzić jak duże masz opóźnienia wewnętrzne w kliencie pomiędzy mikrofonem a głośnikiem.

0

Z kwestii którym warto się przyjrzeć, ja bym zbadał jak duże pakiety wysyłasz. Jeśli wiele małych to może stąd taki narzut.

0
Jedajo napisał(a):

Zacząłbym od podstaw czyli sprawdzenie/obliczenie jaki jest bitrate strumienia oraz jak duże są bufory i czy przypadkiem w nich nie są zawarte te 1-2sekundy opóźnień. Jeśli sieć byłaby zbyt wolna(w co nie uwierzę jeśli to jest lokalne) lub kod który tu pokazałeś działałby zbyt wolno to opóźnienie by narastało aż pojawiły by się różne artefakty(jako że to jest UDP to dodatkowo przy dużym transferze kolejność pakietów może się pomieszać). Można też zrobić prosty test czyli pominąć kod który tu pokazałeś i sprawdzić jak duże masz opóźnienia wewnętrzne w kliencie pomiędzy mikrofonem a głośnikiem.

enedil napisał(a):

Z kwestii którym warto się przyjrzeć, ja bym zbadał jak duże pakiety wysyłasz. Jeśli wiele małych to może stąd taki narzut.

Ten sam kod klienta tylko bez pośredniczącego serwera daje niezłe rezultaty. Opóźnienia są znośne. Pakiety jakie wysyłam to 5120 bajtów, sporadycznie 10240, w zależności ile "wychwyci" pętla. Dałem warunek, który pobiera z mikrofonu dane powyżej 5000 bajtów i wtedy wysyła. Zdaje sobie sprawę, że można zrobić to lepiej. To wersja testowa. Niestety przy braku połączenia, po stronie klienta nie jest emitowany sygnał readyRead() i głowiłem się jak to rozwiązać. Jak można by zrobić to lepiej? Obecny kod jest dość prymitywny.

    while(true)
    {
        if (input->bytesReady() > 5000)
        {
            buffer = inputDevice->read(input->bytesReady());
            socket->writeDatagram(buffer.data(), buffer.size(), QHostAddress("192.168.0.111"), 1234);

            buffer.resize(socket->pendingDatagramSize());
            socket->readDatagram(buffer.data(), buffer.size());
            outputDevice->write(buffer.data(), buffer.size());
        }
    }

Format jaki ustawiam dla obiektu QAudioInput i QAudioOutput to:

QAudioFormat format;
    format.setSampleRate(16000);
    format.setChannelCount(2);
    format.setSampleSize(32);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::UnSignedInt);
0

Wymagany transfer w przybliżeniu wynosi 1Mbps czyli na tyle mało aby pominąć wpływ czasu przesyłania. Nie znam qt, ale spróbuj wyczyścić bufor nagrywania przed samym while(true) czymś w stylu QAudioInput::reset().
Ogólnie to ciężko albo wręcz niemożliwe jest zrobienie dobrego komunikatora głosowego na UDP bez logiki oraz kanałów kontrolnych bo poza problemami z buforowaniem danych dochodzi wykrywanie zagubionych pakietów, unikanie trzasków na skutek pomieszanych pakietów itp.

1 użytkowników online, w tym zalogowanych: 0, gości: 1