Java audio player cuts audio at the end - java

Hey there stack overflow.
I'm creating a playlist player in Java, so far so good, I got all the logic down and the project is nearing completion. We've been testing the playback by creating some large playlist and just let the thing go from start to end. The playback sounds good, but sometimes the audio is cut off at the end. This happens very rarely. The last x seconds (time varies) are not played.
The files im testing with are all PCM wave file of 16 or 24 bit sampling size. Im using the Java sound engine in combination with Java zooms mp3 and ogg spi to support other types of audio files.
So far I have this logged a couple of times and my first thought was that the file might be corrupt, this is not the case. I've tried playing the file on its own and it played fully!
I've tried to find the problem but i just cant find it. I dont think theres anything wrong with my audio player, im running out of ideas.
Here is how i create my audio input stream:
public static AudioInputStream getUnmarkableAudioInputStream(Mixer mixer, File file)
throws UnsupportedAudioFileException
{
if (!file.exists() || !file.canRead()) {
return null;
}
AudioInputStream stream;
try {
stream = getAudioInputStream(file);
} catch (IOException e) {
logger.error("failed to retrieve stream from file", e);
return null;
}
AudioFormat baseFormat = stream.getFormat();
DataLine.Info info = new DataLine.Info(SourceDataLine.class, baseFormat);
boolean supportedDirectly = false;
if (mixer == null) {
supportedDirectly = AudioSystem.isLineSupported(info);
} else {
supportedDirectly = mixer.isLineSupported(info);
}
// compare the AudioFormat with the desired one
if (baseFormat.getEncoding() != AudioFormat.Encoding.PCM_SIGNED || !supportedDirectly) {
AudioFormat decodedFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(), 16, baseFormat.getChannels(),
baseFormat.getChannels() * 2, baseFormat.getSampleRate(),
false);
// convert the audio format to the supported one
if (AudioSystem.isConversionSupported(decodedFormat, baseFormat)) {
stream = AudioSystem.getAudioInputStream(decodedFormat, stream);
} else {
logger.debug(
"Audio format {} is not supported "
+ "and can not be converted to default format",
baseFormat.toString());
return null;
}
}
return stream;
}
And this is my audio player thread:
final class PlayerThread extends Thread
{
private byte[] buffer;
/**
* Initialize the buffer
*/
public void initBuffer()
{
linelock.lock();
try {
buffer = new byte[line.getBufferSize() / 5];
} finally {
linelock.unlock();
}
}
public void run()
{
initBuffer();
while (!isInterrupted()) {
checkState();
// if the line is just cleared go to the start of the loop
if (line == null || isInterrupted()) {
continue;
}
write();
}
// clean up all resources
close();
// change the state
state = Player.State.STOPPED;
}
private void checkState()
{
if (state != Player.State.PLAYING) {
if (line != null) {
line.flush();
}
try {
synchronized (this) {
this.wait();
}
} catch (InterruptedException e) {
// reset the interupt status
interrupt();
}
}
}
private void write()
{
// how much bytes could be written on the line
int available = line.available();
// is the space on the line big enough to write the buffer to
if (available >= buffer.length) {
// fill the buffer array
int read = 0;
try {
read = audioStream.read(buffer, 0, buffer.length);
} catch (Throwable ball) {
logger.error("Error in audio engine (read)", ball);
}
// if there was something to read, write it to the line
// otherwise stop the player
if (read >= 0) {
try {
linelock.lock();
line.write(buffer, 0, read);
} catch (Throwable ball) {
logger.error("Error in audio engine (write)", ball);
} finally {
linelock.unlock();
}
bytesRead += read;
} else {
line.drain();
MoreDefaultPlayer.this.stop();
}
}
}
private void close()
{
// invoke close on listeners
invokePlayerClosedOnListeners();
// destroy the volume chain
vc.removeVolumeListener(MoreDefaultPlayer.this);
// close the stream
try {
audioStream.close();
} catch (IOException e) {
logger.error("failed to close audio stream");
}
clearAllListeners();
linelock.lock();
try {
// quit the line
line.stop();
line.close();
line = null;
} finally {
linelock.unlock();
}
}
}
As you can see I drain the line after, so i dont think the problem is the line being closed before everything from the stream is played.
Can anyone see what might be wrong with this code?

I don't see an obvious answer, but there are a couple things that raise yellow flags for me. The common practise is to put the line.write() method in a while loop, not to invoke it repeatedly. There is usually no need to test for line.available() or to handle locking the line. The method line.write() will handle the necessary blocking if there is no space on the line available. I've always been cautioned not to lock or block audio lines unnecessarily.
Is the locking logic an integral part of the handling of the sequence of queues? The error you are describing could be in that handling. (Maybe there is an interaction with the test of available() compared to the buffer size? Is the amount of cutoff roughly equal to the buffer size?)
I would consider implementing a LineListener to announce when a cue is finished, and making that event the trigger of the playback of the next cue. An LineEvent of type STOP can be issued when the given file is done, notifying whatever handles the queue to proceed to the next file.

Related

Missing character off incoming string from Bluetooth

I am currently trying to create a water level readout as a progress bar in a simple Android app. Currently, I am using an Arduino Mega 2560 with a HC-05 to transmit the readout of the water level sensor. To simplify things, the arduino code is just counting up and down from 0 to 1000 and back, as follows.
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println("Test for Water Sensor");
Serial1.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
for (int i = 0; i <= 1000; i++)
{
Serial1.println(i);
Serial.println(i);
delay(100);
}
for (int i = 1000; i >= 0; i--)
{
Serial1.println(i);
Serial.println(i);
delay(100);
}
}
On the android end, I am using this to convert to int, then change the progress bar. It also currently displays the unconverted message in a TextView.
mHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message msg){
if(msg.what == MESSAGE_READ){
String readMessage = null;
try {
readMessage = new String((byte[]) msg.obj, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
mReadBuffer.setText(readMessage);
try {
waterLevelValue = NumberFormat.getInstance().parse(readMessage).intValue();
waterLevel.setProgress(waterLevelValue);
} catch (ParseException e) {
e.printStackTrace();
}
}
if(msg.what == CONNECTING_STATUS){
if(msg.arg1 == 1)
mBluetoothStatus.setText("Connected to Device: " + msg.obj);
else
mBluetoothStatus.setText("Connection Failed");
}
}
};
The issue I am getting is that quite often (maybe 1-2 times a second) it is not reading the first digit. I can see on the Serial Monitor that all digits are going there, but on the android app, it will sometimes miss the first (eg: 443, 444, 45, 446, 447, etc)
What could be causing the issue here, I am very new to Bluetooth, so please help! More than happy to send more portions of code if needed.
EDIT: Adding code for reading input stream. Probably was important in the first place.
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.available();
if(bytes != 0) {
SystemClock.sleep(100); //pause and wait for rest of data. Adjust this depending on your sending speed.
bytes = mmInStream.available(); // how many bytes are ready to be read?
bytes = mmInStream.read(buffer, 0, bytes); // record how many bytes we actually read
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget(); // Send the obtained bytes to the UI activity
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}

Java: How to use microphone input as input for the JavaFX media player?

I want to play the microphone input in realtime using the JavaFX media player (to analyse its frequencies). The problem is, that the MediaPlayer only accepts Strings as source. I know how to write the microphone input into a byte array and into a file.
Using the byte array as source for the MediaPlayer is (for me) not possible. I tried using a temporary file, but that causes the following error:
Exception in thread "JavaFX Application Thread" MediaException: MEDIA_UNSUPPORTED : Empty signature!
I think this is, because I'm using a file as input while I'm still writing new data into it. My full code until now:
public class Music {
static AudioFormat format;
static DataLine.Info info;
public static void input(int i, int j, int pinState) {
format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
try {
info = new DataLine.Info(TargetDataLine.class, format);
final TargetDataLine targetLine = (TargetDataLine) AudioSystem.getLine(info);
targetLine.open();
AudioInputStream audioStream = new AudioInputStream(targetLine);
File temp = File.createTempFile("Input", ".wav");
temp.deleteOnExit();
Thread targetThread = new Thread() {
public void run() {
targetLine.start();
try {
AudioSystem.write(audioStream, AudioFileFormat.Type.WAVE, temp);
} catch (IOException e) {
e.printStackTrace();
}
}
};
targetThread.start();
Media media = new Media(temp.toURI().toURL().toString());
MediaPlayer player = new MediaPlayer(media);
player.setAudioSpectrumThreshold(-100);
player.setMute(false);
player.setAudioSpectrumListener(new AudioSpectrumListener() {
#Override
public void spectrumDataUpdate(double timestamp, double duration, float[] magnitudes, float[] phases) {
if(Var.nodeController[i] == 3) { //testing if the targetLine should keep on capturing sound
} else {
targetLine.stop();
targetLine.close();
player.stop();
}
}
});
player.play();
} catch (LineUnavailableException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
I need to find a solution to use the microphone input as MediaPlayer input, either using a file or a byte array or any other possible solution.
I am going to speculate that the following library might be helpful.
https://github.com/SzymonKatra/AudioAnalyzer
Note that it makes use of FFmpeg which claims to be:
A complete, cross-platform solution to record, convert and stream
audio and video.
The key component (based on my cursory look-over) seems to be the class FFTAnalyzer.java which the documentation says is based upon an FFT code/algorithm from Princeton.
So, the basic plan (I believe) would be the following:
obtain microphone input (a stream of bytes) from targetdataline
convert N frames of bytes to normalized floats or doubles and package as an array to send to FFTAnalyzer.analyze(double[] samples)
request the analysis results via FFTAnalyzer.getAmplitudes()
examine the result for each equalization band and apply smoothing as appropriate
repeat
I'm unclear as to exactly how much of the library is needed. It could be that since you are not dealing with video or cross-platform issues, only a class or two from this library would be needed.

Clip Play + volumeControl = a Pop

I have searched and searched, and have not found the cause of my problem.
Setting: I am trying to play a list of clips in sequence indefinitely till the GUI of the program tells to stop. Also, the user can adjust the volume while the clips are being played.
Let's say I have clips A, B and C. The sequence of play will be ABCABCABCABC.....
Problem: The problem is that the first time clips A, B and C start - there are POPs - after that for the GUI tells to stop, there are no POPs.
Code: In the run() method of the thread, get all filenames of the clips, create all clips and save them in a LinkedHashSet. Next, play these clips in loop, creating the volume control object for each.
My Observation: The offending lines are commented (with // TODO: UNCOMMENT ME!!!!!) With the commented lines, there are no POPs, but the volume control is disabled. When these lines are uncommented, the POPs return.
Question: Where am I going wrong? I don't have any hair on the head to pull out! :)
private LinkedHashSet<Clip> clips = new LinkedHashSet<Clip>();
private FloatControl volControl = null;
.
.
.
.
#Override
public void run() {
List<String> fileNames = smd.getFiles();
if ( !(numFiles.isEmpty()) ) {
try {
// Create all clips and save in LinkedHashSet
//
for (String s: fileNames) {
clips.add(CreateClip(s));
}
// Play each clip till GUI says to stop
//
while (smd.getStopPlayStatus()) {
for (Clip c: clips) {
// Calculate and set the clip volume to what GUI says
//
float dB = (float) (Math.log(smd.getSwarMVolume()/100f) / Math.log(10.0) * 80.0);
System.out.println(" vol = " + volumeLevel/100f + " || dB = " + dB);
Control[ ] ctls = c.getControls();
for (Control ctl : ctls) {
if (ctl.toString().toLowerCase().contains("master gain")) {
volControl = (FloatControl) ctl;
break;
}
}
// volControl.setValue(dB); // TODO: UNCOMMENT ME!!!!!!!!
// Play the clip
//
loop(c, 0);
}
}
} catch (ConcurrentModificationException ex) {
}
}
}
void loop(Clip clip, int times){
if (times == -1) {
clip.loop(Clip.LOOP_CONTINUOUSLY);
} else {
clip.loop(times);
}
while (clip.isRunning()) {
float dB = (float) (Math.log(smd.getVolume()/100f) / Math.log(10.0) * 80.0);
// volControl.setValue(dB); // TODO: UNCOMMENT ME!!!!!!!!
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
clip.drain();
}
private Clip CreateClip(String fileName) {
Clip c = null;
try {
File file = new File(fileName);
AudioInputStream sound = AudioSystem.getAudioInputStream(file);
AudioFormat format = sound.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class, format);
c = (Clip) AudioSystem.getLine(info);
c.addLineListener(this);
c.open(sound);
} catch (MalformedURLException ex) {
} catch (UnsupportedAudioFileException ex) {
} catch (IOException ex) {
} catch (LineUnavailableException ex) {
}
return c;
}
The volume controls that are provided are very crude. When you set a new volume, the entire change happens all at once. This can create a discontinuity in the sound that results in a pop.
To eliminate this, one can try making the volume change more gradually. Sometimes this works. You will have to tinker with it to find the best trade-off of the amount of time needed for a transition and smoothness.
There are several issues that pertain. (1) The "distance" one can travel in a single volume change without a pop can be different at the low end versus the high end, depending on the scaling in use. (2) The number of changes you can make over a period of time is limited by the buffer size. You can only make one change per buffer. Thus decreasing the buffer size will allow more granularity in the volume changes, but will increase the risk of drop-outs.
Frankly, I just threw up my hands and gave up using the provided volume lines. Instead, I program the changes to transition on a per-frame basis. This can be done by taking each buffer and converting the bytes to PCM, doing the volume multiplication on the individual frames and converting back to bytes. The processing cost turns out to be pretty minor.

Java audio Stream Closed error

I am trying to add sound to a game I am making, but every time I try to load the sound, I get a Stream Closed Exception. I don't understand why this is happening.
Loads the sound:
public class WavPlayer extends Thread {
/*
* #param s The path of the wav file.
* #return The sound data loaded into the WavSound object
*/
public static WavSound loadSound(String s){
// Get an input stream
InputStream is = WavPlayer.class.getClassLoader().getResourceAsStream(s);
AudioInputStream audioStream;
try {
// Buffer the input stream
BufferedInputStream bis = new BufferedInputStream(is);
// Create the audio input stream and audio format
audioStream = AudioSystem.getAudioInputStream(bis); //!Stream Closed Exception occurs here
AudioFormat format = audioStream.getFormat();
// The length of the audio file
int length = (int) (audioStream.getFrameLength() * format.getFrameSize());
// The array to store the samples in
byte[] samples = new byte[length];
// Read the samples into array to reduce disk access
// (fast-execution)
DataInputStream dis = new DataInputStream(audioStream);
dis.readFully(samples);
// Create a sound container
WavSound sound = new WavSound(samples, format, (int) audioStream.getFrameLength());
// Don't start the sound on load
sound.setState(SoundState.STATE_STOPPED);
// Create a new player for each sound
new WavPlayer(sound);
return sound;
} catch (Exception e) {
// An error. Mustn't happen
}
return null;
}
// Private variables
private WavSound sound = null;
/**
* Constructs a new player with a sound and with an optional looping
*
* #param s The WavSound object
*/
public WavPlayer(WavSound s) {
sound = s;
start();
}
/**
* Runs the player in a separate thread
*/
#Override
public void run(){
// Get the byte samples from the container
byte[] data = sound.getData();
InputStream is = new ByteArrayInputStream(data);
try {
// Create a line for the required audio format
SourceDataLine line = null;
AudioFormat format = sound.getAudioFormat();
// Calculate the buffer size and create the buffer
int bufferSize = sound.getLength();
// System.out.println(bufferSize);
byte[] buffer = new byte[bufferSize];
// Create a new data line to write the samples onto
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
line = (SourceDataLine) AudioSystem.getLine(info);
// Open and start playing on the line
try {
if (!line.isOpen()) {
line.open();
}
line.start();
} catch (Exception e){}
// The total bytes read
int numBytesRead = 0;
boolean running = true;
while (running) {
// Destroy this player if the sound is destroyed
if (sound.getState() == SoundState.STATE_DESTROYED) {
running = false;
// Release the line and release any resources used
line.drain();
line.close();
}
// Write the data only if the sound is playing or looping
if ((sound.getState() == SoundState.STATE_PLAYING)
|| (sound.getState() == SoundState.STATE_LOOPING)) {
numBytesRead = is.read(buffer, 0, buffer.length);
if (numBytesRead != -1) {
line.write(buffer, 0, numBytesRead);
} else {
// The samples are ended. So reset the position of the
// stream
is.reset();
// If the sound is not looping, stop it
if (sound.getState() == SoundState.STATE_PLAYING) {
sound.setState(SoundState.STATE_STOPPED);
}
}
} else {
// Not playing. so wait for a few moments
Thread.sleep(Math.min(1000 / Global.FRAMES_PER_SECOND, 10));
}
}
} catch (Exception e) {
// Do nothing
}
}
The error message I get is: "Exception in thread "main" java.io.IOException: Stream closed
at java.io.BufferedInputStream.getInIfOpen(BufferedInputStream.java:134)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read(BufferedInputStream.java:237)
at java.io.DataInputStream.readInt(DataInputStream.java:370)
at com.sun.media.sound.WaveFileReader.getFMT(WaveFileReader.java:224)
at com.sun.media.sound.WaveFileReader.getAudioInputStream(WaveFileReader.java:140)
at javax.sound.sampled.AudioSystem.getAudioInputStream(AudioSystem.java:1094)
at stm.sounds.WavPlayer.loadSound(WavPlayer.java:42)
at stm.STM.(STM.java:265)
at stm.STM.main(STM.java:363)"
Most probably the file path in this line is not correct:
WavPlayer sound1 = WavPlayer.loadSound("coin.wav");
You should pass the path of the 'coin.wav' file instead of just its name.
For instance if its under a folder named sounds, which let's say right under the root of project, that parameter should be 'sounds/coin.wav'.
The problem is in your static method loadSound. This method returns null when an exception is thrown. You catch it but you do nothing with it,
NEVER make empty catch.
Catch specific exceptions.
I would change your method signature loadSound as
public static WavSound loadSound(String s) throws Exception // rather than exception specific exception!!
And then your method without try-catch

How to tell when AudioTrack object has finished playing?

I'm trying to play a PCM file in Android using the AudioTrack class. I can get the file to play just fine, but I cannot reliably tell when playback has finished. AudioTrack.getPlayState says playback has stopped when it hasn't finished playing. I'm having the same problem with AudioTrack.setNotificationMarkerPosition, and I'm pretty sure my marker is set to the end of the file (although I'm not completely sure I'm doing it right). Likewise, playback continues when getPlaybackHeadPosition is at the end of the file and has stopped incrementing. Can anyone help?
I found that using audioTrack.setNotificationMarkerPosition(audioLength) and audioTrack.setPlaybackPositionUpdateListener worked for me. See the following code:
// Get the length of the audio stored in the file (16 bit so 2 bytes per short)
// and create a short array to store the recorded audio.
int audioLength = (int) (pcmFile.length() / 2);
short[] audioData = new short[audioLength];
DataInputStream dis = null;
try {
// Create a DataInputStream to read the audio data back from the saved file.
InputStream is = new FileInputStream(pcmFile);
BufferedInputStream bis = new BufferedInputStream(is);
dis = new DataInputStream(bis);
// Read the file into the music array.
int i = 0;
while (dis.available() > 0) {
audioData[i] = dis.readShort();
i++;
}
// Create a new AudioTrack using the same parameters as the AudioRecord.
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, RECORDER_SAMPLE_RATE, RECORDER_CHANNEL_OUT,
RECORDER_AUDIO_ENCODING, audioLength, AudioTrack.MODE_STREAM);
audioTrack.setNotificationMarkerPosition(audioLength);
audioTrack.setPlaybackPositionUpdateListener(new OnPlaybackPositionUpdateListener() {
#Override
public void onPeriodicNotification(AudioTrack track) {
// nothing to do
}
#Override
public void onMarkerReached(AudioTrack track) {
Log.d(LOG_TAG, "Audio track end of file reached...");
messageHandler.sendMessage(messageHandler.obtainMessage(PLAYBACK_END_REACHED));
}
});
// Start playback
audioTrack.play();
// Write the music buffer to the AudioTrack object
audioTrack.write(audioData, 0, audioLength);
} catch (Exception e) {
Log.e(LOG_TAG, "Error playing audio.", e);
} finally {
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
// don't care
}
}
}
This works for me:
do{ // Montior playback to find when done
x = audioTrack.getPlaybackHeadPosition();
}while (x< pcmFile.length() / 2);

Categories

Resources