How can I control the mic on-off function using java code? I need to control the time for which mic is on.
I tried using the following code in java:
final AudioFormat format = getFormat();//getformat() has the audio format
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
final TargetDataLine line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format); //open mic for input
line.start();
byte[] buffer = new byte[1048576];
OutputStream out = new ByteArrayOutputStream();//output the audio to buffer
boolean running = true;
try {
while (running) {
int count = line.read(buffer, 0, buffer.length);
running=false;
if (count > 0) {
out.write(buffer, 0, count);
}
}
out.close();
} catch (IOException e) {
System.out.println("Error");
System.err.println("I/O problems: " + e);
System.exit(-1);
}
But this basically depends on the the size of buffer. And the while loop can input audio for 30secs per pass.
I need to take the sample inputs for just 10secs.
any help?? thanks.:)
It seems you are trying to control the duration of the miking via the size of the buffer. I'm pretty sure this isn't common practice. Usually one uses a buffer that is a fraction of a second in size (to keep latency low), and iterates through it repeatedly. To control the duration of an open-ended read or playback operation, it is more usual to change the value of the "running" boolean.
Thus, from outside of the loop, code updates the "running" boolean, and when the loop notices that there has been a request to stop, the read loop ends.
I'm not up on specifics as to how one gets permission to turn on a mike or not. I know the java sound tutorials talk about it.
http://docs.oracle.com/javase/tutorial/sound/capturing.html
In their example, they use a boolean "stopped" to control when to end the recording loop.
Related
I'm writing a function to capture an audio clip for ~ 7.5 seconds using a TargetDataLine. The code executes and renders an 'input.wav' file, but when I play it there is no sound.
My approach, as shown in the code at the bottom of this post, is to do the following things:
Create an AudioFormat and get the Info for a Target Data Line.
Create the Target Data Line by getting the line from AudioSystem.
Open and Start the TargetDataLine, which allocates system resources for recording.
Create an auxiliary Thread that will record audio by writing to a file.
Start the auxiliary Thread, pause the main Thread in the meantime, and then close out the Target Data Line in order to stop recording.
What I have tried so far:
Changing the AudioFormat. Initially, I was using the other AudioFormat constructor which takes the file type as well (where the first argument is AudioFormat.Encoding.PCM_SIGNED etc). I had a sample rate of 44100, 16 bits, 2 channels and small-Endian settings on the other format, which yielded the same result.
Changing the order of commands on my auxiliary and main Thread (i.e. performing TLine.open() or start() in alternate locations).
Checking that my auxiliary thread does actually start.
For reference I am using IntelliJ on a Mac OS Big Sur.
public static void captureAudio() {
try {
AudioFormat f = new AudioFormat(22050, 8, 1, false, false);
DataLine.Info secure = new DataLine.Info(TargetDataLine.class, f);
if (!AudioSystem.isLineSupported(secure)) {
System.err.println("Unsupported Line");
}
TargetDataLine tLine = (TargetDataLine)AudioSystem.getLine(secure);
System.out.println("Starting recording...");
tLine.open(f);
tLine.start();
File writeTo = new File("input.wav");
Thread t = new Thread(){
public void run() {
try {
AudioInputStream is = new AudioInputStream(tLine);
AudioSystem.write(is, AudioFileFormat.Type.WAVE, writeTo);
} catch(IOException e) {
System.err.println("Encountered system I/O error in recording:");
e.printStackTrace();
}
}
};
t.start();
Thread.sleep(7500);
tLine.stop();
tLine.close();
System.out.println("Recording has ended.");
} catch(Exception e) {
e.printStackTrace();
}
}
Update 1: Some new testing and results
My microphone and speakers are both working with other applications - recorded working audio with QuickTimePlayer.
I did a lot of testing around what my TargetDataLines are and what the deal is with them. I ran the following code:
public static void main(String[] args) {
AudioFormat f = new AudioFormat(48000, 16, 2, true, false);
//DataLine.Info inf = new DataLine.Info(SourceDataLine.class, f);
try {
TargetDataLine line = AudioSystem.getTargetDataLine(f);
DataLine.Info test = new DataLine.Info(TargetDataLine.class, f);
TargetDataLine other = (TargetDataLine)AudioSystem.getLine(test);
String output = line.equals(other) ? "Yes" : "No";
if (output.equals("No")) {
System.out.println(other.toString());
}
System.out.println(line.toString());
System.out.println("_______________________________");
for (Mixer.Info i : AudioSystem.getMixerInfo()) {
Line.Info[] tli = AudioSystem.getMixer(i).getTargetLineInfo();
if (tli.length != 0) {
Line comp = AudioSystem.getLine(tli[0]);
System.out.println(comp.toString() + ":" +i.getName());
if (comp.equals(line) || comp.equals(other)) {
System.out.println("The TargetDataLine is from " + i.getName());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
Long story short, the TargetDataLine I receive from doing
TargetDataLine line = AudioSystem.getTargetDataLine(f); and
TargetDataLine other = (TargetDataLine)AudioSystem.getLine(new DataLine.Info(TargetDataLine.class, f));
are different, and furthermore, don't match any of the TargetDataLines that are associated with my system's mixers.
The output of the above code was this (where there first lines are other and line respectively):
com.sun.media.sound.DirectAudioDevice$DirectTDL#cc34f4d
com.sun.media.sound.DirectAudioDevice$DirectTDL#17a7cec2
_______________________________
com.sun.media.sound.PortMixer$PortMixerPort#79fc0f2f:Port MacBook Pro Speakers
com.sun.media.sound.PortMixer$PortMixerPort#4d405ef7:Port ZoomAudioDevice
com.sun.media.sound.DirectAudioDevice$DirectTDL#3f91beef:Default Audio Device
com.sun.media.sound.DirectAudioDevice$DirectTDL#1a6c5a9e:MacBook Pro Microphone
com.sun.media.sound.DirectAudioDevice$DirectTDL#37bba400:ZoomAudioDevice
Upon this realization I manually loaded up all the TargetDataLines from my mixers and tried recording audio with each of them to see if I got any sound.
I used the following method to collect all the TargetDataLines:
public static ArrayList<Line.Info> allTDL() {
ArrayList<Line.Info> all = new ArrayList<>();
for (Mixer.Info i : AudioSystem.getMixerInfo()) {
Line.Info[] tli = AudioSystem.getMixer(i).getTargetLineInfo();
if (tli.length != 0) {
for (int f = 0; f < tli.length; f += 1) {
all.add(tli[f]);
}
}
}
return all;
}
My capture/record audio method remained the same, except for switching the format to AudioFormat f = new AudioFormat(48000, 16, 2, true, false);, changing the recording time to 5000 milliseconds, and writing the method header as public static void recordAudio(Line.Info inf) so I could load each TargetDataLine individually with it's info.
I then executed the following code to rotate TargetDataLines:
public static void main(String[] args) {
for (Line.Info inf : allTDL()) {
recordAudio(inf);
try {
Thread.sleep(5000);
} catch(Exception e) {
e.printStackTrace();
}
if (!soundless(loadAsBytes("input.wav"))) {
System.out.println("The recording with " + inf.toString() + " has sound!");
}
System.out.println("The last recording with " + inf.toString() + " was soundless.");
}
}
}
The output was as such:
Recording...
Was unable to cast com.sun.media.sound.PortMixer$PortMixerPort#506e1b77 to a TargetDataLine.
End recording.
The last recording with SPEAKER target port was soundless.
Recording...
Was unable to cast com.sun.media.sound.PortMixer$PortMixerPort#5e9f23b4 to a TargetDataLine.
End recording.
The last recording with ZoomAudioDevice target port was soundless.
Recording...
End recording.
The last recording with interface TargetDataLine supporting 8 audio formats, and buffers of at least 32 bytes was soundless.
Recording...
End recording.
The last recording with interface TargetDataLine supporting 8 audio formats, and buffers of at least 32 bytes was soundless.
Recording...
End recording.
The last recording with interface TargetDataLine supporting 14 audio formats, and buffers of at least 32 bytes was soundless.
TL;DR the audio came out soundless for every TargetDataLine.
For completeness, here are the soundless and loadAsBytes functions:
public static byte[] loadAsBytes(String name) {
assert name.contains(".wav");
ByteArrayOutputStream out = new ByteArrayOutputStream();
File retrieve = new File("src/"+ name);
try {
InputStream input = AudioSystem.getAudioInputStream(retrieve);
int read;
byte[] b = new byte[1024];
while ((read = input.read(b)) > 0) {
out.write(b, 0, read);
}
out.flush();
byte[] full = out.toByteArray();
return full;
} catch(UnsupportedAudioFileException e) {
System.err.println("The File " + name + " is unsupported on this system.");
e.printStackTrace();
} catch (IOException e) {
System.err.println("Input-Output Exception on retrieval of file " + name);
e.printStackTrace();
}
return null;
}
static boolean soundless(byte[] s) {
if (s == null) {
return true;
}
for (int i = 0; i < s.length; i += 1) {
if (s[i] != 0) {
return false;
}
}
return true;
}
I'm not really sure what the issue could be at this point save for an operating system quirk that doesn't allow Java to access audio lines, but I do not know how to fix that - looking at System Preferences there isn't any obvious way to allow access. I think it might have to be done with terminal commands but also not sure of precisely what commands I'd have to execute there.
I'm not seeing anything wrong in the code you are showing. I haven't tried testing it on my system though. (Linux, Eclipse)
It seems to me your code closely matches this tutorial. The author Nam Ha Minh is exceptionally conscienscious about answering questions. You might try his exact code example and consult with him if his version also fails for you.
But first, what is the size of the resulting .wav file? Does the file size match the amount of data expected for the duration you are recording? If so, are you sure you have data incoming from your microphone? Nam has another code example where recorded sound is progressively read and placed into memory. Basically, instead of using the AudioInputStream as a parameter to the AudioSystem.write method, you execute multiple read method calls on the AudioInputStream and inspect the incoming data directly. That might be helpful for trouble-shooting whether the problem is occurring on the incoming vs outgoing part of the process.
I'm not knowledgeable enough about formats to know if the Mac does things differently. I'm surprised you are setting the format to unsigned. For my limited purposes, I stick with "CD quality stereo" and signed PCM at all junctures.
EDIT: based on feedback, it seems that the problem is that the incoming line is not returning data. From looking at other, similar tutorials, it seems that several people have had the same problem on their Mac systems.
First thing to verify: does your microphone work with other applications?
As far as next steps, I would try verifying the chosen line. The lines that are exposed to java can be enumerated/inspected. The tutorial Accessing Audio System Resources has some basic information on how to do this. It looks like AudioSystem.getMixerInfo() will return a list of available mixers that can be inspected. Maybe AudioSystem.getTargetLineInfo() would be more to the point.
I suppose it is possible that the default Line or Port being used when you obtain a TargetDataLine isn't the one that is running the microphone. If a particular line or port turns out to be the one you need, then it can be specified explicitly via an overridden getTargetDataLine method.
I'm reading that there might be a security policy that needs to be handled. I don't fully understand the code, but if that were the issue, an Exception presumably would have been thrown. Perhaps there are new security measures coming from the MacOs, to prevent an external program from opening a mic line surreptitiously?
If you do get this solved, be sure and post the answer and mark it solved. This seems to be a live question for many people.
My aim is to take in two channels of audio from a live source, perform some FFT analysis and display realtime graphs of the data.
So far I have researched and have gotten to the point where I can create a targetdataline from my audio interface with two channels of audio at a specified audioformat. I have created a buffer for this stream of bytes, however I would like to treat each audio channel independently. Do I need to split the stream as it writes to the buffer, and have two buffers? Or do I need to split the buffer into different arrays to process?
final AudioFormat format = getFormat();
DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
TargetDataLine line = (TargetDataLine) m.getLine(info);
line.open(format);
line.start();
System.out.println("Line Started");
Thread captureThread = new Thread(){
int bufferSize = (int) (format.getSampleRate() * format.getFrameRate() * format.getChannels());
byte buffer[] = new byte[bufferSize / 5];
out = new ByteArrayOutputStream();
while(running) {
int numBytesRead = line.read(buffer, 0, buffer.length);
while (numBytesRead > 0) {
arraytoProcess = buffer;
Thread fftThread;
fftThread = new Thread(){
public void fftrun() {
try {
fftCalc();
} catch (ParseException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
};
};
while (numBytesRead == buffer.length){
fftThread.start();
}
}
I am sure I have gone far wrong, however any pointers would help. When I try running this at the moment I am aware that it takes too longer to complete the 'fftThread' than it takes for each pass of the buffer so I get an illegal thread state exception (however it is currently getting all bytes (stereo channel) passed to this thread. I have tried the good old search engines however things aren't overly clear on how to deal with accessing multiple channels of a TargetDataStream.
I'm trying to write 2 different buffers (buffer A and B) multithreaded with SourceDataLine to play the sounds at the same time. But it keeps switching between buffer A and buffer B, do I need to merge the buffers together before writing them to my SourceDataLine or is there a way to play them synchronized?
class PlayThread extends Thread {
byte[] buffer = new byte[2 * 1024];
#Override
public void run() {
try {
while (true) {
DatagramPacket receive = new DatagramPacket(buffer, buffer.length);
mDatagramSocket.receive(receive);
mSourceDataLine.write(receive.getData(), 0, receive.getData().length);
System.out.println("Received!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
I have 2 PlayThread instances with a different incoming buffer. Below is the function where the SourceDataLine is initialized.
private void init() {
try {
DataLine.Info sourceDataLineInfo = new DataLine.Info(
SourceDataLine.class, audioFormat);
DataLine.Info targetDataLineInfo = new DataLine.Info(
TargetDataLine.class, audioFormat);
Mixer.Info[] mixerInfo = AudioSystem.getMixerInfo();
Mixer mixer = AudioSystem.getMixer(mixerInfo[3]);
mSourceDataLine = (SourceDataLine) AudioSystem
.getLine(sourceDataLineInfo);
mTargetDataLine = (TargetDataLine) mixer.getLine(targetDataLineInfo);
mSourceDataLine.open(audioFormat, 2 * 1024);
mSourceDataLine.start();
mTargetDataLine.open(audioFormat, 2 * 1024);
mTargetDataLine.start();
} catch (LineUnavailableException ex) {
ex.printStackTrace();
}
}
Thank you.
You absolutely do have to merge them. Imagine writing numbers to a file from two threads:
123456...
123456...
might become
11234235656...
Which is what's happening to you.
Another issue is that you need to buffer your data as it comes in from the network, or you will likely drop it. You need at least two threads -- one for reading and one for playing for this to work. However, in your case, you will probably have better luck with one reader thread for each input packet stream. (See my talk slides: http://blog.bjornroche.com/2011/11/slides-from-fundamentals-of-audio.html I specifically have a slide about streaming from http which is also relevant here)
So, Instead of multiple PlayThreads, make multiple ReaderThreads, which wait for data and then write to a buffer of some sort (PipedInput and PipedOutputStream work well for Java). Then you need another thread to read the data from the buffers and then write the COMBINED data to the stream.
This leaves your original question of how to combine the data. The answer is that there's no single answer, but usually the easiest correct way is to average the data on a sample-by-sample basis. However, exactly how you do so depends on your data format, which your code doesn't include. Assuming it's big-endian 16-bit integer, you need to convert the incoming raw data to shorts, average the shorts, and convert the averaged short back to bytes.
The byte to short conversion is most easily accomplished using DataInputStream and DataOutputStream.
So, I'm working on a project for class wherein we have to have a game with background music. I'm trying to play a .wav file as background music, but since I can't use clips (too short for a music file) I have to play with the AudioStream.
In my first implementation, the game would hang until the song finished, so I threw it into its own thread to try and alleviate that. Currently, the game plays very slowly while the song plays. I'm not sure what I need to do to make this thread play nice with my animator thread, because we we're never formally taught threads. Below is my background music player class, please someone tell me what I've done wrong that makes it hog all the system resources.
public class BGMusicPlayer implements Runnable {
File file;
AudioInputStream in;
SourceDataLine line;
int frameSize;
byte[] buffer = new byte [32 * 1024];
Thread player;
boolean playing = false;
boolean fileNotOver = true;
public BGMusicPlayer (File inputFile){
try{
file = inputFile;
in = AudioSystem.getAudioInputStream (inputFile);
AudioFormat format = in.getFormat();
frameSize = format.getFrameSize();
DataLine.Info info =new DataLine.Info (SourceDataLine.class, format);
line = (SourceDataLine) AudioSystem.getLine (info);
line.open();
player = new Thread (this);
player.start();
}
catch(Exception e){
System.out.println("That is not a valid file. No music for you.");
}
}
public void run() {
int readPoint = 0;
int bytesRead = 0;
player.setPriority(Thread.MIN_PRIORITY);
while (fileNotOver) {
if (playing) {
try {
bytesRead = in.read (buffer,
readPoint,
buffer.length - readPoint);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (bytesRead == -1) {
fileNotOver = false;
break;
}
int leftover = bytesRead % frameSize;
// send to line
line.write (buffer, readPoint, bytesRead-leftover);
// save the leftover bytes
System.arraycopy (buffer, bytesRead,
buffer, 0,
leftover);
readPoint = leftover;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void start() {
playing = true;
if(!player.isAlive())
player.start();
line.start();
}
public void stop() {
playing = false;
line.stop();
}
}
You are pretty close, but there are a couple of unusual things that maybe are contributing to the performance problem.
First off, if you are just playing back a .wav, there shouldn't really be a need to deal with any "readpoint" but a value of 0, and there shouldn't really be a need for a "leftover" computation. When you do the write, it should simply be the same number of bytes that were read in (return value of the read() method).
I'm also unclear why you are doing the ArrayCopy. Can you lose that?
Setting the Thread to low priority, and putting a Sleep--I guess you were hoping those would slow down the audio processing to allow more of your game to process? I've never seen this done before and it is really unusual if it is truly needed. I really recommend getting rid of these as well.
I'm curious where your audio file is coming from. Your not streaming it over the web, are you?
By the way, the way you get your input from a File and place it into an InputStream very likely won't work with Java7. A lot of folks are reporting a bug with that. It turns out it is more correct and efficient to generate a URL from the File, and then get the AudioInputStream using the URL as the argument rather than the file. The error that can come up is a "Mark/Reset" error. (A search on that will show its come up a number of times here.)
I'm making a java application and I need to play audio. I'm playing mainly small sound files of my cannon firing (its a cannon shooting game) and the projectiles exploding, though I plan on having looping background music. I have found two different methods to accomplish this, but both don't work how I want.
The first method is literally a method:
public void playSoundFile(File file) {//http://java.ittoolbox.com/groups/technical-functional/java-l/sound-in-an-application-90681
try {
//get an AudioInputStream
AudioInputStream ais = AudioSystem.getAudioInputStream(file);
//get the AudioFormat for the AudioInputStream
AudioFormat audioformat = ais.getFormat();
System.out.println("Format: " + audioformat.toString());
System.out.println("Encoding: " + audioformat.getEncoding());
System.out.println("SampleRate:" + audioformat.getSampleRate());
System.out.println("SampleSizeInBits: " + audioformat.getSampleSizeInBits());
System.out.println("Channels: " + audioformat.getChannels());
System.out.println("FrameSize: " + audioformat.getFrameSize());
System.out.println("FrameRate: " + audioformat.getFrameRate());
System.out.println("BigEndian: " + audioformat.isBigEndian());
//ULAW format to PCM format conversion
if ((audioformat.getEncoding() == AudioFormat.Encoding.ULAW)
|| (audioformat.getEncoding() == AudioFormat.Encoding.ALAW)) {
AudioFormat newformat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
audioformat.getSampleRate(),
audioformat.getSampleSizeInBits() * 2,
audioformat.getChannels(),
audioformat.getFrameSize() * 2,
audioformat.getFrameRate(), true);
ais = AudioSystem.getAudioInputStream(newformat, ais);
audioformat = newformat;
}
//checking for a supported output line
DataLine.Info datalineinfo = new DataLine.Info(SourceDataLine.class, audioformat);
if (!AudioSystem.isLineSupported(datalineinfo)) {
//System.out.println("Line matching " + datalineinfo + " is not supported.");
} else {
//System.out.println("Line matching " + datalineinfo + " is supported.");
//opening the sound output line
SourceDataLine sourcedataline = (SourceDataLine) AudioSystem.getLine(datalineinfo);
sourcedataline.open(audioformat);
sourcedataline.start();
//Copy data from the input stream to the output data line
int framesizeinbytes = audioformat.getFrameSize();
int bufferlengthinframes = sourcedataline.getBufferSize() / 8;
int bufferlengthinbytes = bufferlengthinframes * framesizeinbytes;
byte[] sounddata = new byte[bufferlengthinbytes];
int numberofbytesread = 0;
while ((numberofbytesread = ais.read(sounddata)) != -1) {
int numberofbytesremaining = numberofbytesread;
sourcedataline.write(sounddata, 0, numberofbytesread);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
The problem with this is that my entire program stops until the sound file is finished, or at least nearly finished.
The second method is this:
File file = new File("Launch1.wav");
AudioClip clip;
try {
clip = JApplet.newAudioClip(file.toURL());
clip.play();
} catch (Exception e) {
e.getMessage();
}
The problem I have here is that every time the sound file ends early or doesn't play at all depending on where I place the code.
Is their any way to play sound without the above mentioned problems? Am I doing something wrong? Any help is greatly appreciated.
For the first method you have to create another thread for audio.
For example like this:
new Thread(
new Runnable() {
public void run() {
try {
// PLAY AUDIO CODE
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
Of course you have to make sure that previous sound isn't still playing.
I guess you should run your playSound method in a background thread as mentioned in the doc here
"you'll probably want to invoke this
playback loop in a separate thread
from the rest of the application
program, so that your program doesn't
appear to freeze when playing a long
sound"
Maybe by doing something like
// shared executor
ExecutorService soundExecutor = ...; //Executors.newSingleThreadExecutor();
...
final File soundFile = ...;
soundExecutor.submit(new Runnable(){
public void run(){
SoundUtils.playSoundFile(soundFile);
}
});
The problem with this is that my entire program stops until the sound file is finished, or at least nearly finished.
This screams a threading issue. Have you tried playing the sound in a background thread? By the way, is this a Swing program? If so, use a SwingWorker to play the sound. There are many reasons for this, but one primary reason is that it's easy to track the state of the thread via the PropertyChangeListener support built in to SwingWorker.
You should create a thread that handles the audio playback. But make sure that it is able to mix in sounds, so two shots that happen after each other can get their sounds played at the correct time, without waiting for the prior sound to finish.
There should be Frameworks out there that do the mixing for you.
A good starting point is:
http://www.oracle.com/technetwork/java/javase/tech/index-jsp-140239.html