有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java将从安卓设备接收的数据输出为声音

目标:我已经运行了一个小型安卓应用程序,它通过麦克风接受输入并实时流式传输回来——我正在尝试修改它,以便它将数据流式传输到我电脑上的java服务器

问题:我得到的只是一次又一次的“tappy”噪音,或者是连续的“嘟嘟”声(这两种噪音的变化取决于我读取数据服务器端的方式)电脑没有声音。我意识到这很可能与我播放声音的方式有关,因为在安卓设备上,它能够准确地播放自己的声音

代码: 在客户端和服务器端只有一个类

服务器接收和尝试播放数据的方式:

public static void main(String[] args) {
    try {
        ServerSocket x = new ServerSocket(6790);
        try {
            while (true) {
                System.out.println("started");

                Socket clientSocket = x.accept();

                System.out.println("Connected" + clientSocket.getInetAddress());
                InputStream y = clientSocket.getInputStream();
                // ObjectInputStream in = new ObjectInputStream(y);

                AudioFormat format = new AudioFormat(8000, 16, 1, true, true);

                AudioInputStream audIn = new AudioInputStream(y, format, 1);
                SourceDataLine speakers;
                try {
                    byte[] data = new byte[32];
                    int numBytesRead;
                    int CHUNK_SIZE = 1024;
                    int bytesRead = 0;
                    DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, format);
                    speakers = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
                    speakers.open(format);
                    speakers.start();
                    while (bytesRead < 100000) {
                        numBytesRead = audIn.read(data, 0, 16); //y.read(data, 0, 16);
                        bytesRead += numBytesRead;
                        System.out.println(numBytesRead);
                        //for immediate playback
                        if (numBytesRead > 0) {
                            speakers.write(data, 0, 16);
                            System.out.println("writing data");
                        }
                    }
                    speakers.drain();
                    speakers.close();
                } catch (LineUnavailableException e) {
                    e.printStackTrace();
                }    
            }
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

我如何从客户端录制和发送:

public void run() {
    try {
        Log.i("Audio", "Running Audio Thread");
        AudioRecord recorder = null;
        AudioTrack track = null;
        ObjectOutputStream out = new ObjectOutputStream(x.getOutputStream());
        short[][] buffers = new short[256][160];
        int ix = 0;

        /*
         * Initialize buffer to hold continuously recorded audio data, start recording, and start
         * playback.
         */

        int N = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        recorder = new AudioRecord(AudioSource.MIC, 8000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, N * 1);
        track = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, N * 1,
                AudioTrack.MODE_STREAM);
        recorder.startRecording();
        track.play();

        /*
         * Loops until something outside of this thread stops it.
         * Reads the data from the recorder and writes it to the audio track for playback.
         */
        while (!stopped) {
            Log.i("Map", "Writing new data to buffer");
            short[] buffer = buffers[ix++ % buffers.length];
            N = recorder.read(buffer, 0, buffer.length);

            // ensure endianess
            ByteBuffer bb = ByteBuffer.allocate(buffer.length * 2);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            bb.asShortBuffer().put(buffer);
            byte[] byteBuffer = bb.array();

            track.write(buffer, 0, buffer.length);

            out.write(byteBuffer);
        }

        /*
        * Frees the thread's resources after the loop completes so that it can be run again
        */
        recorder.stop();
        recorder.release();
        track.stop();
        track.release();
        Log.d("Exit", "Exiting thread");
    } catch (Throwable x) {
        Log.w("Audio", "Error reading voice audio", x);
    }

}

我非常清楚UDP通常用于语音流,但这是唯一一组接近正常工作的代码。在以前的版本中,我尝试过使用RTP和WebRTC以及多个库,这是唯一一个能够获得“实时”播放的版本

我已经尝试了很多不同的方法,比如使用Clip和ObjectInputStream,但似乎没有任何效果。在这个版本中,我通过OutputStream通过ObjectOutputStream发送short[]byte[],并通过AudioInputStream读取数据,但只要数据有效,我愿意发送和读取任何类型的数据。我尝试过将short[]转换为byte[]并播放,但似乎没有效果

错误:
不报告任何实际错误(如未引发异常)。日志中也没有任何错误(这不再正确;有关更多详细信息,请参阅编辑)

请注意,format的唯一工作配置是:

AudioFormat format = new AudioFormat(8000, 16, 1, true, true);  //louder
AudioFormat format = new AudioFormat(8000, 16, 1, true, false); //quieter

其他2种有符号/无符号+endian组合抛出异常

我还可以确认数据实际上是通过连接发送的——我使用wireshark接入它,我可以清楚地看到数据的来源和目的地,以及发送的数据。我肯定会发送到正确的端口

如果需要更多的代码,请询问,我将编辑并发布它

TLDR;如何播放从安卓设备发送到运行java服务器的计算机的声音

编辑:在进一步调查和修改代码后,声音仍然没有播放,但我发现了一些新问题。Logcat现在抛出一个异常,服务器不断读取流结束信号(-1)。我通过在屏幕上打印来确认

LogCat错误:

03-04 10:28:47.496: W/Audio(794): Error reading voice audio
03-04 10:28:47.496: W/Audio(794): java.net.SocketException: Socket closed
03-04 10:28:47.496: W/Audio(794):   at libcore.io.Posix.sendtoBytes(Native Method)
03-04 10:28:47.496: W/Audio(794):   at libcore.io.Posix.sendto(Posix.java:156)
03-04 10:28:47.496: W/Audio(794):   at libcore.io.BlockGuardOs.sendto(BlockGuardOs.java:177)
03-04 10:28:47.496: W/Audio(794):   at libcore.io.IoBridge.sendto(IoBridge.java:466)
03-04 10:28:47.496: W/Audio(794):   at java.net.PlainSocketImpl.write(PlainSocketImpl.java:508)
03-04 10:28:47.496: W/Audio(794):   at java.net.PlainSocketImpl.access$100(PlainSocketImpl.java:46)
03-04 10:28:47.496: W/Audio(794):   at java.net.PlainSocketImpl$PlainSocketOutputStream.write(PlainSocketImpl.java:270)
03-04 10:28:47.496: W/Audio(794):   at java.io.OutputStream.write(OutputStream.java:82)
03-04 10:28:47.496: W/Audio(794):   at com.javaorigin.rtpsender.MainActivity$Audio.run(MainActivity.java:126)

据我所知,我没有关闭任何与socket或socket本身(服务器和客户端)相关的流。那么是什么导致了这个异常呢?只有在我“停止”录制时才会引发此异常-在从安卓设备录制期间,播放效果良好。如果我删除out.write(),我将返回到接收完整循环功能(如中所示,我可以对设备讲话,设备将播放它)。它还允许我在同一个会话中多次“启动”或“停止”。不会抛出任何异常。将out.write()放回代码时,我无法启动/停止多个会话。相反,在初始会话之后,抛出异常,下次我尝试“启动”录制时,应用程序崩溃


共 (2) 个答案

  1. # 1 楼答案

    将ObjectOutputStream的数据传输方法更改为使用ByteBuffer和normal流(根据Jason C的回答中的建议)后,声音播放仍然没有变化

    如何修复声音的实际播放:

    注意AudioInputStream audIn = new AudioInputStream(y, format, 1);中的最后一个参数是1;这意味着它将只读取1个样本帧的长度通过增加该值,我可以开始实际播放声音。(我通过将audIn的声明更改为AudioInputStream audIn = new AudioInputStream(y, format, Integer.MAX_VALUE);进行测试)

    我怀疑可以确认它的长度仍然有限制。因此,解决方案将是一个变通办法,使其成为一个“连续流”,而不是一个受长度限制的流。参见Java Length Unlimited AudioInputStream了解如何形成连续流的更多固溶体

  2. # 2 楼答案

    乍一看,虽然我没有仔细检查过您的代码,也没有亲自尝试过您的代码,但我可以看到您正在使用ObjectOutputStream.writeObject()编写数组,而是将其作为原始字节读取

    这肯定会引起问题ObjectOutputStream.writeObject()用于序列化Java对象;它写入的不仅仅是原始数据,甚至对于数组也是如此。它编写对象类、签名和其他一些内容,以便另一端的ObjectInputStream可以对其进行反序列化(如果您好奇,可以指定此格式here

    你有很多选择。我能想到的是:

    • 不要使用ObjectOutputStream.writeObject()。相反,将原始数据直接写入套接字(使用正确的endian格式等来匹配帧格式)。例如,您可以通过ByteBuffer#asShortBuffer()将数据缓冲在ByteBuffer中(确保将endian ness设置为匹配),然后将相应的字节数组写入套接字的输出流。这将是开销最低的方法

    • 创建一些Serializable类,将短缓冲区作为成员保存。用ObjectOutputStream写它,另一端用ObjectInputStream读它(客户端和服务器中都需要一个相同的类)。不过,它不会像前面的选项那样直接用AudioInputStream读取

    你也可以考虑其他的选择。如果ByteBuffer太难了,无法写入代码,那么您可以将每个简短的代码单独写入一对字节;相同的结束效果(注意ShortBuffer.array()将允许您作为short[]访问缓冲区,这可能有助于简化集成)