Why AudioInputStream read never returns -1? - java

I'm reading an AudioInputStream and using it to make a chunked POST request to a server in order to stream audio. I don't understand why read() method on the stream always returns 0, also after having called stop() and close() on TargetDataLine. I'm expecting at some point to have -1 since the stream is closed and there is no more data on it (EOF). This is causing me problems in Apache HTTP Client call that is making the POST, because at some point it expects -1 to terminate writing the output stream and in this way never terminates the write loop.
Here is the snippet:
public class MicrophoneTest {
// record duration, in milliseconds
static final long RECORD_TIME = 5000;
private static AudioFormat audioFormat = new AudioFormat(48000, 16, 1, true, false);
public static void main(String[] args) throws URISyntaxException, IOException {
final Microphone recorder = new Microphone(audioFormat);
recorder.open();
// creates a new thread that waits for a specified
// of time before stopping
Thread stopper = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(RECORD_TIME);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
recorder.close();
}
});
stopper.start();
// start recording
AudioInputStream inputStream = recorder.start();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1000];
int read = -1;
while ((read = inputStream.read(buffer)) != -1) {
System.out.println(read);
outputStream.write(buffer);
}
// Never gets here!
}
}
public class Microphone {
private static final Logger LOGGER = LoggerFactory.getLogger(Microphone.class);
// format of audio file
private static final AudioFileFormat.Type fileType = AudioFileFormat.Type.WAVE;
// the line from which audio data is captured
private TargetDataLine line;
private AudioFormat audioFormat;
public Microphone(AudioFormat audioFormat) {
this.audioFormat = audioFormat;
}
/**
* Prepare the line for recording
*
* #return
*/
public boolean open() {
try {
Info info = AudioSystem.getMixerInfo()[4];
line = (TargetDataLine) AudioSystem.getTargetDataLine(audioFormat, info);
line.open(audioFormat);
} catch (LineUnavailableException ex) {
LOGGER.error(ex.toString(), ex);
return false;
}
return true;
}
/**
* Captures the sound and return the stream
*/
public AudioInputStream start() {
if (line != null) {
line.start(); // start capturing
LOGGER.info("Start recording...");
return new AudioInputStream(line);
// AudioSystem.write(ais, fileType, outputStream);
} else {
throw new IllegalStateException("Line has not created. Cannot start recording");
}
}
/**
* Stops the recording process
*/
public void stop() {
line.stop();
LOGGER.info("Stop recording...");
}
/**
* Closes the target data line to finish capturing and recording *
*/
public void close() {
line.stop();
line.close();
LOGGER.info("Data line closed");
}
}

The microphone line never reaches the end of a file. It is either on or off. It is not reading a file from a disk or memory location.
I think you need to change your while loop to something like the following:
while(isRecording)
{
read = inputStream.read(buffer);
outputStream.write(buffer, 0, read);
}
and have your stop() method include
isRecording = false;
and your start() method include
isRecording = true;
etc. That sort of thing.

Related

How to change source data line for an audio stream?

I am trying to parse audio from a file onto my computer to a discord bot that can play the audio in a voice channel. I am unable to change where it is being sent to though and am unsure how to proceed.
public void play(String filePath) {
final File file = new File(filePath);
try (final AudioInputStream in = getAudioInputStream(file)) {
//gets the file type
final AudioFormat outFormat = getOutFormat(in.getFormat());
final Info info = new Info(SourceDataLine.class, outFormat);
// I think I want to edit this part to change where the sound is output to
try (final SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info)) {
if (line != null) {
line.open(outFormat);
line.start();
stream(getAudioInputStream(outFormat, in), line);
line.drain();
line.stop();
}
}
} catch (UnsupportedAudioFileException
| LineUnavailableException
| IOException e) {
throw new IllegalStateException(e);
}
}
and I want to parse the audio to connectedChannel
public void onSlashCommandInteraction(#NotNull SlashCommandInteractionEvent event) {
super.onSlashCommandInteraction(event);
if (event.getName().equals("raid")) {
OptionMapping option = event.getOption("raid");
if (option != null) {
OptionMapping raidChannel = event.getOption("raid");
OptionMapping audioNumber = event.getOption("audio");
if (raidChannel != null) {
AudioFilePlayer player = new AudioFilePlayer();
player.play("filepath");
System.out.println("Audio file finished!");
//Connecting to discord voice channel
AudioManager audioManager = event.getGuild().getAudioManager();
VoiceChannel connectedChannel = raidChannel.getAsVoiceChannel();
audioManager.openAudioConnection(connectedChannel);
event.getGuild().getAudioManager().closeAudioConnection();
event.reply("Raided!").setEphemeral(true).queue();
}
}
}
}
I can't seem to get it working.

capturing internal audio java

i will record the sound from my programs. I use Ubuntu 14.04 and PulseAudio.
Now i try to record from pulseaudio but currently i'm only recording from my microphone.
How can i record the sound from pulseaudio instead of my microphone?
public static void captureAudio() {
try {
final AudioFormat format = getFormat();
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
final TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
line.start();
Runnable runnable = new Runnable() {
int bufferSize = (int)format.getSampleRate() * format.getFrameSize();
byte buffer[] = new byte[bufferSize];
public void run() {
out = new ByteArrayOutputStream();
running = true;
try {
while (running) {
int count = line.read(buffer, 0, buffer.length);
if (count > 0) {
out.write(buffer, 0, count);
}
}
out.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
};
Thread captureThread = new Thread(runnable);
captureThread.start();
} catch (LineUnavailableException ex) {
ex.printStackTrace();
}
}
I tried some things to change this in my code:
Mixer mixer = AudioSystem.getMixer(null);
And then:
final TargetDataLine line = (TargetDataLine) mixer.getLine(info);
Hope anyone have a solution.
Greetings
Daniel
This problem cannot be solved from within Java alone. Java sees only the devices which are already there, as the following Java program demonstrates:
import javax.sound.sampled.*;
public class ListDevices {
public static void main(final String... args) throws Exception {
for (final Mixer.Info info : AudioSystem.getMixerInfo())
System.out.format("%s: %s %s %s %s%n", info, info.getName(), info.getVendor(), info.getVersion(), info.getDescription());
}
}
What you need to do is create a loopback device for your audio system. The following post shows how to do that: https://askubuntu.com/questions/257992/how-can-i-use-pulseaudio-virtual-audio-streams-to-play-music-over-skype The purpose was different, but it should be adaptable for your situation, as your situation seems simpler to me than the situation described in that post.
It should be possible to run those pactl commands from Java using Process.

how to call thread method

So I am quite new to java so be gentle.
I am trying to connect two phones by bluetooth. I created a socket but how should I use it? I mean, bluetooth tutorial says that I should call public void write(byte[] bytes) to send data, but how? I created button, assigned "onClick" method but what then? how should I call method that is in ConnectedThread form UI thread? Here is example from tutorial.
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
Message msg = mainHandler.obtainMessage();
Bundle bundle = new Bundle();
String btnTxt = "Socket aquizaired";
bundle.putString("myKey", btnTxt);
msg.setData(bundle);
mainHandler.sendMessage(msg);
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
// mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
and in the UI thread I have something like
private void sendSms (){ // method assigned to button "onClick"
// i want to send some text here, like "hello" or something...
//???????????????????????????
}
The examples in the android sdk have a very good bluetooth chat example, you should have a look at it.
public void write(byte[] out) {
ConnectedThread r;
synchronized (this) {
if (mState != STATE_CONNECTED) return;
r = mConnectedThread;
}
r.write(out);
}

How to notify PipedInputStream thread that PipedOutputStream thread has written last byte?

How to finish the work correctly at the output end of the pipe? I need the writing thread to terminate or do some other work, while the reading thread reads all written data up to end.
Should I close the pipe at the writing end or what?
UPDATE 1
I want to clarify... According to given answers, am I correct thinking that by-design pipes behavior does not suppose any graceful termination?
I.e. once opened, the only way to stop piping is to break the pipe?
Conventional streams expect end of the stream signal, when read() method returns -1. Am right thinking that this never happens with piped streams?
Yes, closing the PipedOutputStream results in a -1 on the PipedInputStream.
Looks pretty graceful to me! Here's my SSCCE:
import java.io.*;
import java.nio.charset.*;
public class SOPipe
{
public static void main(String[] args) throws Exception
{
PipedOutputStream os = new PipedOutputStream();
PipedInputStream is = new PipedInputStream(os);
ReaderThread readerThread = new ReaderThread(is);
WriterThread writerThread = new WriterThread(os);
readerThread.start();
writerThread.start();
readerThread.join();
writerThread.join();
System.out.println("Both Reader and Writer completed.");
System.out.println("Main method returning normally now.");
}
private static final Charset LATIN1 = Charset.forName("latin1");
public static class WriterThread extends Thread
{
private final PipedOutputStream _os;
public WriterThread(PipedOutputStream os)
{
_os = os;
}
public void run()
{
try
{
String msg = "Ceci n'est pas une pipe";
byte[] msgBytes = msg.getBytes(LATIN1);
System.out.println("WriterThread sending message: " + msg);
for(int i = 0; i < msgBytes.length; i++)
{
_os.write(msgBytes, i, 1);
System.out.println("WriterThread wrote a byte!");
_os.flush();
}
_os.close();
System.out.println("[COMPLETED] WriterThread");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public static class ReaderThread extends Thread
{
private final PipedInputStream _is;
public ReaderThread(PipedInputStream is)
{
_is = is;
}
public void run()
{
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1];
int read;
while ((read = _is.read(buffer, 0, 1)) != -1)
{
System.out.println("ReaderThread read a byte!");
baos.write(buffer, 0, read);
}
System.out.println("[COMPLETED] ReaderThread; received: "
+ new String(baos.toByteArray(), LATIN1));
_is.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
It is enough just to close output stream.
You can extend PipedOutputStream class & override its write() method to add your custom logic, after it has written all the bytes to the piped output stream.
public class CustomPipedOutput extends PipedOutputStream {
#Override
public void write(byte[] byteArray, int offset, int length){
super.write(byteArray, offset, length);
//-- Code to be executed after writing bytes
}
#Override
public void close(){
super.close();
//-- Code to be executed after closing piped input stream
}
}
Similarly, can extend other methods if required accordingly.

How to copy input/output streams of the Process to their System counterparts?

This is a follow up to this question. The answer suggested there is
to copy the Process out, err, and input streams to the System versions
with IOUtils.copy as follows (after fixing various compilation errors):
import org.apache.commons.io.IOUtils;
import java.io.IOException;
public class Test {
public static void main(String[] args)
throws IOException, InterruptedException {
final Process process = Runtime.getRuntime().exec("/bin/sh -i");
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(process.getInputStream(), System.out);
} catch (IOException e) {}
} } ).start();
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(process.getErrorStream(), System.err);
} catch (IOException e) {}
} } ).start();
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(System.in, process.getOutputStream());
} catch (IOException e) {}
} } ).start();
process.waitFor();
}
}
However, the resulting code doesn't work for interactive processes like the one executing sh -i command. In the latter case there is no response to any of the sh commands.
So my question is: could you suggest an alternative to copy the streams that will work with interactive processes?
The problem is that IOUtil.copy() is running while there is data in the InputStream to be copied. Since your process only produces data from time to time, IOUtil.copy() exits as it thinks there is no data to be copied.
Just copy data by hand and use a boolean to stop the thread form outside:
byte[] buf = new byte[1024];
int len;
while (threadRunning) { // threadRunning is a boolean set outside of your thread
if((len = input.read(buf)) > 0){
output.write(buf, 0, len);
}
}
This reads in chunks as many bytes as there are available on inputStream and copies all of them to output. Internally InputStream puts thread so wait() and then wakes it when data is available.
So it's as efficient as you can have it in this situation.
Process.getOutputStream() returns a BufferedOutputStream, so if you want your input to actually get to the subprocess you have to call flush() after every write().
You can also rewrite your example to do everything on one thread (although it uses polling to read both System.in and the process' stdout at the same time):
import java.io.*;
public class TestProcessIO {
public static boolean isAlive(Process p) {
try {
p.exitValue();
return false;
}
catch (IllegalThreadStateException e) {
return true;
}
}
public static void main(String[] args) throws IOException {
ProcessBuilder builder = new ProcessBuilder("bash", "-i");
builder.redirectErrorStream(true); // so we can ignore the error stream
Process process = builder.start();
InputStream out = process.getInputStream();
OutputStream in = process.getOutputStream();
byte[] buffer = new byte[4000];
while (isAlive(process)) {
int no = out.available();
if (no > 0) {
int n = out.read(buffer, 0, Math.min(no, buffer.length));
System.out.println(new String(buffer, 0, n));
}
int ni = System.in.available();
if (ni > 0) {
int n = System.in.read(buffer, 0, Math.min(ni, buffer.length));
in.write(buffer, 0, n);
in.flush();
}
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
}
}
System.out.println(process.exitValue());
}
}
You should instead use the ProcessBuilder.redirectOutput method & friends. Read more here

Categories