Search code examples
javac#audiotcp

In Java, receive audio data streaming from a C# app over TCP sockets and play streaming as it is received


I have some raw PCM data for audio in a C# app. I need to get it into a Java application and playing as audio.

As a first step, I want the Java application to play the audio immediately as it is being streamed in (not saving a temporary file and playing that, playing it as it is received).

I am having problems formatting the PCM audio data in C# properly and sending it to the Java application. I am currently just using a simple TCP socket system to do so, calling the C# app (made in Unity) the "client" and the Java app the "server."

On the Unity C# client, I am using NatMic to get the raw PCM data for the Mic. This function is called many times per second as data comes in:

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

I have a float array and two ints to describe the audio data.

How do I package this data up as bytes to be sent to Java?

I have a TCP set up working where I can send bytes to Java, and it seems to work fine when I test just sending simple string messages back and forth between the client and server.

But when I try to package up the audio and send it over, it never works. I always get an error like this:

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

Here is a snippet on the Java side of how I was trying to play the audio on the Java side as a test (when the client connects I run this once):

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

How should I be packaging the float[], int, and int to send to the Java server so it will recognize it as an audio stream?


Solution

  • First off, you will need to output the data as a SourceDataLine, not a Clip. The Clip cannot play until it has received the entire sound file.

    The usual plan is to read the input line in whatever manner you want and convert it to the proper format in a loop calling the sdl.write() method.

    Something along the following lines should work, for incoming data

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

    If your incoming data is floats, ranging from -1 to 1, and you are outputting in the basic "CD Quality" Java format (44100 fps, 16-bit, stereo, little endian), then the conversion might look like the following:

    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;
    

    Depending on the form of your PCM, you might have to do a bit more or less. For example, if you are reading ints or shorts, ranging from -32767 to 32767, then the multiplication step is not needed.

    To get a SourceDataLine outputting "CD Quality" audio format on the default audio line:

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

    As far as your getInputPCM method, I'm assuming you are able to present only PCM data in the stream and there is no need for further parsing. If you want to do the task of converting the PCM to bytes in C# prior to shipping, that is also perfectly valid.

    There is more written about various output formats on the Java Tutorial's Sampled Package trail.

    For the above, I'm mostly copying/editing code from my github project AudioCue, which is a sort of souped-up Clip.