目标:我有一个小型Android应用程序正在运行,它通过麦克风接受输入并实时流回 - 我试图修改它以便它将数据流式传输到PC上的Java服务器。
问题: 我得到的只是一个" tappy"一遍又一遍的噪音,或连续的哔声"声音(两个噪音的变化取决于我如何读取数据服务器端。)没有来自PC的声音。我意识到这很可能与我播放声音的方式有关,因为在Android设备上它能够准确地播放自己的声音。
代码: 在客户端和服务器端,只有一个类。
服务器如何接收并尝试播放数据:
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这样的东西,以及多个库,这是我唯一能够获得&#34; live&#34;回放。
我已经尝试了很多不同的方法,例如使用Clip和ObjectInputStream,但似乎没有任何工作。在这个版本中,我通过OutputStream通过ObjectOutputStream short[]
发送 byte[]
,并通过AudioInputStream读取,但我打开发送和读取任何类型的数据只要它有效。我尝试将short[]
转换为byte[]
然后播放,但它似乎没有帮助。
错误:
不报告任何实际错误(因为没有抛出异常)。 LogCat也没有错。(这已不再适用;请参阅编辑了解更多详情)
请注意,格式的唯一有效配置是:
AudioFormat format = new AudioFormat(8000, 16, 1, true, true); //louder
AudioFormat format = new AudioFormat(8000, 16, 1, true, false); //quieter
签名/无符号+ endian抛出异常的其他2种组合。
我还可以确认数据实际上是通过连接发送的 - 我使用wireshark进入它,我可以清楚地看到源和目标,以及正在发送的数据。我肯定会发送到正确的端口。
如果需要更多代码,请询问,我将编辑并加载。
TLDR;如何播放从Android设备发送到运行java服务器的计算机的声音?
编辑:经过进一步调查和代码修改后,声音仍未播放,但我发现了一些新问题。 Logcat现在抛出异常,服务器不断读取End Of Stream信号(-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)
据我所知,我没有关闭任何与套接字或套接字本身(服务器和客户端)相关的流。那么什么可能导致这个例外呢?只有当我&#34;停止&#34;录音 - 在从Android设备录制期间,播放工作正常。如果我删除out.write()
,我又回到接收完整循环功能(如我所说,我可以对设备说话,然后设备将播放它)。它还允许我开始&#34;或者&#34;停止&#34;在同一个会话中多次。不会抛出异常。将out.write()
放回代码后,我无法启动/停止多个会话。相反,在初始会话之后,抛出异常,并且下次我尝试&#34;启动&#34;录音,应用程序崩溃。
答案 0 :(得分:2)
乍一看,虽然我没有仔细检查你的代码或者亲自尝试过你的代码,但我可以看到你用ObjectOutputStream.writeObject()
编写数组,但是把它读作原始字节。
这肯定会引起问题。 ObjectOutputStream.writeObject()
用于序列化Java对象;它写的不仅仅是原始数据,即使对于数组也是如此。它编写了对象类,签名和其他一些东西,以便它可以被另一端的ObjectInputStream
反序列化(如果你很好奇,可以指定这种格式here)。
你有很多选择。我能想到的是:
请勿使用ObjectOutputStream.writeObject()
。而是将原始数据直接写入套接字(具有正确的endian-ness等,以匹配您的帧格式)。例如,您可以通过ByteBuffer
在ByteBuffer#asShortBuffer()
中缓冲数据(确保将endian-ness设置为匹配),然后将相应的字节数组写入套接字的输出流。这将是开销最低的方法。
创建一个将短缓冲区保存为成员的Serializable
类。用ObjectOutputStream
写它并在另一端用ObjectInputStream
读它(你需要在客户端和服务器上都有一个相同的类)。但是,与前一个选项一样,AudioInputStream
不能直接读取它。
您也可以考虑其他选项。如果ByteBuffer
太难以在您的代码中工作,那么您可以将每个短字分别写为一对字节;相同的结束效果(请注意,ShortBuffer.array()
将允许您以short[]
的形式访问缓冲区,这可能有助于简化集成。
答案 1 :(得分:2)
在将数据传输方法从ObjectOutputStream更改为使用ByteBuffer和普通流后(根据Jason C的答案),声音播放仍然没有变化。
如何修复声音的实际播放:
请注意AudioInputStream audIn = new AudioInputStream(y, format, 1);
中的最后一个参数是1;这意味着它只能读取1个样本帧的长度。 通过增加此值,我能够开始实际播放声音。 (我通过将audIn的声明更改为AudioInputStream audIn = new AudioInputStream(y, format, Integer.MAX_VALUE);
我嫌疑人可以确认这仍有其长度限制。因此,解决方案将是一个解决方案,使其成为一个连续的流&#34;而不是一个受长度限制的。有关如何制作连续流的更实用的解决方案,请参阅Java Length Unlimited AudioInputStream。