Asynchronous audio player with java for Android - java

I have created an Java application that runs in Android. Sounds are prepared and played only with a synchronous MediaPlayer class, involving a latency from 50 to 80 ms, that is too big for a real time product.
So, for improving performance of a sound player in Java for Android (by minimizing its latency), I am looking for an asynchronous audio player or media player.
Asynchronous because that avoids latency when loading (preparing) or playing a sound
Do you know an Android native library or something else that can be imported in a java application?
For instance, I have seen that URL but I don't know how to do for "plugging" it in a Java application?
https://developer.android.com/reference/android/media/AsyncPlayer.html
Thanks

I wrote a pretty complere chapter about Android Audio in my book. Here is the flowchart I use to decide which API to use. The old AsyncPlayer you referenced is deprecatated and would not really solve your latency issue in my opinion.
Media Player is the worse for start up latency. SoundPool is probably the best choice based on the info you have provided.
AudioTrack gives you the most flexibility.
Hope this helps. Here is a code excerpt for playing sounds using the soundPool API:
private void playSoundPool(int soundID) {
int MAX_STREAMS = 20;
int REPEAT = 0;
SoundPool soundPool = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, REPEAT);
soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
#Override
public void onLoadComplete(SoundPool soundPool, int soundId, int status) {
int priority = 0;
int repeat = 0;
float rate = 1.f; // Frequency Rate can be from .5 to 2.0
// Set volume
AudioManager mgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
float streamVolumeCurrent =
mgr.getStreamVolume(AudioManager.STREAM_MUSIC);
float streamVolumeMax =
mgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = streamVolumeCurrent / streamVolumeMax;
// Play it
soundPool.play(soundId, volume, volume, priority, repeat, rate);
}
});
soundPool.load(this, soundID, 1);
}

Related

Is there any way to create a fixed accelerometer sensor delay on my Android app? I want to achieve, exactly, 20 Hz sampling rate

I want to simulate a fixed accelerometer sensor delay with sampling rate 20Hz. (600 samples/ 30sec)
This is for a Human Activity Recognition Android mobile app, that uses TensorFlow to predict the activity of a user. I experimented with fixed SENSOR_DELAY(NORMAL, UI, FASTEST) and samplingPeriodUS. I noticed that with the custom value on samplingPeriodUS you cannot get a consistent delay, as the sampling rate alters constantly. I ended up, using the SENSOR_DELAY_NORMAL as it gives me a consistent sampling rate, nearly 46Hz.
With this code I collect a 600 accelerometer samples (x,y,z) and then add it to an Arraylist that sends it to Tensorflow to return the probabilities.
With this set I expect to get 600 samples per 13-14 seconds. I want to achieve a 30 seconds delay(20Hz).
Is there any way achieving it with Thread.Sleep, ScheduledExecutorService or maybe a Linear Interpolation?
The source code's taken from curiousily and has been slightly modified: Github Link
protected void onResume() {
super.onResume();
getSensorManager().registerListener(this, getSensorManager().getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onSensorChanged(SensorEvent event) {
specificActivityPrediction(activity);
x.add(event.values[0]);
y.add(event.values[1]);
z.add(event.values[2]);
}
private void specificActivityPrediction(String activity) {
if (x.size() == N_SAMPLES && y.size() == N_SAMPLES && z.size() == N_SAMPLES) {
data = new ArrayList<>();
data.addAll(x);
data.addAll(y);
data.addAll(z);
results = classifier.predictProbabilities(toFloatArray(data));
downstairsTextView.setText(Float.toString(round(results[0], 2)));
joggingTextView.setText(Float.toString(round(results[1], 2)));
sittingTextView.setText(Float.toString(round(results[2], 2)));
standingTextView.setText(Float.toString(round(results[3], 2)));
upstairsTextView.setText(Float.toString(round(results[4], 2)));
walkingTextView.setText(Float.toString(round(results[5], 2)));
sendProbabilities(activity);
x.clear();
y.clear();
z.clear();
}
}

Android Wear Watch Face get battery percentage of Phone

I'm developing an Android Wear Watch Face and I want to show the battery percentage of the watch and the phone. I managed to get the percentage of the Watch but I'm new to Java & Android so please go easy on me with the explaining.
private String getBatteryInfoPhone()
{
float retVal = 0;
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = registerReceiver(null, iFilter);
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
retVal = level / (float)scale;
return Integer.toString(Math.round(retVal)) + "%";
}
This is the code I have at the moment to give me the percentage of my phone.
Please note, that this is a Watch Face, so it's a service with no Activities.
With this solution I constantly get 1% instead of the actual percentage.
batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
will give you already value between 1 and 100, so you can plug it directly as the percentage. You don't need to divide it by the scale.

Java Audio Metronome | Timing and Speed Problems

I’m starting to work on a music/metronome application in Java and I’m running into some problems with the timing and speed.
For testing purposes I’m trying to play two sine wave tones at the same time at regular intervals, but instead they play in sync for a few beats and then slightly out of sync for a few beats and then back in sync again for a few beats.
From researching good metronome programming, I found that Thread.sleep() is horrible for timing, so I completely avoided that and went with checking System.nanoTime() to determine when the sounds should play.
I’m using AudioSystem’s SourceDataLine for my audio player and I’m using a thread for each tone that constantly polls System.nanoTime() in order to determine when the sound should play. I create a new SourceDataLine and delete the previous one each time a sound plays, because the volume fluctuates if I leave the line open and keep playing sounds on the same line. I create the player before polling nanoTime() so that the player is already created and all it has to do is play the sound when it is time.
In theory this seemed like a good method for getting each sound to play on time, but it’s not working correctly. I’m not sure if the timing problems are from running different threads or if it has to do with deleting and recreating the SourceDataLine or if it’s in playing sounds or what exactly...
At the moment this is just a simple test in Java, but my goal is to create my app on mobile devices (Android, iOS, Windows Phone, etc)...however my current method isn’t even keeping perfect time on a PC, so I’m worried that certain mobile devices with limited resources will have even more timing problems. I will also be adding more sounds to it to create more complex rhythms, so it needs to be able to handle multiple sounds going simultaneously without sounds lagging.
Another problem I’m having is that the max tempo is controlled by the length of the tone since the tones don’t overlap each other. I tried adding additional threads so that every tone that played would get its own thread...but that really screwed up the timing, so I took it out. I would like to have a way to overlap the previous sound to allow for much higher tempos.
Any help getting these timing and speed issues straightened out would be greatly appreciated!
Thanks.
SoundTest.java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.io.*;
import javax.sound.sampled.*;
public class SoundTest implements ActionListener {
static SoundTest soundTest;
// ENABLE/DISABLE SOUNDS
boolean playSound1 = true;
boolean playSound2 = true;
JFrame mainFrame;
JPanel mainContent;
JPanel center;
JButton buttonPlay;
int sampleRate = 44100;
long startTime;
SourceDataLine line = null;
int tickLength;
boolean playing = false;
SoundElement sound01;
SoundElement sound02;
public static void main (String[] args) {
soundTest = new SoundTest();
SwingUtilities.invokeLater(new Runnable() { public void run() {
soundTest.gui_CreateAndShow();
}});
}
public void gui_CreateAndShow() {
gui_FrameAndContentPanel();
gui_AddContent();
}
public void gui_FrameAndContentPanel() {
mainContent = new JPanel();
mainContent.setLayout(new BorderLayout());
mainContent.setPreferredSize(new Dimension(500,500));
mainContent.setOpaque(true);
mainFrame = new JFrame("Sound Test");
mainFrame.setContentPane(mainContent);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.pack();
mainFrame.setVisible(true);
}
public void gui_AddContent() {
JPanel center = new JPanel();
center.setOpaque(true);
buttonPlay = new JButton("PLAY / STOP");
buttonPlay.setActionCommand("play");
buttonPlay.addActionListener(this);
buttonPlay.setPreferredSize(new Dimension(200, 50));
center.add(buttonPlay);
mainContent.add(center, BorderLayout.CENTER);
}
public void actionPerformed(ActionEvent e) {
if (!playing) {
playing = true;
if (playSound1)
sound01 = new SoundElement(this, 800, 1);
if (playSound2)
sound02 = new SoundElement(this, 1200, 1);
startTime = System.nanoTime();
if (playSound1)
new Thread(sound01).start();
if (playSound2)
new Thread(sound02).start();
}
else {
playing = false;
}
}
}
SoundElement.java
import java.io.*;
import javax.sound.sampled.*;
public class SoundElement implements Runnable {
SoundTest soundTest;
// TEMPO CHANGE
// 750000000=80bpm | 300000000=200bpm | 200000000=300bpm
long nsDelay = 750000000;
int clickLength = 4100;
byte[] audioFile;
double clickFrequency;
double subdivision;
SourceDataLine line = null;
long audioFilePlay;
public SoundElement(SoundTest soundTestIn, double clickFrequencyIn, double subdivisionIn){
soundTest = soundTestIn;
clickFrequency = clickFrequencyIn;
subdivision = subdivisionIn;
generateAudioFile();
}
public void generateAudioFile(){
audioFile = new byte[clickLength * 2];
double temp;
short maxSample;
int p=0;
for (int i = 0; i < audioFile.length;){
temp = Math.sin(2 * Math.PI * p++ / (soundTest.sampleRate/clickFrequency));
maxSample = (short) (temp * Short.MAX_VALUE);
audioFile[i++] = (byte) (maxSample & 0x00ff);
audioFile[i++] = (byte) ((maxSample & 0xff00) >>> 8);
}
}
public void run() {
createPlayer();
audioFilePlay = soundTest.startTime + nsDelay;
while (soundTest.playing){
if (System.nanoTime() >= audioFilePlay){
play();
destroyPlayer();
createPlayer();
audioFilePlay += nsDelay;
}
}
try { destroyPlayer(); } catch (Exception e) { }
}
public void createPlayer(){
AudioFormat af = new AudioFormat(soundTest.sampleRate, 16, 1, true, false);
try {
line = AudioSystem.getSourceDataLine(af);
line.open(af);
line.start();
}
catch (Exception ex) { ex.printStackTrace(); }
}
public void play(){
line.write(audioFile, 0, audioFile.length);
}
public void destroyPlayer(){
line.drain();
line.close();
}
}
This sort of thing is difficult to get right. What you have to realise is that in order to even play a sound, it has to be loaded into an audio driver (and possibly a sound card). This takes time, and you have to account for that. There are basically two options for you:
Rather than counting down a delay between every beat, count down a delay from the start, when the metronome activates. As an example, say for instance that you want a beat every second. Because of the ~20ms delay, in your old method you'd get beats at 20ms, 1040, 2060, 3080, etc... If you count down from the start and place beats at 1000, 2000, 3000, etc. then they will play at 20ms, 1020, 2020, 3020, etc... There will still be some variance since the dalay itself varies a bit, but there should be about 1000ms between beats and it will not go out of sync (or at least, the problem will not get worse over time and likely can't be heard).
The better option, and the one that most of such programs use, is to generate larger pieces of music. Buffer for instance 20 seconds ahead and play that. The timing should be perfect during those 20 seconds. When those 20 seconds are almost over you must generate some new sound. If you can find out how to do this, you should append the new waveform to the old and have it play continuously. Otherwise, just generate a new 20 second soundbit and accept the delay between them.
Now as for your problem with the sounds not being able to overlap... I'm no expert and I don't really know an answer, but this I do know: Something has to mix the sounds if you need them to overlap. Either you can do that yourself in software by combining the waveform bytes (I think it's an addition in some logarithmic space), or you need to send the different overlapping sounds to different 'channels', in which case the audio driver or sound card does it for you. I don't know how this works in Java though, or I forgot, but I learned this through trial-and-error and working with .mod files.

How to generate buzzer at timer countdown?

I'm looking to play a sound when a CountDownTimer expires. I can't seem to get it working as I want. I've looked at some sample code using the ToneGenerator but it is not working as I'm expecting it to and because I'm new to android and java, not really sure how to get it working better.
Here is what I have:
- Countdown timer works. It starts and stops when I click on it and resumes when I click on it again. Everything is perfect here.
I have managed to play some sounds out of my device but it will actually repeat for some reason. Not sure why.
Here is what I want:
- onFinish() of the CountDownTimer, I would like to generate a tone around 2800Hz for a period of 2 or maybe 2.5 seconds.
- after I get it working, I would like to play with the idea of making it an alternating tone at say 2800Hz and 2000Hz for 50ms each or something and for a period of 2 to 2.5 seconds.
** EDIT **
public void soundBuzzer() {
ToneGenerator toneGen;
int type1, type2;
toneGen = new ToneGenerator(AudioManager.STREAM_MUSIC, 100);
type1 = ToneGenerator.TONE_DTMF_6;
type2 = ToneGenerator.TONE_DTMF_8;
toneGen.startTone(type2, 2000);
}
The ToneGenerator will generate a tone that sounds like the tone on a telephone when you hold down a number. I was unable to get the buzzer to sound using that class and making it sound good. As a result I ended up playing an MP3 for sound.
Here's the code:
// function to generate a tone for 3 seconds
public void soundBuzzer( boolean shortBuzzer ) {
MediaPlayer mp;
if( !shortBuzzer )
mp = MediaPlayer.create(appContext, R.raw.buzzer_2s);
else
mp = MediaPlayer.create(appContext, R.raw.buzzer_short);
mp.start();
}

Android implementing simultaneous sound effects

I am making an Android game where I need to implement lag-free sound effects when the player interacts with a game object. To keep it simple, the objects are moving around on the screen and the player must tap them in order to trigger a sound effect. The issue I am having is that when the player taps many objects in a short amount of time, there is a noticeable delay between each sound effect. So for example, the player taps object 1, sound plays, taps object 2, sound plays, and keeps going until the player taps object n, the sound plays but sounds a bit delayed from previous sound effects that played.
I tried to set my SoundPool objects to load different sounds from the same resource but it didn't seem to do much. So is there a way for each iterations of the sound effect to overlap or even stop the previous iteration of the sound effect to be replaced by the new one without experiencing a noticeable delay in sound? Below is my code:
protected SoundPool mSoundPool;
protected boolean mLoaded;
protected int mBalloonPopStreams = 5;
protected int[] mSoundIds;
protected int mSoundIdx = 0;
protected void initializeMedia() {
this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
mSoundPool = new SoundPool(mBalloonPopStreams, AudioManager.STREAM_MUSIC, 0);
mSoundIds = new int[mBalloonPopStreams];
for (int i = 0; i < mBalloonPopStreams; i++) {
mSoundIds[i] = mSoundPool.load(this, R.raw.sound_effect, 1);
}
mSoundPool.setOnLoadCompleteListener(new OnLoadCompleteListener() {
#Override
public void onLoadComplete(SoundPool soundPool, int sampleId,
int status) {
mLoaded = true;
}
});
}
protected OnTouchListener mTouchEvent = new OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
int action = arg1.getAction();
int actionCode = action & MotionEvent.ACTION_MASK;
int pointerId = action >> MotionEvent.ACTION_POINTER_ID_SHIFT;
int x;
int y;
x = (int)arg1.getX(pointerId);
y = (int)arg1.getY(pointerId);
if (actionCode == MotionEvent.ACTION_DOWN || actionCode == MotionEvent.ACTION_POINTER_DOWN) {
// Check if the player tapped a game object
playSound();
}
return true;
}
};
public void playSound() {
if (mLoaded) {
synchronized(mSoundPool) {
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
float actualVolume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
float maxVolume = (float) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = actualVolume / maxVolume;
mSoundPool.play(mSoundIds[mSoundIdx], volume, volume, 1, 0, 1f);
mSoundIdx = ++mSoundIdx % mBalloonPopStreams;
}
}
}
To sum it up, the problem is that, let's say 5 "pop" sounds are to be played within 1 second. I hear the popping sounds play 5 times but they are delayed and out of sync with what's actually going on in the game. Could this be a limitation on hardware? If not, then have I implemented my code incorrectly? What are some workarounds to this problem?
This answer to a similar question might help. The OP of that question was having their app crash if they tried to play multiple sounds with SoundPool, but it might be a different symptom of the same problem.
You may obtain better result using AudioTrack instead of SoundPool at the cost of more code on your side.

Categories