帮助创建Speex Voip服务器和客户端

时间:2010-09-26 20:57:20

标签: java voip speex jspeex

我正在尝试创建一个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;
}

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

1 个答案:

答案 0 :(得分:5)

我正在开展一个类似的项目。从我阅读的所有内容和个人经验来看,您最好的选择是处理少量数据并尽快发送。您希望在接收器侧进行任何抖动缓冲。

VoIP应用程序通常每秒发送50-100个数据包。对于8000Hz的uLaw编码,这将导致80-160字节的分组大小。这样做的原因是一些数据包将不可避免地被丢弃,并且您希望对接收器的影响尽可能小。因此,对于每个数据包10ms或20ms的音频数据,丢弃的数据包可能会导致小的打嗝,但不会像丢失2k的音频数据那样糟糕(~250ms)。

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

用户似乎对这里和那里的数据包丢失更加宽容,这导致低于标准的音频质量,因为大多数电话的音频质量都不是很好。然而,用户也习惯于在现代电话电路上具有非常低的延迟,因此引入甚至250ms的往返延迟可能会非常令人沮丧。

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

虽然挑选那些最小/最大值很难。您正试图平衡平滑的音频(无欠载)和发送器与接收器之间的最小延迟。 VoIP很有趣但确实令人沮丧!祝你好运!