8

Speex Voip クライアントとサーバーを作成しようとしています。UDP を介してローカル マシンで基本的な機能が正常に動作するようになりました。移植性のためにJSpeexを使用しています。クライアントとサーバーの作成に関するヒントを探しています。あなたの考えは何ですか?

JSpeex ライブラリは 1 回の呼び出しで 320 バイトしかエンコードできないため、サーバーに送信されるパケットはごくわずかです (私の場合は ~244 バイト)。クライアントは、約 1 または 2 KB のエンコードされたデータの準備が整うまで待ってから送信した方がよいでしょうか。それとも、サーバーにパケットのバッファリングを処理させたほうがよいでしょうか?

また、データのバッファリングを実装する方法についての助けがあればいいでしょう。

私が持っているもののいくつかは、ローカル マシンで動作します。

クライアント:

public void run() {
    int nBytesToRead = (m_inputAudioFormat.getFrameSize() * 160);
    int nAvailable = 0;
    byte[] abPCMData = new byte[nBytesToRead];
    byte[] abSpeexData = null;
    UserSpeexPacket userSpeexPacket = new UserSpeexPacket("Xiphias3", "TheLounge", null, 0);

    while (m_captureThread != null) {
        nAvailable = m_line.available();
        if (nAvailable >= nBytesToRead) {
            int nBytesRead = m_line.read(abPCMData, 0, nBytesToRead);
            if (nBytesRead == -1) break;
            if (nBytesRead < nBytesToRead)
                Arrays.fill(abPCMData, nBytesRead, abPCMData.length, (byte) 0);
            abSpeexData = createSpeexPacketFromPCM(abPCMData, 0, abPCMData.length);
            //DatagramPacket packet = new DatagramPacket(abSpeexData, 0, abSpeexData.length, m_connection.getInetAddress(), m_nServerPort);
            userSpeexPacket.setSpeexData(abSpeexData);
            userSpeexPacket.incrementPacketNumber();
            DatagramPacket packet = UserSpeexPacket.userSpeexPacketToDatagramPacket(m_connection.getInetAddress(), m_connection.getPort(), userSpeexPacket);
            try {
                m_connection.send(packet);
            }
            catch(IOException iox) {
                System.out.println("Connection to server lost: " + iox.getMessage());
                break;
            }
        }
    }
    closeLine();
    disconnect();
}

public byte[] createSpeexPacketFromPCM(byte[] abPCMData, int nOffset, int nLength)
{
    byte[] abEncodedData = null;
    m_speexEncoder.processData(abPCMData, nOffset, nLength);
    abEncodedData = new byte[m_speexEncoder.getProcessedDataByteSize()];
    m_speexEncoder.getProcessedData(abEncodedData, 0);
    return abEncodedData;
}

サーバ:

    DatagramPacket packet = new DatagramPacket(new byte[2048], 0, 2048);
    byte[] abPCMData = null;
    long lPrevVolPrintTime = 0;

    while (m_bServerRunning) {
        try {
            m_serverSocket.receive(packet);
            //System.out.println("Packet size is " + packet.getData().length);
            //System.out.println("Got packet from " + packet.getAddress().getHostAddress());
            //abPCMData = decodeSpeexPacket(packet.getData(),  0, packet.getLength());
            UserSpeexPacket usp = UserSpeexPacket.datagramPacketToUserSpeexPacket(packet);
            abPCMData = decodeSpeexPacket(usp.getSpeexData(), 0, usp.getSpeexData().length);
            m_srcDataLine.write(abPCMData, 0, abPCMData.length);

            if (System.currentTimeMillis() >= (lPrevVolPrintTime + 500)) {
                //System.out.println("Current volume: " + AudioUtil.getVolumeLevelForPCM22050Hz16Bit1Channel(abPCMData, 0, abPCMData.length));
                lPrevVolPrintTime = System.currentTimeMillis();
            }
        }
        catch (IOException iox) {
            if (m_bServerRunning) {
                System.out.println("Server socket broke: " + iox.getMessage());
                stopServer();
            }
        }
    }

4

1 に答える 1

5

私は同様のプロジェクトに取り組んでいます。私が読んだすべてのことと個人的な経験から、あなたの最善の選択肢は、小さなデータで作業し、できるだけ早く送信することです. ジッターのバッファリングは受信側で行う必要があります。

VoIP アプリケーションが 1 秒あたり 50 ~ 100 パケットを送信するのは一般的です。8000Hz での uLaw エンコーディングの場合、パケット サイズは 80 ~ 160 バイトになります。これは、一部のパケットが必然的にドロップされるため、受信者への影響をできるだけ小さくしたいためです。したがって、パケットあたり 10 ミリ秒または 20 ミリ秒のオーディオ データの場合、ドロップされたパケットは小さな問題を引き起こす可能性がありますが、2k のオーディオ データ (約 250 ミリ秒) を失うほど悪くはありません。

さらに、大きなパケット サイズでは、送信する前に送信側ですべてのデータを蓄積する必要があります。そのため、一般的なネットワーク レイテンシが 50 ミリ秒で、パケットあたり 20 ミリ秒のオーディオ データがある場合、受信者は、送信者が言うことを最低 70 ミリ秒聞くことができません。250 ミリ秒のオーディオが一度に送信されるとどうなるか想像してみてください。送信者が話してから受信者がその音声を再生するまでに 270 ミリ秒かかります。

ほとんどの電話のオーディオ品質は最初からそれほど優れていないため、ユーザーはあちこちでパケット損失をより許容しているようです。ただし、ユーザーは最新の電話回線の遅延が非常に小さいことにも慣れているため、250 ミリ秒の往復遅延が発生すると、非常にイライラすることがあります。

ここで、バッファリングの実装に関しては、Queue を使用して (おっと、ここでは .NET を使用しています :))、それを Queue 内の必要な最小および最大パケット数を追跡​​するクラスにラップするという優れた戦略を見つけました。複数のスレッドからアクセスする可能性が高いため、厳密なロックを使用してください。キューが「底をつき」、パケットがゼロの場合 (バッファ アンダーラン)、フラグを設定し、パケット数が必要な最小値に達するまで null を返します。ただし、コンシューマーは null が返されることを確認する必要があり、出力バッファーに何もキューに入れないようにする必要があります。あるいは、消費者が最後のパケットを追跡し、それを繰り返しエンキューすることができます。これにより、オーディオのループが発生する可能性がありますが、場合によっては、無音よりも「聞こえる」ことがあります。プロデューサが最小値に達するのに十分なパケットをキューに入れるまで、これを行う必要があります。これにより、ユーザーの無音時間が長くなりますが、一般的には、短い頻繁な無音 (途切れ) よりも受け入れられます。パケットのバーストを取得し、プロデューサーがキューをいっぱいにする (目的の最大値に達する) 場合は、新しいパケットを無視し始めるか、キューの先頭から十分なパケットをドロップして最小値に戻すことができます。

ただし、これらの最小値/最大値を選択するのは困難です。送信者と受信者の間の遅延を最小限に抑えて、スムーズなオーディオ (アンダーランなし) のバランスを取ろうとしています。VoIP は楽しいですが、イライラすることもあります。幸運を!

于 2010-10-14T13:49:21.177 に答える