8

我正在尝试创建一个 Speex Voip 客户端和服务器。我已经掌握了基础知识,并且通过 UDP 在本地计算机上工作正常。我正在使用 JSpeex 来实现可移植性。我正在寻找有关创建客户端和服务器的提示。你怎么认为?

JSpeex 库每次调用只能编码 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 应用程序通常每秒发送 50-100 个数据包。对于 8000Hz 的 uLaw 编码,这将导致数据包大小为 80-160 字节。这样做的原因是一些数据包不可避免地会被丢弃,并且您希望对接收器的影响尽可能小。因此,对于每个数据包 10 毫秒或 20 毫秒的音频数据,丢弃的数据包可能会导致小问题,但不会像丢失 2k 音频数据(约 250 毫秒)那么糟糕。

此外,对于较大的数据包大小,您必须在发送之前在发送方累积所有数据。因此,假设典型的网络延迟为 50 毫秒,每个数据包有 20 毫秒的音频数据,接收方至少在 70 毫秒内不会听到发送方所说的内容。现在想象一下当一次发送 250 毫秒的音频时会发生什么。发送者说话和接收者播放该音频之间将经过 270 毫秒。

用户似乎更能容忍这里和那里的数据包丢失,这导致音频质量低于标准,因为大多数电话的音频质量一开始就不是那么好。然而,用户也习惯于现代电话线路的极低延迟,因此引入甚至 250 毫秒的往返延迟可能会非常令人沮丧。

现在,就实现缓冲而言,我找到了一个使用队列的好策略(哎呀,在这里使用 .NET :)),然后将其包装在一个类中,该类跟踪队列中所需的最小和最大数据包数。使用严格的锁定,因为您很可能会从多个线程访问它。如果队列“触底”并且其中包含零个数据包(缓冲区欠载),请设置一个标志并返回 null,直到数据包计数达到您想要的最小值。但是,您的使用者必须检查是否返回 null 并且不要将任何内容排队到输出缓冲区中。或者,您的消费者可以跟踪最后一个数据包并重复将其排入队列,这可能会导致音频循环,但在某些情况下,这可能比静音“听起来”更好。您必须这样做,直到生产者将足够的数据包放入队列以达到最小值。这将为用户带来更长的沉默时间,但这通常比短暂的、频繁的沉默(断断续续)更容易被接受。如果您收到大量数据包并且生产者填满了队列(达到所需的最大值),您可以开始忽略新的数据包,或者从队列的前面丢弃足够的数据包以返回到最小值。

不过,选择那些最小/最大值很困难。您正在尝试平衡流畅的音频(无欠载)与发送方和接收方之间的最小延迟。VoIP 很有趣,但肯定会令人沮丧!祝你好运!

于 2010-10-14T13:49:21.177 回答