Android implementing simultaneous sound effects - java

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.

Related

Play next sound without waiting for previous playback to end

I am making a roulette game type application, I have 10 rectangles that change color simulating a random choice, In addition to the color change, I want a sound to be played every time the roulette goes through a rectangle.
First a list of possible prizes is created awardList, then the winner is randomly selected positionAwardSelected
I tried to do the following:
final Handler handler = new Handler();
int count = 0;
private void playRoulette(){
List<Award> awardList = new ArrayList<>();
int positionAwardSelected;
// ... List filling and winner selection code (not necessary to include for the question) ...
int numberMovements = ThreadLocalRandom.current().nextInt(20, 60 + 1);
MediaPlayer mp = MediaPlayer.create(getApplicationContext(), R.raw.pin);
final Runnable runnable = new Runnable() {
public void run() {
if (count > numberMovements){
resetColors();
getConstrainLayoutRectangle(positionAwardSelected).setBackground(ContextCompat.getDrawable(Roulette.this, R.drawable.selected_rectangle));
} else {
resetColors();
int randomPosition = ThreadLocalRandom.current().nextInt(0, awardList.size());
getConstrainLayoutRectangle(randomPosition).setBackground(ContextCompat.getDrawable(Roulette.this, R.drawable.selected_rectangle));
}
mp.start();
if (count++ < numberMovements) {//Time it takes to change the rectangle, before the winner 70% of the time it takes 175ms and 30% of the time it takes 250ms, to change to the winner it takes 100ms
handler.postDelayed(this, timeDelay());
} else {
handler.postDelayed(this, 100);
}
}
};
handler.post(runnable);
}
private int timeDelay(){
int a = new Random().nextInt(100);
if (a >= 30){
return 175;
} else {
return 250;
}
}
Color changes, speed and award selection work fine, I have 2 sound issues unfortunately. The first problem is that the sound does not match the color change of the roulette wheel, it seems that the sound is slower, the second problem is that the sound seems to go into an infinite loop since it never finishes playing
I realize that the first problem happens because the duration of the sound is greater than the duration of timeDelay (), when timeDelay > duration of sound, it matches the change of the selected rectangle with sound, but I need timeDelay () to be shorter for a fluid motion.
Alternative 2
When I substitute mp.start(); for MediaPlayer.create(this, R.raw.pin).start(); everything matches perfectly regardless of the duration of timeDelay (), but only the first 5 positions of the wheel, then the following loop occurs: silence of about 4 seconds, the sound is played 5 times and it never ends.
Hope someone can help me, thanks

Java android OutOfMemoryError in SensorEventListener

I have a OutOfMemoryError in class in which I implements a SensorEventListener. In logs here sometimes I have a OutOfMemoryError
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
gravity = event.values;
}
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
geomagnetic = event.values;
}
if (gravity != null && geomagnetic != null) {
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, gravity, geomagnetic);
if (success) {
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
float azimuthInRadians = orientation[0];
float azimuthInDegress = (float)Math.toDegrees(azimuthInRadians);
if (azimuthInDegress < 0.0f) {
azimuthInDegress += 360.0f;
}
azimuth = (int) azimuthInDegress;
Hawk.put(HawkConst.AZIMUTH, azimuth);
}
}
}
This is a logs which I have. Sometimes I have this error but no always
at com.android.internal.util.FastXmlSerializer.<init>(FastXmlSerializer.java:55)
at com.android.internal.util.XmlUtils.writeMapXml(XmlUtils.java:177)
at android.app.SharedPreferencesImpl.writeToFile(SharedPreferencesImpl.java:596)
at android.app.SharedPreferencesImpl.access$800(SharedPreferencesImpl.java:52)
at android.app.SharedPreferencesImpl$2.run(SharedPreferencesImpl.java:511)
at android.app.SharedPreferencesImpl.enqueueDiskWrite(SharedPreferencesImpl.java:532)
at android.app.SharedPreferencesImpl.access$100(SharedPreferencesImpl.java:52)
at android.app.SharedPreferencesImpl$EditorImpl.commit(SharedPreferencesImpl.java:454)
at com.orhanobut.hawk.SharedPreferencesStorage.put(SharedPreferencesStorage.java:23)
at com.orhanobut.hawk.Hawk.put(Hawk.java:63)
at pl.***.****.worker.Tracker.onSensorChanged(Tracker.java:154)
at android.hardware.SystemSensorManager$SensorEventQueue.dispatchSensorEvent(SystemSensorManager.java:474)
at android.os.MessageQueue.nativePollOnce(MessageQueue.java)
at android.os.MessageQueue.next(MessageQueue.java:138)
at android.os.Looper.loop(Looper.java:131)
at android.app.ActivityThread.main(ActivityThread.java:5593)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
at dalvik.system.NativeStart.main(NativeStart.java)
It seems that you are doing some heavy lifting in your onSensorChanged() callback. So in your callback you are calling SharedPreferences. Reason why it is bad is that onSensorChanged() is called everytime there is new value from sensor, based on you listener config this might happen multiple time a second. This means that you are trying to save to file (with sharedpreferences) multiple times a sec. This requires a lot of allocations on andorid side of things and might result in OutOfMemoryError.
To solve this I would advise storing value in variable and only execute save in some constant intervals or based on some events (button click, lifecycle event, etc.).
Also I see that you are using .commit() which blocks thread till file is saved, you could try to play around with .apply(), which moves commiting action to other thread. Reagardless you need to limit how many times you are using Sharedpreferences.

Asynchronous audio player with java for Android

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);
}

Using event driven input detection in LIBGDX for moving actors

I'm having trouble with event driven input detection for moving my actors.
I'm currently using a GestureDetector and detecting a Tap (this all works)`
#Override
public void show() {
GestureDetector GD = new GestureDetector(this);
InputMultiplexer inputMulti = new InputMultiplexer();
inputMulti.addProcessor(hudStage);
inputMulti.addProcessor(GD);
Gdx.input.setInputProcessor(inputMulti);
}
#Override
public boolean tap(float x, float y, int count, int button) {
System.out.println("YOU HAVE PERFORMED A TAP");
this.playStage.act(Gdx.graphics.getDeltaTime());
return true;
}`
Currently tap just prints out something along the lines of you have performed a tap(see above) it also calls the stage act method which works partly (It moves the actor a frames worth of movement instead of the entire distance). I've used polling to get the actor to perform how I want as i can call the method in the render loop which then allows it to continue updating movements frame by frame. Using Event driven I've not currently found a of updating it frame by frame so instead it does as much as it can in one frame and then stops.
I've looked around, a lot, looked into threading but it seems libgdx is not thread safe. My main problem with going back to polling is that i need to separate out my stages and I'm not entirely sure how to do that.
For polling i didn't use an action but used this code
if(!fStarted){
if (Gdx.input.isTouched()) {
fStarted = true;
touchX = Gdx.input.getX();
spriteX = cSprite.x;
}
}
final float dv = delta * speed;
if (Math.abs(touchX - spriteX) > 45) {
playStage.getBatch().draw(flameImage, spriteX, fSprite.y);
if (spriteX < touchX) {
spriteX += dv;
//fSprite.x = spriteX;
}
if (spriteX > touchX) {
spriteX -= dv;
//fSprite.x = spriteX;
}
}
else{
spriteX = 9000;
fStarted=false;
}
fStarted just determines whether or not the actor is currently moving to a position. As the actor is a projectile I didn't want more than one at one point (game choices you know).
If there is any other information you need just comment and I'll provide it.
Clarity
How can I use event driven gesture detection to move an actor to a position that is tapped by the user?

Getting current fingers position on Android

I'm developing an application that needs to get all fingers on the screen of the Android. That's not complex, and I could make it in a few lines of code.
However, the way android sends callbacks is not what I need. I have done many tests, and here is what I could find out:
Once he sent the fingers positions, if no "big" movement has been made, he will not send a onTouch event.
Also, when a finger is REMOVED from the screen (for example: there are 3 fingers, and one is removed), it seems that it ONLY sends the next event, if at least one of the remaining fingers move on the screen.
I'm doing some tracking on fingers, and matching with objects, and to do this properly, I need to know all the fingers positions all the time. If there is not a way to "request" finger's touch event even when it didn't moved, how can I access the current finger positions without an callback event? Is there any other solution?
Here is my code:
ArrayList<Vector3> fingerTips = new ArrayList<Vector3>();
#Override
public boolean onTouchEvent(MotionEvent event) {
final int points = event.getPointerCount();
String out = "==========\n";
fingerTips.clear();
for(int i = 0; i < points; i++){
out += "\tPoint "+i+"\t("+event.getX(i)+", "+event.getY(i) + ")\n";
fingerTips.add( new Vector3(event.getX(i), event.getY(i)) );
}
Log.i(TAG, out);
// Send touches to SurfaceView
chwaziViewGL.onUpdateFingerTips(fingerTips);
return true;
}
My problem was that I didn't received one event with all the finger positions AFTER removing a finger...
After lot's of tests and logging, I discovered that it's true (in part).
Whenever you REMOVE a finger on the SCREEN, the NEXT event sent is an ACTION_POINTER_UP or ACTION_UP, meaning that the finger x is no longer touching the screen.
Since there was NO motion after removing the finger, the last MotionEvent sent was the UP, containg the removed finger also.
So, to fix that, I check if the action is UP, and on the loop that get's all the fingers, I created an if checking if that was the finger removed from the screen. If so, I just didn't add it to the array.
Here is my final code:
ArrayList<Vector3> fingerTips = new ArrayList<Vector3>();
#Override
public boolean onTouchEvent(MotionEvent event) {
final int points = event.getPointerCount();
// Check if it's an event that a finger
// was removed, if so, set removedPoint
int removedPoint = -1;
final int action = event.getAction() & MotionEvent.ACTION_MASK;
if(action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP)
removedPoint = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
String out = "==========\n";
fingerTips.clear();
for(int i = 0; i < points; i++){
// Find out pointer ID
int pointerID = event.getPointerId(i);
if(pointerID == MotionEvent.INVALID_POINTER_ID){
out += "\tPoint "+pointerID+" INVALID\n";
continue;
}
// Check if it's the removed finger
if(removedPoint == i){
out += "\tPoint "+pointerID+" REMOVED\n";
continue;
}
out += "\tPoint "+pointerID+"\t("+event.getX(i)+", "+event.getY(i) + ")\n";
fingerTips.add( new Vector3(event.getX(i), event.getY(i), pointerID) );
}
Log.i(TAG, out);
// Send touches to SurfaceView
chwaziViewGL.onUpdateFingerTips(fingerTips);
return true;
}
Sorry, but your code cost me a lot of hairs;-(( After days I found the problem finally before I got insane. This code
final int action = event.getAction() & MotionEvent.ACTION_MASK;
if(action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_UP)
removedPoint = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
could have never been worked!!!
Why? Because if you mask the value that is coming from getAction() it is simply a plain integer value of the action. And when you try to use this plain value and masking it again with something, it has no meaning at all!
The correction is to use again event.getAction() in the seconds masking:
removedPoint = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
It was really a nightmare to find this. But anyway, I think you just wanted the best and help others:-))) Its ok.

Categories