1

I have a callback that gets incoming audio data as FloatBuffer containing 1024 floats that gets called several times per second. But I need an AudioInputStream since my system only works with them.

Converting the floats into 16bit PCM isgned audio data is not a problem, but I cannot create an InputStream out of it. The AudioInputStream constructor only accepts data with known length, but I have a constant stream. The AudioSystem.getAudioInputStream throws an "java.io.IOException: mark/reset not supported" if I feed it with a PipedInputStream containing the audio data.

Any ideas?


Here's my current code:

Jack jack = Jack.getInstance();
JackClient client = jack.openClient("Test", EnumSet.noneOf(JackOptions.class), EnumSet.noneOf(JackStatus.class));
JackPort in = client.registerPort("in", JackPortType.AUDIO, EnumSet.of(JackPortFlags.JackPortIsInput));

PipedInputStream pin = new PipedInputStream(1024 * 1024 * 1024);
PipedOutputStream pout = new PipedOutputStream(pin);
client.setProcessCallback(new JackProcessCallback() {
public boolean process(JackClient client, int nframes) {
    FloatBuffer inData = in.getFloatBuffer();
    byte[] buffer = new byte[inData.capacity() * 2];
    for (int i = 0; i < inData.capacity(); i++) {
        int sample = Math.round(inData.get(i) * 32767);
        buffer[i * 2] = (byte) sample;
        buffer[i * 2 + 1] = (byte) (sample >> 8);
    }
    try {
        pout.write(buffer, 0, buffer.length);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return true;
}
});
client.activate();
client.transportStart();

Thread.sleep(10000);
client.transportStop();
client.close();

AudioInputStream audio = AudioSystem.getAudioInputStream(new BufferedInputStream(pin, 1024 * 1024 * 1024));
AudioSystem.write(audio, Type.WAVE, new File("test.wav"));

It uses the JnaJack library, but it doesn't really matter where the data comes from. The conversion to bytes is fine by the way: writing that data directly to a SourceDataLine will work correctly. But I need the data as AudioInputStream.

piegames
  • 975
  • 12
  • 31
  • Wrap the PipedInputStream in a BufferedInputStream. – VGR Apr 10 '18 at 13:26
  • @VGR `java.io.IOException: Resetting to invalid mark` – piegames Apr 10 '18 at 13:33
  • Why do you need to construct `AudioInputStream` since you can get it with a call to `AudioSystem.getAudioInputStream`? if `mark` and `reset` are not supported, don't use them. – dsp_user Apr 10 '18 at 13:42
  • 1
    I can't get an `AudioInputStream` by calling `AudioSystem.getAudioInputStream` since this throws the exceptions mentioned above. I don't need mark and reset, but `getAudioInputStream(InpuStream)` apparently does. – piegames Apr 10 '18 at 13:45
  • Try a bigger buffer, like one megabyte: `new BufferedInputStream(pipedInputStream, 1024 * 1024)` – VGR Apr 10 '18 at 13:51
  • @VGR The bigger buffer helps, but: `javax.sound.sampled.UnsupportedAudioFileException: could not get audio input stream from input stream` Apparently, just throwing audio data into a stream is not enough to make it an audio stream. – piegames Apr 10 '18 at 16:45
  • To solve that, we’ll need to see the code that creates the AudioInputStream. I suspect you will need to forego using an AudioInputStream, and instead write the data directly to a SourceDataLine. – VGR Apr 10 '18 at 17:05
  • 1
    It turns out [the other constructor of AudioInputStream](https://docs.oracle.com/javase/9/docs/api/javax/sound/sampled/AudioInputStream.html#AudioInputStream-javax.sound.sampled.TargetDataLine-) has the answer: the length given to a constructor can be [AudioSystem.NOT_SPECIFIED](https://docs.oracle.com/javase/9/docs/api/javax/sound/sampled/AudioSystem.html#NOT_SPECIFIED). – VGR Apr 10 '18 at 20:38
  • @VGR This solves it. I'd never thought this would work and I haven't found any documentation telling me that this is possible. That's why you always should have a look at the source code. Put this as answer and I'll accept it. – piegames Apr 10 '18 at 21:22

1 Answers1

1

AudioSystem.getAudioInputStream expects a stream which conforms to a supported AudioFileFormat, which means it must conform to a known type. From the documentation:

The stream must point to valid audio file data.

And also from that documentation:

The implementation of this method may require multiple parsers to examine the stream to determine whether they support it. These parsers must be able to mark the stream, read enough data to determine whether they support the stream, and reset the stream's read pointer to its original position. If the input stream does not support these operation, this method may fail with an IOException.

You can create your own AudioInputStream using the three-argument constructor. If the length is not known, it can specified as AudioSystem.NOT_SPECIFIED. Frustratingly, neither the constructor documentation nor the class documentation mentions this, but the other constructor’s documentation does.

VGR
  • 40,506
  • 4
  • 48
  • 63
  • I need a pipe size of at least 64kiB and a buffer size of at least 128MiB(!) for this to work, otherwise I won't get any sound. Any idea why this needs to be so big or how to reduce the buffer? I am transporting 48kHz at 1024B/s – piegames Apr 11 '18 at 18:36
  • Are you still using AudioSystem.getAudioInputStream? That method needs a buffer, but if you avoid that method and just construct an AudioInputStream directly, you shouldn’t need any buffer at all. With the caveat, of course, that if reading stalls, the sound won’t play cleanly. But you shouldn’t get any exceptions. – VGR Apr 11 '18 at 18:51