简体   繁体   English

强制 Java Android Socket 立即发送数据

[英]Force Java Android Socket to Send Data Immediately

As a hobby project, I'm writing an android voip client.作为一个爱好项目,我正在编写一个 android voip 客户端。 When writing voice data to the socket (Vars.mediaSocket), many times, the data isn't immediately sent out over the wifi but just stalls and then all at once it will send 20 seconds worth of voice.将语音数据写入套接字 (Vars.mediaSocket) 时,很多时候,数据不会立即通过 wifi 发送出去,而是停止,然后它会立即发送 20 秒的语音。 Then it will stall again and wait for 30 seconds and then send 30 seconds of voice.然后它会再次停止并等待 30 秒,然后发送 30 秒的语音。 The wait is not consistent but after a while it will continuously send voice data immediately.等待是不一致的,但过了一会儿它会立即连续发送语音数据。 I've tried everything from using DataOutputStream to setting the socket output buffer size, setting the sendbuffer size huge, small, and lastly, buffering the voice data from its 32 byte chunks to anything from 128bytes to 32kb.我已经尝试了从使用 DataOutputStream 到设置套接字输出缓冲区大小、设置 sendbuffer 大小巨大、小以及最后将语音数据从 32 字节块缓冲到 128 字节到 32kb 的所有内容。

        Utils.logcat(Const.LOGD, encTag, "MediaCodec encoder thread has started");
        isEncoding = true;
        byte[] amrbuffer = new byte[32];
        short[] wavbuffer = new short[160];
        int outputCounter = 0;

        //setup the wave audio recorder. since it is released and restarted, it needs to be setup here and not onCreate
        wavRecorder = null; //remove pointer to the old recorder for safety
        wavRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLESWAV, AudioFormat.CHANNEL_IN_MONO, FORMAT, 160);
        wavRecorder.startRecording();
        AmrEncoder.init(0);

        while(!micMute)
        {
            int totalRead = 0, dataRead;
            while(totalRead < 160)
            {//although unlikely to be necessary, buffer the mic input
                dataRead = wavRecorder.read(wavbuffer, totalRead, 160 - totalRead);
                totalRead = totalRead + dataRead;
            }
            int encodeLength = AmrEncoder.encode(AmrEncoder.Mode.MR122.ordinal(), wavbuffer, amrbuffer);

            try
            {
                Vars.mediaSocket.getOutputStream().write(amrbuffer);
                Vars.mediaSocket.getOutputStream().flush();
            }
            catch (IOException i)
            {
                Utils.logcat(Const.LOGE, encTag, "Cannot send amr out the media socket");
                Utils.dumpException(tag, i);
            }

Is there something I'm missing?有什么我想念的吗? To simulate a second cell phone, I have another client which just simply reads the voice data, throws it away, and reads again in a loop.为了模拟第二部手机,我有另一个客户端,它只是简单地读取语音数据,将其丢弃,然后在循环中再次读取。 I can confirm in the simulated second cell phone when the real cell phone stops sending voice, the simulated one's socket.read hangs until the real one starts sending voice again.我可以在模拟的第二部手机中确认,当真实手机停止发送语音时,模拟的socket.read挂起,直到真实手机再次开始发送语音。

I'm really hoping not to have to write a jni for the socket as I don't know anything about that and was hoping I could write the app as a standard java app.我真的希望不必为套接字编写 jni,因为我对此一无所知,并希望我可以将应用程序编写为标准的 java 应用程序。

CASE CLOSED : turned out to be a server side bug but the simplifying back to basics suggestions is still a good idea. CASE CLOSED :原来是一个服务器端错误,但简化回到基础的建议仍然是一个好主意。

You are adding most of the latency yourself by reading large amounts of data before writing any of it.通过在写入任何数据之前读取大量数据,您自己增加了大部分延迟。 You should just use the standard Java copy loop:您应该只使用标准的 Java 复制循环:

byte[] buffer = new byte[8192];
int count;
while ((count = in.read(buffer)) > 0)
{
    out.write(buffer, 0, count);
}

You need to adapt this to incorporate your codec step.您需要对其进行调整以合并您的编解码器步骤。 Note that you don't need a buffer the size of the entire input.请注意,您不需要整个输入大小的缓冲区。 You can tune its size to suit yourself but 8192 is a good starting point.您可以调整其大小以适合自己,但 8192 是一个很好的起点。 You can increase it to say 32k but don't decrease it.您可以将其增加到 32k,但不要减少它。 If your codec needs the data in fixed-size chunks, use a buffer of that size and DataInputStream.readFully() .如果您的编解码器需要固定大小的块中的数据,请使用该大小的缓冲区和DataInputStream.readFully() But the larger the buffer the more the latency.但是缓冲区越大,延迟就越多。

EDIT Specific issues with your code:编辑您的代码的具体问题:

byte[] amrbuffer = new byte[AMRBUFFERSIZE];
byte[] outputbuffer = new byte [outputBufferSize];

Remove (see below).删除(见下文)。

short[] wavbuffer = new short[WAVBUFFERSIZE];
int outputCounter = 0;

Remove outputCounter .删除outputCounter

        //setup the wave audio recorder. since it is released and restarted, it needs to be setup here and not onCreate
        wavRecorder = null; //remove pointer to the old recorder for safety

Pointless.无意义。 Remove.消除。

        wavRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLESWAV, AudioFormat.CHANNEL_IN_MONO, FORMAT, WAVBUFFERSIZE);
        wavRecorder.startRecording();
        AmrEncoder.init(0);

OK.好的。

        try
        {
            Vars.mediaSocket.setSendBufferSize(outputBufferSize);
        }
        catch (SocketException e)
        {
            e.printStackTrace();
        }

Pointless.无意义。 Remove.消除。 The socket send buffer should be as large as possible.套接字发送缓冲区应尽可能大。 Unless you know that its default size is < outputBufferSize there is no benefit to this.除非您知道它的默认大小是 < outputBufferSize ,否则这样做没有任何好处。 In any case we are getting rid of outputBuffer altogether.无论如何,我们完全摆脱了outputBuffer

        while(!micMute)
        {
            int totalRead = 0, dataRead;
            while(totalRead < WAVBUFFERSIZE)
            {//although unlikely to be necessary, buffer the mic input
                dataRead = wavRecorder.read(wavbuffer, totalRead, WAVBUFFERSIZE - totalRead);
                totalRead = totalRead + dataRead;
            }
            int encodeLength = AmrEncoder.encode(AmrEncoder.Mode.MR122.ordinal(), wavbuffer, amrbuffer);

OK.好的。

            if(outputCounter == outputBufferSize)
            {
                Utils.logcat(Const.LOGD, encTag, "Sending output buffer");
                try
                {
                    Vars.mediaSocket.getOutputStream().write(outputbuffer);
                    Vars.mediaSocket.getOutputStream().flush();
                }
                catch (IOException i)
                {
                    Utils.logcat(Const.LOGE, encTag, "Cannot send amr out the media socket");
                    Utils.dumpException(tag, i);
                }
                outputCounter = 0;
            }
            System.arraycopy(amrbuffer, 0, outputbuffer, outputCounter, encodeLength);
            outputCounter = outputCounter + encodeLength;
            Utils.logcat(Const.LOGD, encTag, "Output buffer fill: " + outputCounter);

Remove all the above and substitute删除以上所有内容并替换

        Vars.mediaSocket.getOutputStream().write(amrbuffer, 0, encodeLength);

This also means you can get rid of 'outputBuffer' as promised.这也意味着您可以按照承诺摆脱“outputBuffer”。

NB Don't flush inside loops.注意不要冲洗内部循环。 As a matter of fact flushing a socket output stream does nothing, but the general principle still holds.事实上,刷新套接字输出流什么也没做,但一般原则仍然成立。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM