在Java中,通过TCP套接字从C#应用接收音频数据流,并在接收到流时播放流

时间:2019-05-02 17:22:51

标签: java c# audio tcp

我在C#应用程序中有一些原始PCM音频数据。我需要将其放入Java应用程序并作为音频播放。

第一步,我希望Java应用程序在流式传输音频时立即播放音频(而不是保存临时文件并播放,而是在接收到它时播放它)。

我在用C#正确格式化PCM音频数据并将其发送到Java应用程序时遇到问题。我目前仅使用一个简单的TCP套接字系统来执行此操作,将C#应用程序(由Unity制造)称为“客户端”,将Java应用程序称为“服务器”。

在Unity C#客户端上,我正在使用NatMic来获取Mic的原始PCM数据。随着数据的传入,该函数每秒被调用多次:

void IAudioProcessor.OnSampleBuffer(float[] sampleBuffer, int sampleRate, int channelCount, long timestamp)

我有一个float数组和两个用于描述音频数据的整数。

如何将这些数据打包为要发送给Java的字节?

我有一个TCP设置,可以在其中向Java发送字节,并且在我测试只是在客户端和服务器之间来回发送简单字符串消息时,它似乎可以正常工作。

但是,当我尝试打包音频并将其发送过来时,它永远无法工作。我总是会收到这样的错误:

javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream

下面是Java方面的一段代码,它说明了我如何尝试在Java端播放音频作为测试(当客户端连接时,我运行了一次):

    inputStream = client.getInputStream();
    bInputStream = new BufferedInputStream(inputStream);
    Clip clip = AudioSystem.getClip();
    audioStream = AudioSystem.getAudioInputStream(bInputStream);
    clip.open(audioStream);
    clip.start();

我应该如何将float [],int和int打包以发送到Java服务器,以便它将它识别为音频流?

1 个答案:

答案 0 :(得分:1)

首先,您需要将数据输出为SourceDataLine,而不是ClipClip在接收到完整的声音文件之前无法播放。

通常的计划是,以所需的任何方式读取输入行,并在调用sdl.write()方法的循环中将其转换为正确的格式。

对于传入的数据,以下几行应该适用

while(playerRunning)
{
    readBuffer = getInputPCM();
    audioBytes = convertToFollowAudioFormat(readBuffer);
    sourceDataLine.write(audioBytes, 0, sdlBufferSize);
}

如果您输入的数据是浮动的,范围是-1到1,并且您以基本的“ CD Quality” Java格式(44100 fps,16位,立体声,小字节序)输出,那么转换可能看起来像以下:

for (int i = 0, n = buffer.length; i < n; i++)
{
    buffer[i] *= 32767;
    audioBytes[i*2] = (byte) buffer[i];
    audioBytes[i*2 + 1] = (byte)((int)buffer[i] >> 8 );
}
return audioBytes;

根据PCM的形式,您可能需要做更多或更少的事情。例如,如果您正在读取从-32767到32767的整数或短裤,则不需要乘法步骤。

要在默认音频行上获得SourceDataLine输出“ CD Quality”音频格式,

AudioFormat audioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 
                44100, 16, 2, 4, 44100, false);
Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
SourceDataLine sdl = (SourceDataLine)AudioSystem.getLine(info);

就您的getInputPCM方法而言,我假设您只能在流中显示PCM数据,而无需进一步解析。如果要在运送前完成将PCM转换为C#中的字节的任务,那也完全有效。

在Java教程的Sampled Package跟踪中,有更多关于各种输出格式的文章。

对于上述情况,我主要是从github项目AudioCue复制/编辑代码,这是一种强大的Clip